add homekeeper stack
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
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_actions(
|
||||
colony_id: int = Query(...),
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
SELECT * FROM colony_actions WHERE colony_id = :colony_id
|
||||
ORDER BY date::DATE DESC, created_at DESC
|
||||
"""), {"colony_id": colony_id}).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("", status_code=201)
|
||||
def create_action(
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO colony_actions (colony_id, date, time, type, notes, related_colony_id)
|
||||
VALUES (:colony_id, :date, :time, :type, :notes, :related_colony_id)
|
||||
RETURNING *
|
||||
"""), {
|
||||
"colony_id": body.get("colony_id"),
|
||||
"date": body.get("date"),
|
||||
"time": body.get("time"),
|
||||
"type": body.get("type"),
|
||||
"notes": body.get("notes"),
|
||||
"related_colony_id": body.get("related_colony_id"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.get("/{id}")
|
||||
def get_action(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(
|
||||
text("SELECT * FROM colony_actions WHERE id = :id"), {"id": id}
|
||||
).fetchone()
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.put("/{id}")
|
||||
def update_action(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE colony_actions SET date = :date, type = :type, notes = :notes
|
||||
WHERE id = :id RETURNING *
|
||||
"""), {
|
||||
"id": id,
|
||||
"date": body.get("date"),
|
||||
"type": body.get("type"),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(result)
|
||||
|
||||
|
||||
@router.delete("/{id}", status_code=204)
|
||||
def delete_action(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
session.execute(text("DELETE FROM colony_actions WHERE id = :id"), {"id": id})
|
||||
session.commit()
|
||||
@@ -0,0 +1,99 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
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_apiaries(
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
SELECT a.*, COUNT(c.id) AS colony_count
|
||||
FROM apiaries a
|
||||
LEFT JOIN colonies c ON c.apiary_id = a.id AND c.closed_at IS NULL
|
||||
GROUP BY a.id
|
||||
ORDER BY a.name
|
||||
""")).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.get("/{id}")
|
||||
def get_apiary(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(
|
||||
text("SELECT * FROM apiaries WHERE 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_apiary(
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO apiaries (name, location_text, lat, lng, notes)
|
||||
VALUES (:name, :location_text, :lat, :lng, :notes)
|
||||
RETURNING *
|
||||
"""), {
|
||||
"name": body.get("name"),
|
||||
"location_text": body.get("location_text"),
|
||||
"lat": body.get("lat"),
|
||||
"lng": body.get("lng"),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.put("/{id}")
|
||||
def update_apiary(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE apiaries
|
||||
SET name = :name, location_text = :location_text, lat = :lat, lng = :lng, notes = :notes
|
||||
WHERE id = :id
|
||||
RETURNING *
|
||||
"""), {
|
||||
"id": id,
|
||||
"name": body.get("name"),
|
||||
"location_text": body.get("location_text"),
|
||||
"lat": body.get("lat"),
|
||||
"lng": body.get("lng"),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(result)
|
||||
|
||||
|
||||
@router.delete("/{id}", status_code=204)
|
||||
def delete_apiary(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
session.execute(text("DELETE FROM apiaries WHERE id = :id"), {"id": id})
|
||||
session.commit()
|
||||
@@ -0,0 +1,144 @@
|
||||
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_checklists(
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("SELECT * FROM checklists ORDER BY name")).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("", status_code=201)
|
||||
def create_checklist(
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO checklists (name, scope) VALUES (:name, :scope) RETURNING *
|
||||
"""), {"name": body.get("name"), "scope": body.get("scope", "colony")}).fetchone()
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.delete("/{id}", status_code=204)
|
||||
def delete_checklist(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
session.execute(text("DELETE FROM checklists WHERE id = :id"), {"id": id})
|
||||
session.commit()
|
||||
|
||||
|
||||
@router.get("/{id}/items")
|
||||
def list_items(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
SELECT * FROM checklist_items WHERE checklist_id = :id ORDER BY position
|
||||
"""), {"id": id}).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("/{id}/items", status_code=201)
|
||||
def create_item(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO checklist_items (checklist_id, text, position)
|
||||
VALUES (:checklist_id, :text, :position) RETURNING *
|
||||
"""), {
|
||||
"checklist_id": id,
|
||||
"text": body.get("text"),
|
||||
"position": body.get("position", 0),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.delete("/items/{item_id}", status_code=204)
|
||||
def delete_item(
|
||||
item_id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
session.execute(text("DELETE FROM checklist_items WHERE id = :id"), {"id": item_id})
|
||||
session.commit()
|
||||
|
||||
|
||||
@router.get("/{id}/completions")
|
||||
def list_completions(
|
||||
id: int,
|
||||
scope_id: int = Query(...),
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
SELECT cc.*, ci.text, ci.position
|
||||
FROM checklist_completions cc
|
||||
JOIN checklist_items ci ON ci.id = cc.item_id
|
||||
WHERE cc.checklist_id = :checklist_id AND cc.scope_id = :scope_id
|
||||
ORDER BY ci.position
|
||||
"""), {"checklist_id": id, "scope_id": scope_id}).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("/{id}/completions/toggle")
|
||||
def toggle_completion(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
item_id = body.get("item_id")
|
||||
scope_id = body.get("scope_id")
|
||||
date = body.get("date")
|
||||
completed_by = body.get("completed_by")
|
||||
notes = body.get("notes")
|
||||
|
||||
existing = session.execute(text("""
|
||||
SELECT id FROM checklist_completions
|
||||
WHERE checklist_id = :checklist_id AND item_id = :item_id AND scope_id = :scope_id
|
||||
"""), {"checklist_id": id, "item_id": item_id, "scope_id": scope_id}).fetchone()
|
||||
|
||||
if existing is not None:
|
||||
session.execute(
|
||||
text("DELETE FROM checklist_completions WHERE id = :id"),
|
||||
{"id": existing._mapping["id"]},
|
||||
)
|
||||
session.commit()
|
||||
return {"toggled": False}
|
||||
else:
|
||||
session.execute(text("""
|
||||
INSERT INTO checklist_completions (checklist_id, item_id, scope_id, date, completed_by, notes)
|
||||
VALUES (:checklist_id, :item_id, :scope_id, :date, :completed_by, :notes)
|
||||
"""), {
|
||||
"checklist_id": id,
|
||||
"item_id": item_id,
|
||||
"scope_id": scope_id,
|
||||
"date": date,
|
||||
"completed_by": completed_by,
|
||||
"notes": notes,
|
||||
})
|
||||
session.commit()
|
||||
return {"toggled": True}
|
||||
@@ -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)
|
||||
@@ -0,0 +1,136 @@
|
||||
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("/summary")
|
||||
def harvest_summary(
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
SELECT
|
||||
EXTRACT(YEAR FROM date::date)::INTEGER AS year,
|
||||
type,
|
||||
SUM(weight_kg) AS total_kg,
|
||||
COUNT(*) AS n_harvests
|
||||
FROM honey_harvests
|
||||
GROUP BY year, type
|
||||
ORDER BY year DESC, total_kg DESC
|
||||
""")).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.get("")
|
||||
def list_harvests(
|
||||
colony_id: int | None = Query(default=None),
|
||||
apiary_id: int | None = Query(default=None),
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
if colony_id is not None:
|
||||
rows = session.execute(text("""
|
||||
SELECT h.*, c.name AS colony_name, a.name AS apiary_name
|
||||
FROM honey_harvests h
|
||||
LEFT JOIN colonies c ON c.id = h.colony_id
|
||||
LEFT JOIN apiaries a ON a.id = h.apiary_id
|
||||
WHERE h.colony_id = :colony_id
|
||||
ORDER BY h.date DESC
|
||||
"""), {"colony_id": colony_id}).fetchall()
|
||||
elif apiary_id is not None:
|
||||
rows = session.execute(text("""
|
||||
SELECT h.*, c.name AS colony_name, a.name AS apiary_name
|
||||
FROM honey_harvests h
|
||||
LEFT JOIN colonies c ON c.id = h.colony_id
|
||||
LEFT JOIN apiaries a ON a.id = h.apiary_id
|
||||
WHERE h.apiary_id = :apiary_id
|
||||
ORDER BY h.date DESC
|
||||
"""), {"apiary_id": apiary_id}).fetchall()
|
||||
else:
|
||||
rows = session.execute(text("""
|
||||
SELECT h.*, c.name AS colony_name, a.name AS apiary_name
|
||||
FROM honey_harvests h
|
||||
LEFT JOIN colonies c ON c.id = h.colony_id
|
||||
LEFT JOIN apiaries a ON a.id = h.apiary_id
|
||||
ORDER BY h.date DESC
|
||||
""")).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("", status_code=201)
|
||||
def create_harvest(
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO honey_harvests (date, weight_kg, colony_id, apiary_id, type, notes, time)
|
||||
VALUES (:date, :weight_kg, :colony_id, :apiary_id, :type, :notes, :time)
|
||||
RETURNING *
|
||||
"""), {
|
||||
"date": body.get("date"),
|
||||
"weight_kg": body.get("weight_kg"),
|
||||
"colony_id": body.get("colony_id"),
|
||||
"apiary_id": body.get("apiary_id"),
|
||||
"type": body.get("type"),
|
||||
"notes": body.get("notes"),
|
||||
"time": body.get("time"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.get("/{id}")
|
||||
def get_harvest(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(
|
||||
text("SELECT * FROM honey_harvests WHERE id = :id"), {"id": id}
|
||||
).fetchone()
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.put("/{id}")
|
||||
def update_harvest(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE honey_harvests SET date = :date, weight_kg = :weight_kg, notes = :notes
|
||||
WHERE id = :id RETURNING *
|
||||
"""), {
|
||||
"id": id,
|
||||
"date": body.get("date"),
|
||||
"weight_kg": body.get("weight_kg"),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(result)
|
||||
|
||||
|
||||
@router.delete("/{id}", status_code=204)
|
||||
def delete_harvest(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
session.execute(text("DELETE FROM honey_harvests WHERE id = :id"), {"id": id})
|
||||
session.commit()
|
||||
@@ -0,0 +1,164 @@
|
||||
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_inspections(
|
||||
colony_id: int = Query(...),
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
SELECT * FROM inspections WHERE colony_id = :colony_id
|
||||
ORDER BY date::DATE DESC, id DESC
|
||||
"""), {"colony_id": colony_id}).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.get("/last")
|
||||
def last_inspection(
|
||||
colony_id: int = Query(...),
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
row = session.execute(text("""
|
||||
SELECT * FROM inspections WHERE colony_id = :colony_id
|
||||
ORDER BY date::DATE DESC, id DESC LIMIT 1
|
||||
"""), {"colony_id": colony_id}).fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.get("/{id}/traits")
|
||||
def inspection_traits(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
SELECT * FROM inspection_traits WHERE inspection_id = :id
|
||||
"""), {"id": id}).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.get("/{id}")
|
||||
def get_inspection(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(
|
||||
text("SELECT * FROM inspections WHERE 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_inspection(
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO inspections
|
||||
(colony_id, date, inspector, brood_frames, honey_frames, food_stores_kg,
|
||||
colony_strength, queen_seen, eggs_seen, queen_cells_count, notes)
|
||||
VALUES
|
||||
(:colony_id, :date, :inspector, :brood_frames, :honey_frames, :food_stores_kg,
|
||||
:colony_strength, :queen_seen, :eggs_seen, :queen_cells_count, :notes)
|
||||
RETURNING *
|
||||
"""), {
|
||||
"colony_id": body.get("colony_id"),
|
||||
"date": body.get("date"),
|
||||
"inspector": body.get("inspector"),
|
||||
"brood_frames": body.get("brood_frames"),
|
||||
"honey_frames": body.get("honey_frames"),
|
||||
"food_stores_kg": body.get("food_stores_kg"),
|
||||
"colony_strength": body.get("colony_strength"),
|
||||
"queen_seen": int(body.get("queen_seen", 0)),
|
||||
"eggs_seen": int(body.get("eggs_seen", 0)),
|
||||
"queen_cells_count": int(body.get("queen_cells_count", 0)),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
inspection_id = row._mapping["id"]
|
||||
|
||||
traits = body.get("traits", {})
|
||||
for trait, score in (traits or {}).items():
|
||||
if score is not None:
|
||||
session.execute(text("""
|
||||
INSERT INTO inspection_traits (inspection_id, trait, score)
|
||||
VALUES (:inspection_id, :trait, :score)
|
||||
"""), {"inspection_id": inspection_id, "trait": trait, "score": int(score)})
|
||||
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.put("/{id}")
|
||||
def update_inspection(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE inspections SET
|
||||
date = :date, inspector = :inspector, brood_frames = :brood_frames,
|
||||
honey_frames = :honey_frames, food_stores_kg = :food_stores_kg,
|
||||
colony_strength = :colony_strength, queen_seen = :queen_seen,
|
||||
eggs_seen = :eggs_seen, queen_cells_count = :queen_cells_count, notes = :notes
|
||||
WHERE id = :id
|
||||
RETURNING *
|
||||
"""), {
|
||||
"id": id,
|
||||
"date": body.get("date"),
|
||||
"inspector": body.get("inspector"),
|
||||
"brood_frames": body.get("brood_frames"),
|
||||
"honey_frames": body.get("honey_frames"),
|
||||
"food_stores_kg": body.get("food_stores_kg"),
|
||||
"colony_strength": body.get("colony_strength"),
|
||||
"queen_seen": int(body.get("queen_seen", 0)),
|
||||
"eggs_seen": int(body.get("eggs_seen", 0)),
|
||||
"queen_cells_count": int(body.get("queen_cells_count", 0)),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
|
||||
session.execute(
|
||||
text("DELETE FROM inspection_traits WHERE inspection_id = :id"), {"id": id}
|
||||
)
|
||||
traits = body.get("traits", {})
|
||||
for trait, score in (traits or {}).items():
|
||||
if score is not None:
|
||||
session.execute(text("""
|
||||
INSERT INTO inspection_traits (inspection_id, trait, score)
|
||||
VALUES (:inspection_id, :trait, :score)
|
||||
"""), {"inspection_id": id, "trait": trait, "score": int(score)})
|
||||
|
||||
session.commit()
|
||||
return _row(result)
|
||||
|
||||
|
||||
@router.delete("/{id}", status_code=204)
|
||||
def delete_inspection(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
session.execute(text("DELETE FROM inspections WHERE id = :id"), {"id": id})
|
||||
session.commit()
|
||||
@@ -0,0 +1,149 @@
|
||||
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_lists(
|
||||
include_closed: bool = Query(default=False),
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
where = "" if include_closed else "WHERE l.closed_at IS NULL"
|
||||
order = "ORDER BY l.closed_at IS NOT NULL, l.created_at DESC" if include_closed else "ORDER BY l.created_at DESC"
|
||||
rows = session.execute(text(f"""
|
||||
SELECT
|
||||
l.*,
|
||||
COUNT(i.id) AS item_count,
|
||||
COUNT(i.id) FILTER (WHERE i.checked) AS checked_count
|
||||
FROM lk.lists l
|
||||
LEFT JOIN lk.list_items i ON i.list_id = l.id
|
||||
{where}
|
||||
GROUP BY l.id
|
||||
{order}
|
||||
""")).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("", status_code=201)
|
||||
def create_list(
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO lk.lists (name, type, t_shirt_size, notes)
|
||||
VALUES (:name, :type, :t_shirt_size, :notes)
|
||||
RETURNING *
|
||||
"""), {
|
||||
"name": body.get("name"),
|
||||
"type": body.get("type"),
|
||||
"t_shirt_size": body.get("t_shirt_size"),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.post("/{id}/close")
|
||||
def close_list(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE lk.lists SET status = 'closed', 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}/reopen")
|
||||
def reopen_list(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE lk.lists SET status = 'open', closed_at = NULL
|
||||
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.delete("/{id}", status_code=204)
|
||||
def delete_list(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
session.execute(text("DELETE FROM lk.lists WHERE id = :id"), {"id": id})
|
||||
session.commit()
|
||||
|
||||
|
||||
@router.get("/{id}/items")
|
||||
def list_items(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
SELECT * FROM lk.list_items WHERE list_id = :list_id ORDER BY position, created_at
|
||||
"""), {"list_id": id}).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("/{id}/items", status_code=201)
|
||||
def create_item(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO lk.list_items (list_id, text) VALUES (:list_id, :text) RETURNING *
|
||||
"""), {"list_id": id, "text": body.get("text")}).fetchone()
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.patch("/items/{id}")
|
||||
def update_item_checked(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE lk.list_items SET checked = :checked WHERE id = :id RETURNING *
|
||||
"""), {"id": id, "checked": bool(body.get("checked", False))}).fetchone()
|
||||
session.commit()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(result)
|
||||
|
||||
|
||||
@router.delete("/items/{id}", status_code=204)
|
||||
def delete_item(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
session.execute(text("DELETE FROM lk.list_items WHERE id = :id"), {"id": id})
|
||||
session.commit()
|
||||
@@ -0,0 +1,141 @@
|
||||
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_queens(
|
||||
colony_id: int | None = Query(default=None),
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
params: dict[str, Any] = {}
|
||||
where = ""
|
||||
if colony_id is not None:
|
||||
where = "WHERE q.colony_id = :colony_id"
|
||||
params["colony_id"] = colony_id
|
||||
|
||||
rows = session.execute(text(f"""
|
||||
SELECT q.*, c.name AS colony_name, o.name AS origin_colony_name
|
||||
FROM queens q
|
||||
LEFT JOIN colonies c ON c.id = q.colony_id
|
||||
LEFT JOIN colonies o ON o.id = q.origin_colony_id
|
||||
{where}
|
||||
ORDER BY q.introduced_at DESC
|
||||
"""), params).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("", status_code=201)
|
||||
def create_queen(
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO queens (colony_id, year, breed, origin_colony_id, marked_color, notes, introduced_at)
|
||||
VALUES (:colony_id, :year, :breed, :origin_colony_id, :marked_color, :notes, :introduced_at)
|
||||
RETURNING *
|
||||
"""), {
|
||||
"colony_id": body.get("colony_id"),
|
||||
"year": body.get("year"),
|
||||
"breed": body.get("breed"),
|
||||
"origin_colony_id": body.get("origin_colony_id"),
|
||||
"marked_color": body.get("marked_color"),
|
||||
"notes": body.get("notes"),
|
||||
"introduced_at": body.get("introduced_at"),
|
||||
}).fetchone()
|
||||
queen_id = row._mapping["id"]
|
||||
colony_id = body.get("colony_id")
|
||||
if colony_id is not None:
|
||||
session.execute(text("""
|
||||
UPDATE colonies SET current_queen_id = :queen_id WHERE id = :colony_id
|
||||
"""), {"queen_id": queen_id, "colony_id": colony_id})
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.get("/{id}")
|
||||
def get_queen(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(
|
||||
text("SELECT * FROM queens WHERE id = :id"), {"id": id}
|
||||
).fetchone()
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.put("/{id}")
|
||||
def update_queen(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE queens SET
|
||||
colony_id = :colony_id, year = :year, breed = :breed,
|
||||
origin_colony_id = :origin_colony_id, marked_color = :marked_color,
|
||||
notes = :notes, introduced_at = :introduced_at, superseded_at = :superseded_at
|
||||
WHERE id = :id
|
||||
RETURNING *
|
||||
"""), {
|
||||
"id": id,
|
||||
"colony_id": body.get("colony_id"),
|
||||
"year": body.get("year"),
|
||||
"breed": body.get("breed"),
|
||||
"origin_colony_id": body.get("origin_colony_id"),
|
||||
"marked_color": body.get("marked_color"),
|
||||
"notes": body.get("notes"),
|
||||
"introduced_at": body.get("introduced_at"),
|
||||
"superseded_at": body.get("superseded_at"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(result)
|
||||
|
||||
|
||||
@router.post("/{id}/supersede")
|
||||
def supersede_queen(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE queens SET superseded_at = CURRENT_DATE WHERE id = :id RETURNING *
|
||||
"""), {"id": id}).fetchone()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
colony_id = result._mapping.get("colony_id")
|
||||
if colony_id is not None:
|
||||
session.execute(text("""
|
||||
UPDATE colonies SET current_queen_id = NULL
|
||||
WHERE id = :colony_id AND current_queen_id = :queen_id
|
||||
"""), {"colony_id": colony_id, "queen_id": id})
|
||||
session.commit()
|
||||
return _row(result)
|
||||
|
||||
|
||||
@router.delete("/{id}", status_code=204)
|
||||
def delete_queen(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
session.execute(text("DELETE FROM queens WHERE id = :id"), {"id": id})
|
||||
session.commit()
|
||||
@@ -0,0 +1,284 @@
|
||||
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)
|
||||
|
||||
|
||||
# ---- Counts ----
|
||||
|
||||
@router.get("/counts")
|
||||
def list_counts(
|
||||
colony_id: int = Query(...),
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
SELECT * FROM varroa_counts WHERE colony_id = :colony_id ORDER BY date DESC
|
||||
"""), {"colony_id": colony_id}).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("/counts", status_code=201)
|
||||
def create_count(
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO varroa_counts (colony_id, date_from, date, method, count, notes, time)
|
||||
VALUES (:colony_id, :date_from, :date, :method, :count, :notes, :time)
|
||||
RETURNING *
|
||||
"""), {
|
||||
"colony_id": body.get("colony_id"),
|
||||
"date_from": body.get("date_from"),
|
||||
"date": body.get("date"),
|
||||
"method": body.get("method"),
|
||||
"count": body.get("count"),
|
||||
"notes": body.get("notes"),
|
||||
"time": body.get("time"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.get("/counts/{id}")
|
||||
def get_count(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(
|
||||
text("SELECT * FROM varroa_counts WHERE id = :id"), {"id": id}
|
||||
).fetchone()
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.put("/counts/{id}")
|
||||
def update_count(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE varroa_counts
|
||||
SET date_from = :date_from, date = :date, method = :method, count = :count, notes = :notes
|
||||
WHERE id = :id
|
||||
RETURNING *
|
||||
"""), {
|
||||
"id": id,
|
||||
"date_from": body.get("date_from"),
|
||||
"date": body.get("date"),
|
||||
"method": body.get("method"),
|
||||
"count": body.get("count"),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(result)
|
||||
|
||||
|
||||
@router.delete("/counts/{id}", status_code=204)
|
||||
def delete_count(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
session.execute(text("DELETE FROM varroa_counts WHERE id = :id"), {"id": id})
|
||||
session.commit()
|
||||
|
||||
|
||||
# ---- Treatments ----
|
||||
|
||||
@router.get("/treatments")
|
||||
def list_treatments(
|
||||
colony_id: int = Query(...),
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
rows = session.execute(text("""
|
||||
SELECT * FROM varroa_treatments WHERE colony_id = :colony_id ORDER BY date DESC
|
||||
"""), {"colony_id": colony_id}).fetchall()
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("/treatments", status_code=201)
|
||||
def create_treatment(
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO varroa_treatments (colony_id, date, product, method, dosage, notes, time)
|
||||
VALUES (:colony_id, :date, :product, :method, :dosage, :notes, :time)
|
||||
RETURNING *
|
||||
"""), {
|
||||
"colony_id": body.get("colony_id"),
|
||||
"date": body.get("date"),
|
||||
"product": body.get("product"),
|
||||
"method": body.get("method"),
|
||||
"dosage": body.get("dosage"),
|
||||
"notes": body.get("notes"),
|
||||
"time": body.get("time"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.get("/treatments/{id}")
|
||||
def get_treatment(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(
|
||||
text("SELECT * FROM varroa_treatments WHERE id = :id"), {"id": id}
|
||||
).fetchone()
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.put("/treatments/{id}")
|
||||
def update_treatment(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE varroa_treatments
|
||||
SET date = :date, product = :product, method = :method, dosage = :dosage, notes = :notes
|
||||
WHERE id = :id
|
||||
RETURNING *
|
||||
"""), {
|
||||
"id": id,
|
||||
"date": body.get("date"),
|
||||
"product": body.get("product"),
|
||||
"method": body.get("method"),
|
||||
"dosage": body.get("dosage"),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(result)
|
||||
|
||||
|
||||
@router.delete("/treatments/{id}", status_code=204)
|
||||
def delete_treatment(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
session.execute(text("DELETE FROM varroa_treatments WHERE id = :id"), {"id": id})
|
||||
session.commit()
|
||||
|
||||
|
||||
# ---- Controls ----
|
||||
|
||||
@router.get("/controls")
|
||||
def list_controls(
|
||||
colony_id: int | None = Query(default=None),
|
||||
treatment_id: int | None = Query(default=None),
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> list[dict]:
|
||||
if treatment_id is not None:
|
||||
rows = session.execute(text("""
|
||||
SELECT vc.*, vt.product, vt.date AS treatment_date
|
||||
FROM varroa_controls vc
|
||||
JOIN varroa_treatments vt ON vt.id = vc.treatment_id
|
||||
WHERE vc.treatment_id = :treatment_id
|
||||
ORDER BY vc.date DESC
|
||||
"""), {"treatment_id": treatment_id}).fetchall()
|
||||
elif colony_id is not None:
|
||||
rows = session.execute(text("""
|
||||
SELECT vc.*, vt.product, vt.date AS treatment_date
|
||||
FROM varroa_controls vc
|
||||
JOIN varroa_treatments vt ON vt.id = vc.treatment_id
|
||||
WHERE vc.colony_id = :colony_id
|
||||
ORDER BY vc.date DESC
|
||||
"""), {"colony_id": colony_id}).fetchall()
|
||||
else:
|
||||
return []
|
||||
return [_row(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("/controls", status_code=201)
|
||||
def create_control(
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(text("""
|
||||
INSERT INTO varroa_controls (treatment_id, colony_id, date, result, notes)
|
||||
VALUES (:treatment_id, :colony_id, :date, :result, :notes)
|
||||
RETURNING *
|
||||
"""), {
|
||||
"treatment_id": body.get("treatment_id"),
|
||||
"colony_id": body.get("colony_id"),
|
||||
"date": body.get("date"),
|
||||
"result": body.get("result"),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.get("/controls/{id}")
|
||||
def get_control(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
row = session.execute(
|
||||
text("SELECT * FROM varroa_controls WHERE id = :id"), {"id": id}
|
||||
).fetchone()
|
||||
if row is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(row)
|
||||
|
||||
|
||||
@router.put("/controls/{id}")
|
||||
def update_control(
|
||||
id: int,
|
||||
body: dict[str, Any],
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
) -> dict:
|
||||
result = session.execute(text("""
|
||||
UPDATE varroa_controls SET date = :date, result = :result, notes = :notes
|
||||
WHERE id = :id RETURNING *
|
||||
"""), {
|
||||
"id": id,
|
||||
"date": body.get("date"),
|
||||
"result": body.get("result"),
|
||||
"notes": body.get("notes"),
|
||||
}).fetchone()
|
||||
session.commit()
|
||||
if result is None:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
return _row(result)
|
||||
|
||||
|
||||
@router.delete("/controls/{id}", status_code=204)
|
||||
def delete_control(
|
||||
id: int,
|
||||
session: Session = Depends(get_session),
|
||||
_: str = Depends(get_current_user),
|
||||
):
|
||||
session.execute(text("DELETE FROM varroa_controls WHERE id = :id"), {"id": id})
|
||||
session.commit()
|
||||
Reference in New Issue
Block a user