Now with some soft delete options for safer operations.
This commit is contained in:
@@ -44,11 +44,13 @@ I am an interactive CLI agent assisting with software engineering tasks for One
|
|||||||
- **Security Hardening:** Implemented a 5-level recursion depth limit and a field allowlist (`searchable_fields`) for the Search API.
|
- **Security Hardening:** Implemented a 5-level recursion depth limit and a field allowlist (`searchable_fields`) for the Search API.
|
||||||
- **Non-blocking Concurrency:** Standardized on `asyncio.sleep()` for delay simulation to prevent Gunicorn worker hangs.
|
- **Non-blocking Concurrency:** Standardized on `asyncio.sleep()` for delay simulation to prevent Gunicorn worker hangs.
|
||||||
|
|
||||||
## Session Learnings & Progress (Jan 2-3, 2026)
|
## Session Learnings & Progress (Jan 2-5, 2026)
|
||||||
|
|
||||||
### V3 CRUD Infrastructure & Security
|
### V3 CRUD Infrastructure & Security
|
||||||
- **Modular Object Definitions**: Successfully refactored the monolithic `ae_obj_types_def.py` into a domain-driven structure under `app/object_definitions/`. This improved maintainability while keeping legacy V2 keys for backward compatibility.
|
- **Modular Object Definitions**: Successfully refactored the monolithic `ae_obj_types_def.py` into a domain-driven structure under `app/object_definitions/`. This improved maintainability while keeping legacy V2 keys for backward compatibility.
|
||||||
- **Advanced Search (POST)**: Implemented a robust `/search` endpoint supporting recursive AND/OR logic and standardized full-text search via the `q` property.
|
- **Advanced Search (POST)**: Implemented a robust `/search` endpoint supporting recursive AND/OR logic and standardized full-text search via the `q` property.
|
||||||
|
- **Soft Delete Implementation**: Updated `DELETE /v3/crud/{obj}/{id}` and its child equivalent to support a `method` query parameter (`delete`, `hide`, `disable`). This allows for soft deletion by setting `hide=True` or `enable=False`, while preserving the default hard delete behavior.
|
||||||
|
- **Badge Model Updates**: Added `print_count`, `print_first_datetime`, and `print_last_datetime` to `Event_Badge_Basic_Base` to ensure these fields are returned in basic badge queries.
|
||||||
- **Security Hardening**: Enforced a 5-level recursion depth limit and a field allowlist (`searchable_fields`) per object to prevent unauthorized data leaks.
|
- **Security Hardening**: Enforced a 5-level recursion depth limit and a field allowlist (`searchable_fields`) per object to prevent unauthorized data leaks.
|
||||||
- **JWT Authentication**: Implemented modern JWT validation for V3, supporting both the `Authorization` header and a `jwt` query parameter (enabling secure, header-free file downloads).
|
- **JWT Authentication**: Implemented modern JWT validation for V3, supporting both the `Authorization` header and a `jwt` query parameter (enabling secure, header-free file downloads).
|
||||||
- **Frontend Integration**: Created a dedicated `V3_FRONTEND_API_GUIDE.md` to help the Svelte Gemini agent and developers migrate to the new endpoints.
|
- **Frontend Integration**: Created a dedicated `V3_FRONTEND_API_GUIDE.md` to help the Svelte Gemini agent and developers migrate to the new endpoints.
|
||||||
|
|||||||
@@ -450,6 +450,7 @@ async def delete_obj(
|
|||||||
response: Response,
|
response: Response,
|
||||||
obj_type_l1: str = Path(min_length=2, max_length=50),
|
obj_type_l1: str = Path(min_length=2, max_length=50),
|
||||||
obj_id: str = Path(min_length=11, max_length=22),
|
obj_id: str = Path(min_length=11, max_length=22),
|
||||||
|
method: str = Query('delete', regex='^(delete|hide|disable)$', description="Deletion method: delete (hard), hide (soft), disable (soft)"),
|
||||||
account: AccountContext = Depends(get_account_context),
|
account: AccountContext = Depends(get_account_context),
|
||||||
delay: DelayParams = Depends(get_delay_params),
|
delay: DelayParams = Depends(get_delay_params),
|
||||||
):
|
):
|
||||||
@@ -457,8 +458,9 @@ async def delete_obj(
|
|||||||
Delete a top-level object.
|
Delete a top-level object.
|
||||||
|
|
||||||
Soft Delete:
|
Soft Delete:
|
||||||
- Note that 'sql_delete' may implement soft-delete behavior depending on the
|
- Use 'method=hide' to set 'hide=True'.
|
||||||
'method' query parameter (delete, disable, hide), matching legacy behavior.
|
- Use 'method=disable' to set 'enable=False'.
|
||||||
|
- Default is 'method=delete' (hard database delete).
|
||||||
"""
|
"""
|
||||||
if delay.sleep_time_s > 0:
|
if delay.sleep_time_s > 0:
|
||||||
await asyncio.sleep(delay.sleep_time_s)
|
await asyncio.sleep(delay.sleep_time_s)
|
||||||
@@ -480,10 +482,22 @@ async def delete_obj(
|
|||||||
if not record_id:
|
if not record_id:
|
||||||
return mk_resp(data=False, status_code=404, response=response, status_message=f"Object with ID '{obj_id}' not found.")
|
return mk_resp(data=False, status_code=404, response=response, status_message=f"Object with ID '{obj_id}' not found.")
|
||||||
|
|
||||||
if sql_delete_result := sql_delete(table_name=table_name_delete, record_id=record_id):
|
if method == 'hide':
|
||||||
return mk_resp(data=True, response=response, status_message=f"Object with ID '{obj_id}' deleted successfully.")
|
if sql_update(table_name=table_name_delete, record_id=record_id, data={'hide': True}):
|
||||||
|
return mk_resp(data=True, response=response, status_message=f"Object with ID '{obj_id}' hidden successfully.")
|
||||||
|
else:
|
||||||
|
return mk_resp(data=False, status_code=400, response=response, status_message="Failed to hide object. It may not support the 'hide' field.")
|
||||||
|
elif method == 'disable':
|
||||||
|
if sql_update(table_name=table_name_delete, record_id=record_id, data={'enable': False}):
|
||||||
|
return mk_resp(data=True, response=response, status_message=f"Object with ID '{obj_id}' disabled successfully.")
|
||||||
|
else:
|
||||||
|
return mk_resp(data=False, status_code=400, response=response, status_message="Failed to disable object. It may not support the 'enable' field.")
|
||||||
else:
|
else:
|
||||||
return mk_resp(data=False, status_code=400, response=response, status_message="Failed to delete object in database. It may not have been found.")
|
# Default: hard delete
|
||||||
|
if sql_delete_result := sql_delete(table_name=table_name_delete, record_id=record_id):
|
||||||
|
return mk_resp(data=True, response=response, status_message=f"Object with ID '{obj_id}' deleted successfully.")
|
||||||
|
else:
|
||||||
|
return mk_resp(data=False, status_code=400, response=response, status_message="Failed to delete object in database. It may not have been found.")
|
||||||
|
|
||||||
|
|
||||||
@router.get('/{parent_obj_type}/{parent_obj_id}/{child_obj_type}/', response_model=Resp_Body_Base)
|
@router.get('/{parent_obj_type}/{parent_obj_id}/{child_obj_type}/', response_model=Resp_Body_Base)
|
||||||
@@ -811,6 +825,7 @@ async def delete_child_obj(
|
|||||||
parent_obj_id: str = Path(min_length=11, max_length=22),
|
parent_obj_id: str = Path(min_length=11, max_length=22),
|
||||||
child_obj_type: str = Path(min_length=2, max_length=50),
|
child_obj_type: str = Path(min_length=2, max_length=50),
|
||||||
child_obj_id: str = Path(min_length=11, max_length=22),
|
child_obj_id: str = Path(min_length=11, max_length=22),
|
||||||
|
method: str = Query('delete', regex='^(delete|hide|disable)$', description="Deletion method: delete (hard), hide (soft), disable (soft)"),
|
||||||
account: AccountContext = Depends(get_account_context),
|
account: AccountContext = Depends(get_account_context),
|
||||||
delay: DelayParams = Depends(get_delay_params),
|
delay: DelayParams = Depends(get_delay_params),
|
||||||
):
|
):
|
||||||
@@ -819,6 +834,11 @@ async def delete_child_obj(
|
|||||||
|
|
||||||
Safety:
|
Safety:
|
||||||
- Enforces parentage verification before deletion to prevent unauthorized data removal.
|
- Enforces parentage verification before deletion to prevent unauthorized data removal.
|
||||||
|
|
||||||
|
Soft Delete:
|
||||||
|
- Use 'method=hide' to set 'hide=True'.
|
||||||
|
- Use 'method=disable' to set 'enable=False'.
|
||||||
|
- Default is 'method=delete' (hard database delete).
|
||||||
"""
|
"""
|
||||||
if delay.sleep_time_s > 0:
|
if delay.sleep_time_s > 0:
|
||||||
await asyncio.sleep(delay.sleep_time_s)
|
await asyncio.sleep(delay.sleep_time_s)
|
||||||
@@ -859,11 +879,22 @@ async def delete_child_obj(
|
|||||||
else:
|
else:
|
||||||
return mk_resp(data=False, status_code=404, response=response, status_message=f"Child object '{child_obj_id}' not found.")
|
return mk_resp(data=False, status_code=404, response=response, status_message=f"Child object '{child_obj_id}' not found.")
|
||||||
|
|
||||||
# If verification passes, delete the object
|
if method == 'hide':
|
||||||
if sql_delete(table_name=table_name_delete, record_id=resolved_child_id):
|
if sql_update(table_name=table_name_delete, record_id=resolved_child_id, data={'hide': True}):
|
||||||
return mk_resp(data=True, response=response, status_message=f"Object with ID '{child_obj_id}' deleted successfully.")
|
return mk_resp(data=True, response=response, status_message=f"Object with ID '{child_obj_id}' hidden successfully.")
|
||||||
|
else:
|
||||||
|
return mk_resp(data=False, status_code=400, response=response, status_message="Failed to hide object. It may not support the 'hide' field.")
|
||||||
|
elif method == 'disable':
|
||||||
|
if sql_update(table_name=table_name_delete, record_id=resolved_child_id, data={'enable': False}):
|
||||||
|
return mk_resp(data=True, response=response, status_message=f"Object with ID '{child_obj_id}' disabled successfully.")
|
||||||
|
else:
|
||||||
|
return mk_resp(data=False, status_code=400, response=response, status_message="Failed to disable object. It may not support the 'enable' field.")
|
||||||
else:
|
else:
|
||||||
return mk_resp(data=False, status_code=400, response=response, status_message="Failed to delete object in database.")
|
# Default: hard delete
|
||||||
|
if sql_delete(table_name=table_name_delete, record_id=resolved_child_id):
|
||||||
|
return mk_resp(data=True, response=response, status_message=f"Object with ID '{child_obj_id}' deleted successfully.")
|
||||||
|
else:
|
||||||
|
return mk_resp(data=False, status_code=400, response=response, status_message="Failed to delete object in database.")
|
||||||
|
|
||||||
log.setLevel(logging.WARNING)
|
log.setLevel(logging.WARNING)
|
||||||
log.debug(locals())
|
log.debug(locals())
|
||||||
|
|||||||
Reference in New Issue
Block a user