feat(children): support soft delete child profiles

This commit is contained in:
stu2not
2026-05-25 17:27:12 +08:00
parent 5d86ee5826
commit 8cff60f87b
4 changed files with 96 additions and 5 deletions

View File

@@ -88,3 +88,9 @@ export async function updateChild(
data, data,
}) })
} }
export async function deleteChild(childId: number): Promise<void> {
return request<void>(`/banban/children/${childId}`, {
method: 'DELETE',
})
}

View File

@@ -42,11 +42,29 @@ class ChildDAO(BaseDAO):
async def get_by_id(self, child_id: int) -> Optional[Mapping]: async def get_by_id(self, child_id: int) -> Optional[Mapping]:
return ( return (
await self.execute( await self.execute(
"SELECT * FROM children WHERE child_id = :child_id", "SELECT * FROM children WHERE child_id = :child_id AND status = 1",
{"child_id": child_id}, {"child_id": child_id},
) )
).mappings().first() ).mappings().first()
async def get_by_parent(self, child_id: int, user_id: int) -> Optional[Mapping]:
return (
await self.execute(
"""
SELECT c.*
FROM children AS c
JOIN parent_child_relations AS pcr
ON pcr.child_id = c.child_id
WHERE c.child_id = :child_id
AND pcr.user_id = :user_id
AND c.status = 1
AND pcr.status = 1
LIMIT 1
""",
{"child_id": child_id, "user_id": user_id},
)
).mappings().first()
async def list_by_parent(self, user_id: int, limit: int = 20, cursor: int = None) -> list[Mapping]: async def list_by_parent(self, user_id: int, limit: int = 20, cursor: int = None) -> list[Mapping]:
params = {"user_id": user_id, "limit": limit + 1} params = {"user_id": user_id, "limit": limit + 1}
where = "pcr.user_id = :user_id AND pcr.status = 1 AND c.status = 1" where = "pcr.user_id = :user_id AND pcr.status = 1 AND c.status = 1"
@@ -104,3 +122,41 @@ class ChildDAO(BaseDAO):
{"child_id": child_id, "user_id": user_id}, {"child_id": child_id, "user_id": user_id},
) )
return result.scalar_one_or_none() is not None return result.scalar_one_or_none() is not None
async def soft_delete_for_parent(self, child_id: int, user_id: int) -> bool:
if not await self.has_access(child_id, user_id):
return False
await self.execute(
"""
UPDATE children
SET status = 0,
updated_at = CURRENT_TIMESTAMP
WHERE child_id = :child_id
AND status = 1
""",
{"child_id": child_id},
)
await self.execute(
"""
UPDATE parent_child_relations
SET status = 0,
updated_at = CURRENT_TIMESTAMP
WHERE child_id = :child_id
AND user_id = :user_id
AND status = 1
""",
{"child_id": child_id, "user_id": user_id},
)
await self.execute(
"""
UPDATE device_bindings
SET child_id = NULL,
updated_at = CURRENT_TIMESTAMP
WHERE child_id = :child_id
AND owner_user_id = :user_id
AND status = 1
""",
{"child_id": child_id, "user_id": user_id},
)
return True

View File

@@ -72,8 +72,9 @@ async def get_child(
request: Request, request: Request,
current_user_id: int = Depends(get_current_user_id), current_user_id: int = Depends(get_current_user_id),
) -> ChildResponse: ) -> ChildResponse:
del request
service = ChildService() service = ChildService()
child = await service.get(child_id) child = await service.get(child_id, current_user_id)
if not child: if not child:
raise HTTPException(status_code=404, detail="child not found") raise HTTPException(status_code=404, detail="child not found")
return ChildResponse(**child) return ChildResponse(**child)
@@ -94,3 +95,16 @@ async def update_child(
if not child: if not child:
raise HTTPException(status_code=404, detail="child not found") raise HTTPException(status_code=404, detail="child not found")
return ChildResponse(**child) return ChildResponse(**child)
@router.delete("/{child_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_child(
child_id: int,
request: Request,
current_user_id: int = Depends(get_current_user_id),
) -> None:
del request
service = ChildService()
deleted = await service.delete(child_id, current_user_id)
if not deleted:
raise HTTPException(status_code=404, detail="child not found")

View File

@@ -52,11 +52,11 @@ class ChildService(DatabaseServiceBase):
finally: finally:
await db_session.close() await db_session.close()
async def get(self, child_id: int) -> Optional[Mapping]: async def get(self, child_id: int, user_id: int) -> Optional[Mapping]:
db_session = await self.get_session() db_session = await self.get_session()
try: try:
dao = ChildDAO(db_session) dao = ChildDAO(db_session)
return await dao.get_by_id(child_id) return await dao.get_by_parent(child_id, user_id)
finally: finally:
await db_session.close() await db_session.close()
@@ -75,6 +75,21 @@ class ChildService(DatabaseServiceBase):
raise PermissionError("No access to this child") raise PermissionError("No access to this child")
await dao.update(child_id, child_name, child_gender, child_birthday) await dao.update(child_id, child_name, child_gender, child_birthday)
await db_session.commit() await db_session.commit()
return await self.get(child_id) return await self.get(child_id, user_id)
finally:
await db_session.close()
async def delete(self, child_id: int, user_id: int) -> bool:
db_session = await self.get_session()
try:
dao = ChildDAO(db_session)
deleted = await dao.soft_delete_for_parent(child_id, user_id)
if not deleted:
return False
await db_session.commit()
return True
except Exception:
await db_session.rollback()
raise
finally: finally:
await db_session.close() await db_session.close()