Feature: Gasthund-Zugang für Sitter
- sitting_subscriptions Tabelle (dog_id, owner_id, sitter_id, valid_until) - POST/DELETE/GET /api/sitting-access — Zugang gewähren/widerrufen/auflisten - GET /api/dogs gibt Gasthunde zurück (is_guest=True, sitting_until, owner_name) - Diary POST erlaubt Sitter-Schreibzugang; PATCH/DELETE nur für Besitzer - Dog-Switcher: GAST-Badge bei fremden Hunden - Dog-Profil: Sitter-Zugang-Sektion (nur für Besitzer), Freund auswählen + Datum - Diary Detail-View: Bearbeiten-Button für Gasthunde ausgeblendet
This commit is contained in:
parent
eef787cc72
commit
289158b2cd
10 changed files with 327 additions and 18 deletions
|
|
@ -54,6 +54,29 @@ def _own_dog(dog_id: int, user_id: int, conn):
|
|||
return dog
|
||||
|
||||
|
||||
def _can_read_dog(dog_id: int, user_id: int, conn):
|
||||
"""Eigener Hund ODER geteilter Hund ODER aktiver Sitter-Zugang (Lesezugriff)."""
|
||||
dog = conn.execute(
|
||||
"SELECT id FROM dogs WHERE id=? AND user_id=?", (dog_id, user_id)
|
||||
).fetchone()
|
||||
if not dog:
|
||||
dog = conn.execute(
|
||||
"""SELECT d.id FROM dogs d
|
||||
JOIN dog_shares ds ON ds.dog_id = d.id
|
||||
WHERE d.id=? AND ds.shared_with_id=? AND ds.accepted_at IS NOT NULL""",
|
||||
(dog_id, user_id)
|
||||
).fetchone()
|
||||
if not dog:
|
||||
dog = conn.execute(
|
||||
"""SELECT 1 FROM sitting_subscriptions
|
||||
WHERE dog_id=? AND sitter_id=? AND valid_until >= date('now')""",
|
||||
(dog_id, user_id)
|
||||
).fetchone()
|
||||
if not dog:
|
||||
raise HTTPException(404, "Hund nicht gefunden.")
|
||||
return dog
|
||||
|
||||
|
||||
def _validate_dog_ids(dog_ids: list[int], primary: int, user_id: int, conn) -> list[int]:
|
||||
"""Stellt sicher dass alle IDs dem User gehören. Gibt die bereinigte Liste zurück."""
|
||||
all_ids = list({primary} | set(dog_ids))
|
||||
|
|
@ -123,7 +146,7 @@ async def list_diary(dog_id: int, limit: int = 20, offset: int = 0,
|
|||
q: Optional[str] = None, milestone: int = 0,
|
||||
user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
_own_dog(dog_id, user["id"], conn)
|
||||
_can_read_dog(dog_id, user["id"], conn)
|
||||
extra = "AND (d.is_milestone=1 OR d.typ='meilenstein')" if milestone else ""
|
||||
if q:
|
||||
pattern = f"%{q}%"
|
||||
|
|
@ -167,8 +190,24 @@ async def create_diary(dog_id: int, data: DiaryCreate,
|
|||
pass
|
||||
|
||||
with db() as conn:
|
||||
_own_dog(dog_id, user["id"], conn)
|
||||
all_dogs = _validate_dog_ids(data.dog_ids or [], dog_id, user["id"], conn)
|
||||
# Erlaubnis: eigener Hund ODER aktiver Sitter-Zugang
|
||||
dog = conn.execute("SELECT user_id FROM dogs WHERE id=?", (dog_id,)).fetchone()
|
||||
is_owner = dog and dog["user_id"] == user["id"]
|
||||
is_sitter = conn.execute("""
|
||||
SELECT 1 FROM sitting_subscriptions
|
||||
WHERE dog_id=? AND sitter_id=? AND valid_until >= date('now')
|
||||
""", (dog_id, user["id"])).fetchone()
|
||||
if not is_owner and not is_sitter:
|
||||
# Fallback: shared dog check
|
||||
try:
|
||||
_own_dog(dog_id, user["id"], conn)
|
||||
except HTTPException:
|
||||
raise HTTPException(403, "Kein Zugriff auf diesen Hund.")
|
||||
# Sitter darf nur den Gasthund als einzigen Hund eintragen
|
||||
if is_sitter and not is_owner:
|
||||
all_dogs = [dog_id]
|
||||
else:
|
||||
all_dogs = _validate_dog_ids(data.dog_ids or [], dog_id, user["id"], conn)
|
||||
|
||||
conn.execute(
|
||||
"""INSERT INTO diary
|
||||
|
|
@ -320,7 +359,7 @@ async def nearby_places(dog_id: int, lat: float, lon: float,
|
|||
@router.get("/{dog_id}/diary/{entry_id}")
|
||||
async def get_diary(dog_id: int, entry_id: int, user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
_own_dog(dog_id, user["id"], conn)
|
||||
_can_read_dog(dog_id, user["id"], conn)
|
||||
row = conn.execute(
|
||||
"""SELECT DISTINCT d.* FROM diary d
|
||||
LEFT JOIN diary_dogs dd ON dd.diary_id = d.id
|
||||
|
|
@ -339,7 +378,17 @@ async def get_diary(dog_id: int, entry_id: int, user=Depends(get_current_user)):
|
|||
async def update_diary(dog_id: int, entry_id: int, data: DiaryUpdate,
|
||||
user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
_own_dog(dog_id, user["id"], conn)
|
||||
# Nur Besitzer des Hundes darf bearbeiten, NICHT Sitter
|
||||
entry_owner = conn.execute(
|
||||
"SELECT d.user_id FROM diary dg JOIN dogs d ON d.id=dg.dog_id WHERE dg.id=?",
|
||||
(entry_id,)
|
||||
).fetchone()
|
||||
if not entry_owner or entry_owner["user_id"] != user["id"]:
|
||||
# Prüfen ob geteilter Hund (dog_shares)
|
||||
try:
|
||||
_own_dog(dog_id, user["id"], conn)
|
||||
except HTTPException:
|
||||
raise HTTPException(403, "Nur der Besitzer darf Einträge bearbeiten.")
|
||||
|
||||
# Prüfen ob Eintrag diesem Hund gehört (direkt oder via diary_dogs)
|
||||
exists = conn.execute(
|
||||
|
|
@ -383,7 +432,16 @@ async def update_diary(dog_id: int, entry_id: int, data: DiaryUpdate,
|
|||
@router.delete("/{dog_id}/diary/{entry_id}", status_code=204)
|
||||
async def delete_diary(dog_id: int, entry_id: int, user=Depends(get_current_user)):
|
||||
with db() as conn:
|
||||
_own_dog(dog_id, user["id"], conn)
|
||||
# Nur Besitzer des Hundes darf löschen, NICHT Sitter
|
||||
entry_owner = conn.execute(
|
||||
"SELECT d.user_id FROM diary dg JOIN dogs d ON d.id=dg.dog_id WHERE dg.id=?",
|
||||
(entry_id,)
|
||||
).fetchone()
|
||||
if not entry_owner or entry_owner["user_id"] != user["id"]:
|
||||
try:
|
||||
_own_dog(dog_id, user["id"], conn)
|
||||
except HTTPException:
|
||||
raise HTTPException(403, "Nur der Besitzer darf Einträge löschen.")
|
||||
conn.execute(
|
||||
"DELETE FROM diary WHERE id=? AND dog_id=?", (entry_id, dog_id)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue