diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/d84edab53761_add_restriction_ondelete.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/d84edab53761_add_restriction_ondelete.py new file mode 100644 index 00000000000..f8f79b258a8 --- /dev/null +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/d84edab53761_add_restriction_ondelete.py @@ -0,0 +1,62 @@ +"""add restriction ondelete + +Revision ID: d84edab53761 +Revises: 163b11424cb1 +Create Date: 2025-02-25 09:18:14.541874+00:00 + +""" +from alembic import op + +# revision identifiers, used by Alembic. +revision = "d84edab53761" +down_revision = "163b11424cb1" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_unique_constraint( + "uq_licensed_item_to_resource_resource_id", + "licensed_item_to_resource", + ["licensed_resource_id"], + ) + op.drop_constraint( + "fk_rut_pricing_plan_to_service_key_and_version", + "resource_tracker_pricing_plan_to_service", + type_="foreignkey", + ) + op.create_foreign_key( + "fk_rut_pricing_plan_to_service_key_and_version", + "resource_tracker_pricing_plan_to_service", + "services_meta_data", + ["service_key", "service_version"], + ["key", "version"], + onupdate="CASCADE", + ondelete="RESTRICT", + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint( + "fk_rut_pricing_plan_to_service_key_and_version", + "resource_tracker_pricing_plan_to_service", + type_="foreignkey", + ) + op.create_foreign_key( + "fk_rut_pricing_plan_to_service_key_and_version", + "resource_tracker_pricing_plan_to_service", + "services_meta_data", + ["service_key", "service_version"], + ["key", "version"], + onupdate="CASCADE", + ondelete="CASCADE", + ) + op.drop_constraint( + "uq_licensed_item_to_resource_resource_id", + "licensed_item_to_resource", + type_="unique", + ) + # ### end Alembic commands ### diff --git a/packages/postgres-database/src/simcore_postgres_database/models/licensed_item_to_resource.py b/packages/postgres-database/src/simcore_postgres_database/models/licensed_item_to_resource.py index 28eb8ff6955..f2f5fb1b6bc 100644 --- a/packages/postgres-database/src/simcore_postgres_database/models/licensed_item_to_resource.py +++ b/packages/postgres-database/src/simcore_postgres_database/models/licensed_item_to_resource.py @@ -31,4 +31,13 @@ ), column_created_datetime(timezone=True), column_modified_datetime(timezone=True), + ######### + # NOTE: Currently, there is a constraint that a resource item ID cannot be in multiple licensed items. + # The reason is that the license key and license version coming from the internal license server are part of the licensed resource domain. + # Sim4Life performs a mapping on their side, where the license key and version are mapped to a licensed item. + # If this constraint is broken, the mapping logic in Sim4Life might break. + sa.UniqueConstraint( + "licensed_resource_id", + name="uq_licensed_item_to_resource_resource_id", + ), ) diff --git a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_pricing_plan_to_service.py b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_pricing_plan_to_service.py index 5fd77bbbaad..b802b6724c4 100644 --- a/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_pricing_plan_to_service.py +++ b/packages/postgres-database/src/simcore_postgres_database/models/resource_tracker_pricing_plan_to_service.py @@ -50,6 +50,6 @@ ["services_meta_data.key", "services_meta_data.version"], name="fk_rut_pricing_plan_to_service_key_and_version", onupdate=RefActions.CASCADE, - ondelete=RefActions.CASCADE, + ondelete=RefActions.RESTRICT, ), ) diff --git a/services/web/server/src/simcore_service_webserver/trash/_rest.py b/services/web/server/src/simcore_service_webserver/trash/_rest.py index ac61d4c735f..ed68704639a 100644 --- a/services/web/server/src/simcore_service_webserver/trash/_rest.py +++ b/services/web/server/src/simcore_service_webserver/trash/_rest.py @@ -1,3 +1,4 @@ +import asyncio import logging from aiohttp import web @@ -20,10 +21,6 @@ _logger = logging.getLogger(__name__) -# -# EXCEPTIONS HANDLING -# - _TO_HTTP_ERROR_MAP: ExceptionToHttpErrorMap = { ProjectRunningConflictError: HttpErrorInfo( @@ -42,10 +39,6 @@ ) -# -# ROUTES -# - routes = web.RouteTableDef() @@ -57,12 +50,24 @@ async def empty_trash(request: web.Request): user_id = get_user_id(request) product_name = get_product_name(request) - fire_and_forget_task( - _service.safe_empty_trash( + is_fired = asyncio.Event() + + async def _empty_trash(): + is_fired.set() + await _service.safe_empty_trash( request.app, product_name=product_name, user_id=user_id - ), + ) + + fire_and_forget_task( + _empty_trash(), task_suffix_name="rest.empty_trash", fire_and_forget_tasks_collection=request.app[APP_FIRE_AND_FORGET_TASKS_KEY], ) + # NOTE: Ensures `fire_and_forget_task` is triggered; otherwise, + # when the front-end requests the trash item list, + # it may still display items, misleading the user into + # thinking the `empty trash` operation failed. + await is_fired.wait() + return web.json_response(status=status.HTTP_204_NO_CONTENT)