diff --git a/AUTHORS.md b/AUTHORS.md index 1402897..38751a1 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -4,6 +4,7 @@ The list of contributors in alphabetical order: - [Audrius Mecionis](https://orcid.org/0000-0002-3759-1663) - [Camila Diaz](https://orcid.org/0000-0001-5543-797X) +- [Daan Rosendal](https://orcid.org/0000-0002-3447-9000) - [Diego Rodriguez](https://orcid.org/0000-0003-0649-2002) - [Dinos Kousidis](https://orcid.org/0000-0002-4914-4289) - [Giuseppe Steduto](https://orcid.org/0009-0002-1258-8553) diff --git a/reana_db/alembic/versions/20240314_1312_2e82f33ee37d_workflow_sharing.py b/reana_db/alembic/versions/20240314_1312_2e82f33ee37d_workflow_sharing.py new file mode 100644 index 0000000..6a66347 --- /dev/null +++ b/reana_db/alembic/versions/20240314_1312_2e82f33ee37d_workflow_sharing.py @@ -0,0 +1,51 @@ +"""Workflow sharing. + +Revision ID: 2e82f33ee37d +Revises: eb5309f3d8ee +Create Date: 2024-03-14 13:12:01.029714 + +""" + +import sqlalchemy_utils +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "2e82f33ee37d" +down_revision = "eb5309f3d8ee" +branch_labels = None +depends_on = None + + +def upgrade(): + """Upgrade to 2e82f33ee37d revision.""" + op.create_table( + "user_workflow", + sa.Column( + "workflow_id", sqlalchemy_utils.types.uuid.UUIDType(), nullable=False + ), + sa.Column("user_id", sqlalchemy_utils.types.uuid.UUIDType(), nullable=False), + sa.Column("access_type", sa.Enum("read", name="accesstype"), nullable=False), + sa.Column("message", sa.String(length=5000), nullable=True), + sa.Column("valid_until", sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint( + ["user_id"], + ["__reana.user_.id_"], + name=op.f("fk_user_workflow_user_id_user_"), + ), + sa.ForeignKeyConstraint( + ["workflow_id"], + ["__reana.workflow.id_"], + name=op.f("fk_user_workflow_workflow_id_workflow"), + ), + sa.PrimaryKeyConstraint( + "workflow_id", "user_id", name=op.f("pk_user_workflow") + ), + schema="__reana", + ) + + +def downgrade(): + """Downgrade to eb5309f3d8ee revision.""" + op.drop_table("user_workflow", schema="__reana") + sa.Enum(name="accesstype").drop(op.get_bind()) diff --git a/reana_db/models.py b/reana_db/models.py index 36609fc..61eb277 100644 --- a/reana_db/models.py +++ b/reana_db/models.py @@ -11,8 +11,8 @@ from __future__ import absolute_import import enum -import math import logging +import math import uuid from datetime import datetime from functools import reduce @@ -26,6 +26,21 @@ ) from reana_commons.errors import REANAValidationError from reana_commons.utils import get_disk_usage +from reana_db.config import ( + DB_SECRET_KEY, + DEFAULT_QUOTA_LIMITS, + DEFAULT_QUOTA_RESOURCES, + WORKFLOW_TERMINATION_QUOTA_UPDATE_POLICY, +) +from reana_db.utils import ( + build_workspace_path, + store_workflow_disk_quota, + split_run_number, + update_users_cpu_quota, + update_users_disk_quota, + update_workflow_cpu_quota, + update_workspace_retention_rules, +) from sqlalchemy import ( BigInteger, Boolean, @@ -43,6 +58,7 @@ func, or_, ) +from sqlalchemy.dialects.postgresql import ARRAY from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import relationship @@ -51,22 +67,6 @@ from sqlalchemy_utils.types.encrypted.encrypted_type import AesEngine from sqlalchemy.dialects.postgresql import ARRAY -from reana_db.config import ( - DB_SECRET_KEY, - DEFAULT_QUOTA_LIMITS, - DEFAULT_QUOTA_RESOURCES, - WORKFLOW_TERMINATION_QUOTA_UPDATE_POLICY, -) -from reana_db.utils import ( - build_workspace_path, - store_workflow_disk_quota, - split_run_number, - update_users_cpu_quota, - update_users_disk_quota, - update_workflow_cpu_quota, - update_workspace_retention_rules, -) - convention = { "ix": "ix_%(column_0_label)s", @@ -159,6 +159,14 @@ class User(Base, Timestamp, QuotaBase): username = Column(String(length=255)) tokens = relationship("UserToken", backref="user_", lazy="dynamic") workflows = relationship("Workflow", backref="owner", lazy="dynamic") + workflows_shared_with_me = relationship( + "Workflow", + secondary="__reana.user_workflow", + backref="users_it_is_shared_with", + lazy="dynamic", + viewonly=True, + sync_backref=False, + ) audit_logs = relationship("AuditLog", backref="user_") def __init__(self, access_token=None, **kwargs): @@ -1182,3 +1190,26 @@ class QuotaHealth(enum.Enum): healthy = 0 warning = 1 critical = 2 + + +class AccessType(enum.Enum): + """Enumeration of access types.""" + + read = 0 + + +class UserWorkflow(Base): + """UserWorkflow table.""" + + __tablename__ = "user_workflow" + __table_args__ = {"schema": "__reana"} + + workflow_id = Column(UUIDType, ForeignKey("__reana.workflow.id_"), primary_key=True) + user_id = Column(UUIDType, ForeignKey("__reana.user_.id_"), primary_key=True) + access_type = Column(Enum(AccessType), default=AccessType.read, nullable=False) + message = Column(String(5000)) + valid_until = Column(DateTime) + + def __repr__(self): + """User Workflow string representation.""" + return "".format(self.workflow_id, self.user_id)