From 295d9ade8ac7533184f8744e9b246c1592b2b68b Mon Sep 17 00:00:00 2001 From: dextmorgn Date: Thu, 18 Sep 2025 13:47:14 +0200 Subject: [PATCH] feat: permission --- .../1d0f26dbbef5_add_passive_delete_v2.py | 32 ++++ ...da7_add_investigation_roles_permissions.py | 35 ++++ ...816_add_investigation_roles_permissions.py | 63 +++++++ .../8d0e12b68d1e_fix_backpopulate_issue.py | 32 ++++ .../afdaf9aa539c_add_passive_delete.py | 32 ++++ ...2e5_add_investigation_roles_permissions.py | 33 ++++ .../alembic/versions/d39941278a91_init.py | 32 ++++ flowsint-api/app/api/routes/investigations.py | 78 +++++++-- flowsint-api/app/api/routes/sketches.py | 7 +- flowsint-api/app/security/permissions.py | 33 ++++ flowsint-api/poetry.lock | 159 +++++++----------- flowsint-api/pyproject.toml | 2 - flowsint-api/pytest.ini | 6 - .../src/components/analyses/analyses-list.tsx | 12 +- .../components/analyses/analysis-editor.tsx | 2 +- .../dashboard/investigation-cards.tsx | 62 +++++-- .../dashboard/investigation-sketches.tsx | 14 +- .../dashboard/recent-investigations.tsx | 14 +- .../src/components/flows/flow-list.tsx | 11 +- .../graphs/details-panel/details-panel.tsx | 7 +- .../graphs/details-panel/neighbors.tsx | 4 +- .../graphs/details-panel/relationships.tsx | 4 +- .../src/components/graphs/graph-error.tsx | 7 - .../src/components/graphs/new-sketch.tsx | 10 +- .../investigations/investigation-list.tsx | 39 +++-- .../investigations/new-investigation.tsx | 8 +- .../components/investigations/sketch-list.tsx | 10 +- .../src/components/shared/error-state.tsx | 54 ++++++ .../src/components/shared/skeleton-list.tsx | 4 +- .../routes/_auth.dashboard.flows.index.tsx | 10 +- .../src/routes/_auth.dashboard.vault.tsx | 24 ++- .../src/stores/graph-settings-store.ts | 10 +- .../src/flowsint_core/core/models.py | 79 ++++++--- flowsint-core/src/flowsint_core/core/types.py | 7 + .../src/flowsint_transforms/domain/to_ip.py | 7 +- 35 files changed, 731 insertions(+), 212 deletions(-) create mode 100644 flowsint-api/alembic/versions/1d0f26dbbef5_add_passive_delete_v2.py create mode 100644 flowsint-api/alembic/versions/6be831edfda7_add_investigation_roles_permissions.py create mode 100644 flowsint-api/alembic/versions/6e49acfb3816_add_investigation_roles_permissions.py create mode 100644 flowsint-api/alembic/versions/8d0e12b68d1e_fix_backpopulate_issue.py create mode 100644 flowsint-api/alembic/versions/afdaf9aa539c_add_passive_delete.py create mode 100644 flowsint-api/alembic/versions/c82bf6af92e5_add_investigation_roles_permissions.py create mode 100644 flowsint-api/alembic/versions/d39941278a91_init.py create mode 100644 flowsint-api/app/security/permissions.py delete mode 100644 flowsint-api/pytest.ini delete mode 100644 flowsint-app/src/renderer/src/components/graphs/graph-error.tsx create mode 100644 flowsint-app/src/renderer/src/components/shared/error-state.tsx diff --git a/flowsint-api/alembic/versions/1d0f26dbbef5_add_passive_delete_v2.py b/flowsint-api/alembic/versions/1d0f26dbbef5_add_passive_delete_v2.py new file mode 100644 index 0000000..14176df --- /dev/null +++ b/flowsint-api/alembic/versions/1d0f26dbbef5_add_passive_delete_v2.py @@ -0,0 +1,32 @@ +"""add passive_delete_v2 + +Revision ID: 1d0f26dbbef5 +Revises: afdaf9aa539c +Create Date: 2025-09-17 22:48:31.379106 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '1d0f26dbbef5' +down_revision: Union[str, None] = 'afdaf9aa539c' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/flowsint-api/alembic/versions/6be831edfda7_add_investigation_roles_permissions.py b/flowsint-api/alembic/versions/6be831edfda7_add_investigation_roles_permissions.py new file mode 100644 index 0000000..dcaef47 --- /dev/null +++ b/flowsint-api/alembic/versions/6be831edfda7_add_investigation_roles_permissions.py @@ -0,0 +1,35 @@ +"""add investigation roles permissions + + +Revision ID: 6be831edfda7 +Revises: c82bf6af92e5 +Create Date: 2025-09-17 22:02:46.159090 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = '6be831edfda7' +down_revision: Union[str, None] = 'c82bf6af92e5' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('investigation_user_roles', sa.Column('roles', postgresql.ARRAY(sa.Enum('OWNER', 'EDITOR', 'VIEWER', name='role_enum', create_constraint=True)), server_default='{}', nullable=False)) + op.drop_column('investigation_user_roles', 'role') + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('investigation_user_roles', sa.Column('role', postgresql.ENUM('OWNER', 'EDITOR', 'VIEWER', name='role_enum'), autoincrement=False, nullable=False)) + op.drop_column('investigation_user_roles', 'roles') + # ### end Alembic commands ### diff --git a/flowsint-api/alembic/versions/6e49acfb3816_add_investigation_roles_permissions.py b/flowsint-api/alembic/versions/6e49acfb3816_add_investigation_roles_permissions.py new file mode 100644 index 0000000..a88456d --- /dev/null +++ b/flowsint-api/alembic/versions/6e49acfb3816_add_investigation_roles_permissions.py @@ -0,0 +1,63 @@ +"""add investigation roles permissions + + +Revision ID: 6e49acfb3816 +Revises: 1098b7a5eabc +Create Date: 2025-09-17 21:46:14.314402 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = '6e49acfb3816' +down_revision: Union[str, None] = '1098b7a5eabc' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('investigation_user_roles', + sa.Column('id', sa.UUID(), nullable=False), + sa.Column('user_id', sa.UUID(), nullable=False), + sa.Column('investigation_id', sa.UUID(), nullable=False), + sa.Column('role', sa.Enum('OWNER', 'EDITOR', 'VIEWER', name='role_enum', create_constraint=True), nullable=False), + sa.ForeignKeyConstraint(['investigation_id'], ['investigations.id'], onupdate='CASCADE', ondelete='CASCADE'), + sa.ForeignKeyConstraint(['user_id'], ['profiles.id'], onupdate='CASCADE', ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user_id', 'investigation_id', name='uq_user_investigation') + ) + op.create_index('idx_investigation_roles_investigation_id', 'investigation_user_roles', ['investigation_id'], unique=False) + op.create_index('idx_investigation_roles_user_id', 'investigation_user_roles', ['user_id'], unique=False) + op.drop_index('idx_investigations_profiles_investigation_id', table_name='investigations_profiles') + op.drop_index('idx_investigations_profiles_profile_id', table_name='investigations_profiles') + op.drop_index('projects_profiles_unique_profile_project', table_name='investigations_profiles') + op.drop_table('investigations_profiles') + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('investigations_profiles', + sa.Column('id', sa.UUID(), autoincrement=False, nullable=False), + sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True), + sa.Column('investigation_id', sa.UUID(), autoincrement=False, nullable=True), + sa.Column('profile_id', sa.UUID(), autoincrement=False, nullable=True), + sa.Column('role', sa.VARCHAR(), server_default=sa.text("'member'::character varying"), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint(['investigation_id'], ['investigations.id'], name='investigations_profiles_investigation_id_fkey', onupdate='CASCADE', ondelete='CASCADE'), + sa.ForeignKeyConstraint(['profile_id'], ['profiles.id'], name='investigations_profiles_profile_id_fkey', onupdate='CASCADE', ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id', name='investigations_profiles_pkey') + ) + op.create_index('projects_profiles_unique_profile_project', 'investigations_profiles', ['profile_id', 'investigation_id'], unique=False) + op.create_index('idx_investigations_profiles_profile_id', 'investigations_profiles', ['profile_id'], unique=False) + op.create_index('idx_investigations_profiles_investigation_id', 'investigations_profiles', ['investigation_id'], unique=False) + op.drop_index('idx_investigation_roles_user_id', table_name='investigation_user_roles') + op.drop_index('idx_investigation_roles_investigation_id', table_name='investigation_user_roles') + op.drop_table('investigation_user_roles') + # ### end Alembic commands ### diff --git a/flowsint-api/alembic/versions/8d0e12b68d1e_fix_backpopulate_issue.py b/flowsint-api/alembic/versions/8d0e12b68d1e_fix_backpopulate_issue.py new file mode 100644 index 0000000..abe7868 --- /dev/null +++ b/flowsint-api/alembic/versions/8d0e12b68d1e_fix_backpopulate_issue.py @@ -0,0 +1,32 @@ +"""fix_backpopulate issue + +Revision ID: 8d0e12b68d1e +Revises: 1d0f26dbbef5 +Create Date: 2025-09-17 22:55:20.721587 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '8d0e12b68d1e' +down_revision: Union[str, None] = '1d0f26dbbef5' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/flowsint-api/alembic/versions/afdaf9aa539c_add_passive_delete.py b/flowsint-api/alembic/versions/afdaf9aa539c_add_passive_delete.py new file mode 100644 index 0000000..62b1a75 --- /dev/null +++ b/flowsint-api/alembic/versions/afdaf9aa539c_add_passive_delete.py @@ -0,0 +1,32 @@ +"""add passive_delete + +Revision ID: afdaf9aa539c +Revises: 6be831edfda7 +Create Date: 2025-09-17 22:46:21.139127 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'afdaf9aa539c' +down_revision: Union[str, None] = '6be831edfda7' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/flowsint-api/alembic/versions/c82bf6af92e5_add_investigation_roles_permissions.py b/flowsint-api/alembic/versions/c82bf6af92e5_add_investigation_roles_permissions.py new file mode 100644 index 0000000..d581385 --- /dev/null +++ b/flowsint-api/alembic/versions/c82bf6af92e5_add_investigation_roles_permissions.py @@ -0,0 +1,33 @@ +"""add investigation roles permissions + + +Revision ID: c82bf6af92e5 +Revises: d39941278a91 +Create Date: 2025-09-17 21:55:56.756716 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'c82bf6af92e5' +down_revision: Union[str, None] = 'd39941278a91' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/flowsint-api/alembic/versions/d39941278a91_init.py b/flowsint-api/alembic/versions/d39941278a91_init.py new file mode 100644 index 0000000..79f3f26 --- /dev/null +++ b/flowsint-api/alembic/versions/d39941278a91_init.py @@ -0,0 +1,32 @@ +"""init + +Revision ID: d39941278a91 +Revises: 6e49acfb3816 +Create Date: 2025-09-17 21:52:57.142634 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'd39941278a91' +down_revision: Union[str, None] = '6e49acfb3816' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/flowsint-api/app/api/routes/investigations.py b/flowsint-api/app/api/routes/investigations.py index 816a6c2..527f0c5 100644 --- a/flowsint-api/app/api/routes/investigations.py +++ b/flowsint-api/app/api/routes/investigations.py @@ -1,10 +1,19 @@ from uuid import UUID, uuid4 +from app.security.permissions import check_investigation_permission from fastapi import APIRouter, HTTPException, Depends, status from typing import List from datetime import datetime +from flowsint_core.core.types import Role +from sqlalchemy import or_ from sqlalchemy.orm import Session, selectinload from flowsint_core.core.postgre_db import get_db -from flowsint_core.core.models import Analysis, Investigation, Profile, Sketch +from flowsint_core.core.models import ( + Analysis, + Investigation, + InvestigationUserRole, + Profile, + Sketch, +) from app.api.deps import get_current_user from app.api.schemas.investigation import ( InvestigationRead, @@ -17,17 +26,52 @@ from flowsint_core.core.graph_db import neo4j_connection router = APIRouter() +def get_user_accessible_investigations( + user_id: str, db: Session, allowed_roles: list[Role] = None +) -> list[Investigation]: + """ + Returns all investigations accessible to user depending on its roles + """ + query = db.query(Investigation).join( + InvestigationUserRole, + InvestigationUserRole.investigation_id == Investigation.id, + ) + + query = query.filter(InvestigationUserRole.user_id == user_id) + + if allowed_roles: + # ARRAY(Role) contains any of allowed_roles + conditions = [InvestigationUserRole.roles.any(role) for role in allowed_roles] + # Inclut également le propriétaire de l’investigation + query = query.filter(or_(*conditions, Investigation.owner_id == user_id)) + + return ( + query.options( + selectinload(Investigation.sketches), + selectinload(Investigation.analyses), + selectinload(Investigation.owner), + ) + .distinct() + .all() + ) + + # Get the list of all investigations @router.get("", response_model=List[InvestigationRead]) def get_investigations( - db: Session = Depends(get_db), current_user: Profile = Depends(get_current_user) + db: Session = Depends(get_db), + current_user: Profile = Depends(get_current_user), ): - investigations = ( - db.query(Investigation) - .options(selectinload(Investigation.sketches), selectinload(Investigation.analyses), selectinload(Investigation.owner)) - .filter(Investigation.owner_id == current_user.id) - .all() + """ + Récupère toutes les investigations accessibles à l'utilisateur + selon ses rôles (OWNER, EDITOR, VIEWER). + """ + allowed_roles_for_read = [Role.OWNER, Role.EDITOR, Role.VIEWER] + + investigations = get_user_accessible_investigations( + user_id=current_user.id, db=db, allowed_roles=allowed_roles_for_read ) + return investigations @@ -46,12 +90,21 @@ def create_investigation( description=payload.description or payload.name, owner_id=current_user.id, status="active", - created_at=datetime.utcnow(), - last_updated_at=datetime.utcnow(), ) db.add(new_investigation) + + new_roles = InvestigationUserRole( + id=uuid4(), + user_id=current_user.id, + investigation_id=new_investigation.id, + roles=[Role.OWNER], + ) + db.add(new_roles) + db.commit() db.refresh(new_investigation) + db.refresh(new_roles) + return new_investigation @@ -62,9 +115,14 @@ def get_investigation_by_id( db: Session = Depends(get_db), current_user: Profile = Depends(get_current_user), ): + check_investigation_permission(current_user.id, investigation_id, actions=["read"], db=db) investigation = ( db.query(Investigation) - .options(selectinload(Investigation.sketches), selectinload(Investigation.analyses), selectinload(Investigation.owner)) + .options( + selectinload(Investigation.sketches), + selectinload(Investigation.analyses), + selectinload(Investigation.owner), + ) .filter(Investigation.id == investigation_id) .filter(Investigation.owner_id == current_user.id) .first() diff --git a/flowsint-api/app/api/routes/sketches.py b/flowsint-api/app/api/routes/sketches.py index 7273ce3..dce6dba 100644 --- a/flowsint-api/app/api/routes/sketches.py +++ b/flowsint-api/app/api/routes/sketches.py @@ -1,10 +1,10 @@ +from app.security.permissions import check_investigation_permission from fastapi import APIRouter, HTTPException, Depends, status from pydantic import BaseModel, Field -from typing import Dict, Any, Literal +from typing import Literal, List from fastapi import HTTPException from pydantic import BaseModel, Field from flowsint_core.utils import flatten -from typing import Dict, Any, List from sqlalchemy.orm import Session from app.api.schemas.sketch import SketchCreate, SketchRead, SketchUpdate from flowsint_core.core.models import Sketch, Profile @@ -63,6 +63,9 @@ def create_sketch( current_user: Profile = Depends(get_current_user), ): sketch_data = data.dict() + check_investigation_permission( + current_user.id, sketch_data.get("investigation_id"), actions=["create"], db=db + ) sketch_data["owner_id"] = current_user.id sketch = Sketch(**sketch_data) db.add(sketch) diff --git a/flowsint-api/app/security/permissions.py b/flowsint-api/app/security/permissions.py new file mode 100644 index 0000000..46b0a08 --- /dev/null +++ b/flowsint-api/app/security/permissions.py @@ -0,0 +1,33 @@ +from fastapi import HTTPException +from flowsint_core.core.models import InvestigationUserRole +from flowsint_core.core.types import Role + + +def can_user(roles: list[Role], actions: list[str]) -> bool: + """ + Vérifie si au moins un rôle de la liste autorise au moins une action de la liste. + """ + for role in roles: + for action in actions: + if role == Role.OWNER: + return True + if role == Role.EDITOR and action in ["read", "create", "update"]: + return True + if role == Role.VIEWER and action == "read": + return True + return False + + +from fastapi import HTTPException + + +def check_investigation_permission(user_id: str, investigation_id: str, actions: list[str], db): + role_entry = ( + db.query(InvestigationUserRole) + .filter_by(user_id=user_id, investigation_id=investigation_id) + .first() + ) + + if not role_entry or not can_user(role_entry.roles, actions): + raise HTTPException(status_code=403, detail="Forbidden") + return True diff --git a/flowsint-api/poetry.lock b/flowsint-api/poetry.lock index d844963..69b4650 100644 --- a/flowsint-api/poetry.lock +++ b/flowsint-api/poetry.lock @@ -1019,7 +1019,7 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {dev = "platform_system == \"Windows\" or sys_platform == \"win32\""} +markers = {dev = "platform_system == \"Windows\""} [[package]] name = "confection" @@ -1039,49 +1039,49 @@ srsly = ">=2.4.0,<3.0.0" [[package]] name = "cryptography" -version = "45.0.6" +version = "45.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.7" groups = ["main"] files = [ - {file = "cryptography-45.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402"}, - {file = "cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42"}, - {file = "cryptography-45.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05"}, - {file = "cryptography-45.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453"}, - {file = "cryptography-45.0.6-cp311-abi3-win32.whl", hash = "sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159"}, - {file = "cryptography-45.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec"}, - {file = "cryptography-45.0.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5"}, - {file = "cryptography-45.0.6-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016"}, - {file = "cryptography-45.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3"}, - {file = "cryptography-45.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9"}, - {file = "cryptography-45.0.6-cp37-abi3-win32.whl", hash = "sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02"}, - {file = "cryptography-45.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b"}, - {file = "cryptography-45.0.6-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:705bb7c7ecc3d79a50f236adda12ca331c8e7ecfbea51edd931ce5a7a7c4f012"}, - {file = "cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:826b46dae41a1155a0c0e66fafba43d0ede1dc16570b95e40c4d83bfcf0a451d"}, - {file = "cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cc4d66f5dc4dc37b89cfef1bd5044387f7a1f6f0abb490815628501909332d5d"}, - {file = "cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f68f833a9d445cc49f01097d95c83a850795921b3f7cc6488731e69bde3288da"}, - {file = "cryptography-45.0.6-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3b5bf5267e98661b9b888a9250d05b063220dfa917a8203744454573c7eb79db"}, - {file = "cryptography-45.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2384f2ab18d9be88a6e4f8972923405e2dbb8d3e16c6b43f15ca491d7831bd18"}, - {file = "cryptography-45.0.6-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fc022c1fa5acff6def2fc6d7819bbbd31ccddfe67d075331a65d9cfb28a20983"}, - {file = "cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3de77e4df42ac8d4e4d6cdb342d989803ad37707cf8f3fbf7b088c9cbdd46427"}, - {file = "cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:599c8d7df950aa68baa7e98f7b73f4f414c9f02d0e8104a30c0182a07732638b"}, - {file = "cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:31a2b9a10530a1cb04ffd6aa1cd4d3be9ed49f7d77a4dafe198f3b382f41545c"}, - {file = "cryptography-45.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:e5b3dda1b00fb41da3af4c5ef3f922a200e33ee5ba0f0bc9ecf0b0c173958385"}, - {file = "cryptography-45.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:629127cfdcdc6806dfe234734d7cb8ac54edaf572148274fa377a7d3405b0043"}, - {file = "cryptography-45.0.6.tar.gz", hash = "sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719"}, + {file = "cryptography-45.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee"}, + {file = "cryptography-45.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6"}, + {file = "cryptography-45.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339"}, + {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8"}, + {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf"}, + {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513"}, + {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3"}, + {file = "cryptography-45.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3"}, + {file = "cryptography-45.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6"}, + {file = "cryptography-45.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd"}, + {file = "cryptography-45.0.7-cp311-abi3-win32.whl", hash = "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8"}, + {file = "cryptography-45.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443"}, + {file = "cryptography-45.0.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2"}, + {file = "cryptography-45.0.7-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691"}, + {file = "cryptography-45.0.7-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59"}, + {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4"}, + {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3"}, + {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1"}, + {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27"}, + {file = "cryptography-45.0.7-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17"}, + {file = "cryptography-45.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b"}, + {file = "cryptography-45.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c"}, + {file = "cryptography-45.0.7-cp37-abi3-win32.whl", hash = "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5"}, + {file = "cryptography-45.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90"}, + {file = "cryptography-45.0.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:de58755d723e86175756f463f2f0bddd45cc36fbd62601228a3f8761c9f58252"}, + {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a20e442e917889d1a6b3c570c9e3fa2fdc398c20868abcea268ea33c024c4083"}, + {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:258e0dff86d1d891169b5af222d362468a9570e2532923088658aa866eb11130"}, + {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d97cf502abe2ab9eff8bd5e4aca274da8d06dd3ef08b759a8d6143f4ad65d4b4"}, + {file = "cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:c987dad82e8c65ebc985f5dae5e74a3beda9d0a2a4daf8a1115f3772b59e5141"}, + {file = "cryptography-45.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c13b1e3afd29a5b3b2656257f14669ca8fa8d7956d509926f0b130b600b50ab7"}, + {file = "cryptography-45.0.7-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a862753b36620af6fc54209264f92c716367f2f0ff4624952276a6bbd18cbde"}, + {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:06ce84dc14df0bf6ea84666f958e6080cdb6fe1231be2a51f3fc1267d9f3fb34"}, + {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d0c5c6bac22b177bf8da7435d9d27a6834ee130309749d162b26c3105c0795a9"}, + {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:2f641b64acc00811da98df63df7d59fd4706c0df449da71cb7ac39a0732b40ae"}, + {file = "cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:f5414a788ecc6ee6bc58560e85ca624258a55ca434884445440a810796ea0e0b"}, + {file = "cryptography-45.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f3d56f73595376f4244646dd5c5870c14c196949807be39e79e7bd9bac3da63"}, + {file = "cryptography-45.0.7.tar.gz", hash = "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971"}, ] [package.dependencies] @@ -1094,7 +1094,7 @@ nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_full_version >= \"3.8 pep8test = ["check-sdist ; python_full_version >= \"3.8.0\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==45.0.6)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test = ["certifi (>=2024)", "cryptography-vectors (==45.0.7)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -1198,25 +1198,26 @@ wmi = ["wmi (>=1.5.1)"] [[package]] name = "docker" -version = "6.1.3" +version = "7.1.0" description = "A Python library for the Docker Engine API." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "docker-6.1.3-py3-none-any.whl", hash = "sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9"}, - {file = "docker-6.1.3.tar.gz", hash = "sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20"}, + {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, + {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, ] [package.dependencies] -packaging = ">=14.0" pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} requests = ">=2.26.0" urllib3 = ">=1.26.0" -websocket-client = ">=0.32.0" [package.extras] +dev = ["coverage (==7.2.7)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.1.0)", "ruff (==0.1.8)"] +docs = ["myst-parser (==0.18.0)", "sphinx (==5.1.1)"] ssh = ["paramiko (>=2.4.3)"] +websockets = ["websocket-client (>=1.3.0)"] [[package]] name = "ecdsa" @@ -1372,7 +1373,8 @@ develop = true alembic = "1.13.0" asyncpg = "^0.29" celery = "^5.3" -docker = "^6.1" +cryptography = "^45.0.7" +docker = "^7.1.0" flowsint-transforms = {path = "../flowsint-transforms", develop = true} neo4j = "^5.0" networkx = "^2.6.3" @@ -1380,6 +1382,7 @@ passlib = {version = "^1.7", extras = ["bcrypt"]} phonenumbers = "^9.0.8" psycopg2-binary = "^2.9" pydantic = {version = "^2.11.7", extras = ["email"]} +pytest = "^8.4.2" python-dotenv = "^1.0" python-jose = {version = "^3.3", extras = ["cryptography"]} python-multipart = "^0.0.20" @@ -1404,7 +1407,6 @@ develop = true [package.dependencies] dnspython = "^2.4" -docker = "^6.1" flowsint-core = {path = "../flowsint-core", develop = true} flowsint-types = {path = "../flowsint-types", develop = true} hibpwned = "^1.3.9" @@ -1830,7 +1832,7 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -3266,7 +3268,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -3997,43 +3999,25 @@ files = [ [[package]] name = "pytest" -version = "7.4.4" +version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.21.2" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"}, - {file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"}, + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] -pytest = ">=7.0.0" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" [package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "python-bidi" @@ -5488,23 +5472,6 @@ files = [ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] -[[package]] -name = "websocket-client" -version = "1.8.0" -description = "WebSocket client for Python with low level API options" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, - {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, -] - -[package.extras] -docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] -optional = ["python-socks", "wsaccel"] -test = ["websockets"] - [[package]] name = "werkzeug" version = "3.1.3" @@ -5778,4 +5745,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.13" -content-hash = "713413e0c4f9fe453cc2fbb96fe82650caa2776f40af2fae82601326e4981774" +content-hash = "4a980251fbdec9cd0fde83177e6a8b96e21ce2af0b852b628095da088ce3af41" diff --git a/flowsint-api/pyproject.toml b/flowsint-api/pyproject.toml index a65f18d..dbfef7b 100644 --- a/flowsint-api/pyproject.toml +++ b/flowsint-api/pyproject.toml @@ -26,13 +26,11 @@ alembic = "1.13.0" passlib = {extras = ["bcrypt"], version = "^1.7"} sse-starlette = "^1.8" networkx = "^2.6.3" -pytest-asyncio = "^0.21" email-validator = "^2.2.0" mistralai = "^1.9.3" python-multipart = "^0.0.20" [tool.poetry.group.dev.dependencies] -pytest = "^7.4" black = "^23.0" isort = "^5.12" flake8 = "^6.0" diff --git a/flowsint-api/pytest.ini b/flowsint-api/pytest.ini deleted file mode 100644 index 9e0dbca..0000000 --- a/flowsint-api/pytest.ini +++ /dev/null @@ -1,6 +0,0 @@ -[tool:pytest] -asyncio_mode = auto -testpaths = tests -python_files = test_*.py -python_functions = test_* -addopts = -v -s \ No newline at end of file diff --git a/flowsint-app/src/renderer/src/components/analyses/analyses-list.tsx b/flowsint-app/src/renderer/src/components/analyses/analyses-list.tsx index dc73bd5..056ca6f 100644 --- a/flowsint-app/src/renderer/src/components/analyses/analyses-list.tsx +++ b/flowsint-app/src/renderer/src/components/analyses/analyses-list.tsx @@ -12,6 +12,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query" import { toast } from "sonner" import { formatDistanceToNow } from "date-fns" import { queryKeys } from "@/api/query-keys" +import ErrorState from "../shared/error-state" const AnalysisItem = ({ analysis, active }: { analysis: Analysis, active: boolean }) => { return ( @@ -45,7 +46,7 @@ const AnalysisList = () => { const navigate = useNavigate() // Fetch all analyses for this investigation - const { data: analyses, isLoading, error } = useQuery({ + const { data: analyses, isLoading, error, refetch } = useQuery({ queryKey: queryKeys.analyses.byInvestigation(investigationId || ""), queryFn: () => analysisService.getByInvestigationId(investigationId || ""), enabled: !!investigationId, @@ -82,7 +83,14 @@ const AnalysisList = () => { } }) - if (error) return
Error: {(error as Error).message}
+ if (error) return ( + refetch()} + /> + ) return (
diff --git a/flowsint-app/src/renderer/src/components/analyses/analysis-editor.tsx b/flowsint-app/src/renderer/src/components/analyses/analysis-editor.tsx index e50dd95..186455f 100644 --- a/flowsint-app/src/renderer/src/components/analyses/analysis-editor.tsx +++ b/flowsint-app/src/renderer/src/components/analyses/analysis-editor.tsx @@ -275,7 +275,7 @@ export const AnalysisEditor = ({
- +
{analyses.map((analysisItem) => ( +
+
+ refetch()} + /> + + ); + } + if (!investigations || investigations.length === 0) { return (
@@ -141,19 +167,27 @@ export function InvestigationCards() {
-
- -

No investigations yet

-

- Create your first investigation to start organizing your data. -

- - - -
+ + {/* Compact dashed card empty state */} + + +
+
+ +
+
No investigations yet
+
+ Create an investigation to group graphs and analyses by case or topic. +
+ + + +
+
+
); } diff --git a/flowsint-app/src/renderer/src/components/dashboard/investigation-sketches.tsx b/flowsint-app/src/renderer/src/components/dashboard/investigation-sketches.tsx index 97eaa6d..6466e04 100644 --- a/flowsint-app/src/renderer/src/components/dashboard/investigation-sketches.tsx +++ b/flowsint-app/src/renderer/src/components/dashboard/investigation-sketches.tsx @@ -8,6 +8,7 @@ import { Skeleton } from "@/components/ui/skeleton"; import { BarChart3, Plus, Clock } from "lucide-react"; import { formatDistanceToNow } from "date-fns"; import NewSketch from "@/components/graphs/new-sketch"; +import ErrorState from "@/components/shared/error-state"; interface InvestigationSketchesProps { investigationId: string; @@ -109,7 +110,7 @@ function InvestigationSketchesSkeleton() { } export function InvestigationSketches({ investigationId, title }: InvestigationSketchesProps) { - const { data: investigation, isLoading } = useQuery({ + const { data: investigation, isLoading, error, refetch } = useQuery({ queryKey: ["investigation", "sketches", investigationId], queryFn: () => investigationService.getById(investigationId), staleTime: 30000, // 30 seconds @@ -121,6 +122,17 @@ export function InvestigationSketches({ investigationId, title }: InvestigationS return ; } + if (error) { + return ( + refetch()} + /> + ); + } + const sketches = investigation?.sketches || []; if (sketches.length === 0) { diff --git a/flowsint-app/src/renderer/src/components/dashboard/recent-investigations.tsx b/flowsint-app/src/renderer/src/components/dashboard/recent-investigations.tsx index 5f5f375..535e18c 100644 --- a/flowsint-app/src/renderer/src/components/dashboard/recent-investigations.tsx +++ b/flowsint-app/src/renderer/src/components/dashboard/recent-investigations.tsx @@ -5,6 +5,7 @@ import { formatDistanceToNow } from 'date-fns'; import { investigationService } from '@/api/investigation-service'; import { useQuery } from '@tanstack/react-query'; import { Skeleton } from '@/components/ui/skeleton'; +import ErrorState from '@/components/shared/error-state'; function LoadingSkeleton() { @@ -31,7 +32,7 @@ function LoadingSkeleton() { } export function RecentInvestigations() { - const { data: investigations = [], isLoading } = useQuery({ + const { data: investigations = [], isLoading, error, refetch } = useQuery({ queryKey: ['recent-investigations'], queryFn: () => investigationService.get(), }); @@ -40,6 +41,17 @@ export function RecentInvestigations() { return ; } + if (error) { + return ( + refetch()} + /> + ); + } + if (!investigations?.length) { return (
diff --git a/flowsint-app/src/renderer/src/components/flows/flow-list.tsx b/flowsint-app/src/renderer/src/components/flows/flow-list.tsx index c7a1c72..328b94d 100644 --- a/flowsint-app/src/renderer/src/components/flows/flow-list.tsx +++ b/flowsint-app/src/renderer/src/components/flows/flow-list.tsx @@ -8,9 +8,10 @@ import { formatDistanceToNow } from "date-fns" import { Pencil, PlusIcon } from "lucide-react" import { Button } from "../ui/button" import NewFlow from "./new-flow" +import ErrorState from "../shared/error-state" const FlowsList = () => { - const { data: flows, isLoading } = useQuery({ + const { data: flows, isLoading, error, refetch } = useQuery({ queryKey: ["flows"], queryFn: () => flowService.get(), }) @@ -33,6 +34,14 @@ const FlowsList = () => { }, [flows, searchQuery]) if (isLoading) return
+ if (error) return ( + refetch()} + /> + ) return (
diff --git a/flowsint-app/src/renderer/src/components/graphs/details-panel/details-panel.tsx b/flowsint-app/src/renderer/src/components/graphs/details-panel/details-panel.tsx index c3b7318..d0055d6 100644 --- a/flowsint-app/src/renderer/src/components/graphs/details-panel/details-panel.tsx +++ b/flowsint-app/src/renderer/src/components/graphs/details-panel/details-panel.tsx @@ -11,9 +11,12 @@ import NeighborsGraph from "./neighbors" import Relationships from "./relationships" import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "../../ui/resizable" import { GraphNode } from "@/types" +import { useGraphStore } from "@/stores/graph-store" const DetailsPanel = memo(({ node }: { node: GraphNode | null }) => { const { id: sketchId } = useParams({ strict: false }) + const nodes = useGraphStore((s) => s.nodes) + node = nodes.find((n) => n.id === node?.id) || null if (!node) { return (
@@ -78,12 +81,12 @@ const DetailsPanel = memo(({ node }: { node: GraphNode | null }) => {
- +
- +
diff --git a/flowsint-app/src/renderer/src/components/graphs/details-panel/neighbors.tsx b/flowsint-app/src/renderer/src/components/graphs/details-panel/neighbors.tsx index d2b58cf..bd3920d 100644 --- a/flowsint-app/src/renderer/src/components/graphs/details-panel/neighbors.tsx +++ b/flowsint-app/src/renderer/src/components/graphs/details-panel/neighbors.tsx @@ -4,11 +4,11 @@ import ForceGraphViewer from "../graph-viewer"; import Loader from "@/components/loader"; import { memo, useRef } from "react"; -const NeighborsGraph = memo(({ sketchId, nodeId }: { sketchId: string, nodeId: string }) => { +const NeighborsGraph = memo(({ sketchId, nodeId, nodeLength }: { sketchId: string, nodeId: string, nodeLength: number }) => { const containerRef = useRef(null) const { data: neighborsData, isLoading } = useQuery({ - queryKey: ['neighbors', sketchId, nodeId], + queryKey: ['neighbors', sketchId, nodeId, nodeLength], queryFn: () => sketchService.getNodeNeighbors(sketchId, nodeId), }); return ( diff --git a/flowsint-app/src/renderer/src/components/graphs/details-panel/relationships.tsx b/flowsint-app/src/renderer/src/components/graphs/details-panel/relationships.tsx index ad0dd82..46deec9 100644 --- a/flowsint-app/src/renderer/src/components/graphs/details-panel/relationships.tsx +++ b/flowsint-app/src/renderer/src/components/graphs/details-panel/relationships.tsx @@ -26,9 +26,9 @@ const getInlineRelationships = (nodes: GraphNode[], edges: GraphEdge[]): Relatio return relationships } -const Relationships = memo(({ sketchId, nodeId }: { sketchId: string, nodeId: string }) => { +const Relationships = memo(({ sketchId, nodeId, nodeLength }: { sketchId: string, nodeId: string, nodeLength: number }) => { const { data: neighborsData, isLoading } = useQuery({ - queryKey: ['neighbors', sketchId, nodeId], + queryKey: ['neighbors', sketchId, nodeId, nodeLength], queryFn: () => sketchService.getNodeNeighbors(sketchId, nodeId), }); diff --git a/flowsint-app/src/renderer/src/components/graphs/graph-error.tsx b/flowsint-app/src/renderer/src/components/graphs/graph-error.tsx deleted file mode 100644 index 813c018..0000000 --- a/flowsint-app/src/renderer/src/components/graphs/graph-error.tsx +++ /dev/null @@ -1,7 +0,0 @@ -const GraphError = ({ error }: { error: Error }) => ( -
- Error: {error.message} -
-) - -export default GraphError \ No newline at end of file diff --git a/flowsint-app/src/renderer/src/components/graphs/new-sketch.tsx b/flowsint-app/src/renderer/src/components/graphs/new-sketch.tsx index 7840ec7..c513c8a 100644 --- a/flowsint-app/src/renderer/src/components/graphs/new-sketch.tsx +++ b/flowsint-app/src/renderer/src/components/graphs/new-sketch.tsx @@ -46,18 +46,17 @@ export default function NewSketch({ children }: NewSketchProps) { mutationFn: sketchService.create, onSuccess: (result) => { if (result.id) { - setOpen(false) toast.success("New sketch created.") router.navigate({ to: `/dashboard/investigations/${investigationId}/graph/${result.id}`, }) reset() // Invalidate sketches list and investigation sketches - queryClient.invalidateQueries({ + queryClient.invalidateQueries({ queryKey: queryKeys.sketches.list }) if (investigationId) { - queryClient.invalidateQueries({ + queryClient.invalidateQueries({ queryKey: queryKeys.investigations.sketches(investigationId) }) } @@ -66,8 +65,7 @@ export default function NewSketch({ children }: NewSketchProps) { } }, onError: (error) => { - toast.error("Unexpected error occurred.") - console.error(error) + toast.error(error.message) } }) @@ -137,7 +135,7 @@ export default function NewSketch({ children }: NewSketchProps) { return ( -
{children}
+ {children}
diff --git a/flowsint-app/src/renderer/src/components/investigations/investigation-list.tsx b/flowsint-app/src/renderer/src/components/investigations/investigation-list.tsx index ebf69a7..b06b57a 100644 --- a/flowsint-app/src/renderer/src/components/investigations/investigation-list.tsx +++ b/flowsint-app/src/renderer/src/components/investigations/investigation-list.tsx @@ -4,10 +4,10 @@ import { useQuery, useQueryClient } from "@tanstack/react-query" import type { Sketch } from "@/types/sketch" import NewInvestigation from "./new-investigation" import { Button } from "../ui/button" -import { MoreVertical, PlusIcon, Trash2, Waypoints, ChevronRight, ChevronDown, Folder, FileText, Search } from "lucide-react" +import { MoreVertical, PlusIcon, Trash2, Waypoints, ChevronRight, ChevronDown, Search } from "lucide-react" import { Input } from "../ui/input" import { cn } from "@/lib/utils" -import { Link, useParams } from "@tanstack/react-router" +import { Link } from "@tanstack/react-router" import { SkeletonList } from "../shared/skeleton-list" import { useConfirm } from "@/components/use-confirm-dialog" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu" @@ -16,6 +16,7 @@ import { sketchService } from "@/api/sketch-service" import { useState, useMemo } from "react" import { queryKeys } from "@/api/query-keys" import { useMutation } from "@tanstack/react-query" +import ErrorState from "../shared/error-state" // Tree node component for investigations and sketches const TreeNode = ({ @@ -161,6 +162,8 @@ export const SketchListItem = ({ sketch, investigationId, refetch }: { sketch: S queryClient.invalidateQueries({ queryKey: queryKeys.investigations.dashboard }) + // Ensure parent lists refresh + try { refetch(); } catch {} }, onError: (error) => { console.error("Error deleting sketch:", error) @@ -283,7 +286,14 @@ const InvestigationList = () => { } }; - if (error) return
Error: {(error as Error).message}
+ if (error) return ( + refetch()} + /> + ) return (
@@ -350,16 +360,19 @@ const InvestigationList = () => { })}
) : ( -
-

- No investigations yet -

- - - +
+
+
No investigations yet
+
+ Create an investigation to organize your graphs and notes by case, incident, or research topic. +
+ + + +
)}
diff --git a/flowsint-app/src/renderer/src/components/investigations/new-investigation.tsx b/flowsint-app/src/renderer/src/components/investigations/new-investigation.tsx index ceb36f5..cfe365c 100644 --- a/flowsint-app/src/renderer/src/components/investigations/new-investigation.tsx +++ b/flowsint-app/src/renderer/src/components/investigations/new-investigation.tsx @@ -52,11 +52,10 @@ export default function NewInvestigation({ children, noDropDown = false }: NewIn mutationFn: investigationService.create, onSuccess: (result) => { if (result.id) { - handleClose() toast.success("New investigation created.") router.navigate({ to: `/dashboard/investigations/${result.id}` }) // Invalidate investigations list - queryClient.invalidateQueries({ + queryClient.invalidateQueries({ queryKey: queryKeys.investigations.list }) } else { @@ -64,8 +63,7 @@ export default function NewInvestigation({ children, noDropDown = false }: NewIn } }, onError: (error) => { - console.error('Error creating investigation:', error) - toast.error("An unexpected error occurred") + toast.error(error.message) } }) @@ -147,7 +145,7 @@ export default function NewInvestigation({ children, noDropDown = false }: NewIn if (noDropDown) { return ( -
{children}
+ {children} New investigation diff --git a/flowsint-app/src/renderer/src/components/investigations/sketch-list.tsx b/flowsint-app/src/renderer/src/components/investigations/sketch-list.tsx index 08304ea..9152e57 100644 --- a/flowsint-app/src/renderer/src/components/investigations/sketch-list.tsx +++ b/flowsint-app/src/renderer/src/components/investigations/sketch-list.tsx @@ -10,6 +10,7 @@ import NewSketch from "../graphs/new-sketch" import { SketchListItem } from "./investigation-list" import { useState, useMemo } from "react" import { queryKeys } from "@/api/query-keys" +import ErrorState from "../shared/error-state" const SketchList = () => { const { investigationId } = useParams({ strict: false }) @@ -29,7 +30,14 @@ const SketchList = () => { ) }, [investigation?.sketches, searchQuery]) - if (error) return
Error: {(error as Error).message}
+ if (error) return ( + refetch()} + /> + ) return (
diff --git a/flowsint-app/src/renderer/src/components/shared/error-state.tsx b/flowsint-app/src/renderer/src/components/shared/error-state.tsx new file mode 100644 index 0000000..9d1f464 --- /dev/null +++ b/flowsint-app/src/renderer/src/components/shared/error-state.tsx @@ -0,0 +1,54 @@ +import { AlertTriangle, RefreshCw } from "lucide-react" +import { Button } from "../ui/button" +import { cn } from "@/lib/utils" + +type ErrorStateProps = { + title?: string + description?: string + error?: unknown + onRetry?: () => void + className?: string +} + +const getErrorMessage = (error: unknown) => { + if (!error) return null + if (error instanceof Error) return error.message + if (typeof error === "string") return error + try { + return JSON.stringify(error) + } catch { + return String(error) + } +} + +export function ErrorState({ title = "Something went wrong", description = "Please try again.", error, onRetry, className }: ErrorStateProps) { + const message = getErrorMessage(error) + return ( +
+
+
+ +
+
+

{title}

+ {description &&

{description}

} +
+ {onRetry && ( + + )} + {message && ( +

+ {message} +

+ )} +
+
+ ) +} + +export default ErrorState + + diff --git a/flowsint-app/src/renderer/src/components/shared/skeleton-list.tsx b/flowsint-app/src/renderer/src/components/shared/skeleton-list.tsx index de79c93..b8aced4 100644 --- a/flowsint-app/src/renderer/src/components/shared/skeleton-list.tsx +++ b/flowsint-app/src/renderer/src/components/shared/skeleton-list.tsx @@ -28,10 +28,10 @@ export function SkeletonList({ rowCount, className, mode = 'list' }: SkeletonLis
-
+ {/*
-
+
*/}
diff --git a/flowsint-app/src/renderer/src/routes/_auth.dashboard.flows.index.tsx b/flowsint-app/src/renderer/src/routes/_auth.dashboard.flows.index.tsx index 98703c0..abf3682 100644 --- a/flowsint-app/src/renderer/src/routes/_auth.dashboard.flows.index.tsx +++ b/flowsint-app/src/renderer/src/routes/_auth.dashboard.flows.index.tsx @@ -10,6 +10,7 @@ import { Badge } from '@/components/ui/badge' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import NewFlow from '@/components/flows/new-flow' import { flowService } from '@/api/flow-service' +import ErrorState from '@/components/shared/error-state' interface Flow { id: string @@ -27,7 +28,7 @@ export const Route = createFileRoute('/_auth/dashboard/flows/')({ function FlowPage() { const navigate = useNavigate() - const { data: flows, isLoading } = useQuery({ + const { data: flows, isLoading, error, refetch } = useQuery({ queryKey: ["flow"], queryFn: () => flowService.get(), }) @@ -72,6 +73,13 @@ function FlowPage() {
+ ) : error ? ( + refetch()} + /> ) : !flows?.length ? (
diff --git a/flowsint-app/src/renderer/src/routes/_auth.dashboard.vault.tsx b/flowsint-app/src/renderer/src/routes/_auth.dashboard.vault.tsx index d4a3679..ca5a6c7 100644 --- a/flowsint-app/src/renderer/src/routes/_auth.dashboard.vault.tsx +++ b/flowsint-app/src/renderer/src/routes/_auth.dashboard.vault.tsx @@ -15,6 +15,7 @@ import { useConfirm } from '../components/use-confirm-dialog' import Loader from '@/components/loader' import { type Key as KeyType } from '@/types/key' import { queryKeys } from '@/api/query-keys' +import ErrorState from '@/components/shared/error-state' export const Route = createFileRoute('/_auth/dashboard/vault')({ component: VaultPage, }) @@ -27,7 +28,7 @@ function VaultPage() { const { confirm } = useConfirm() // Fetch keys - const { data: keys = [], isLoading: keysLoading } = useQuery({ + const { data: keys = [], isLoading: keysLoading, error: keysError, refetch } = useQuery({ queryKey: queryKeys.keys.list, queryFn: () => KeyService.get(), }) @@ -96,6 +97,27 @@ function VaultPage() { ) } + if (keysError) { + return ( +
+
+
+

Vault

+

Manage your API keys for third-party services.

+
+
+ refetch()} + /> +
+
+
+ ) + } + return (
diff --git a/flowsint-app/src/renderer/src/stores/graph-settings-store.ts b/flowsint-app/src/renderer/src/stores/graph-settings-store.ts index 304a855..e50243c 100644 --- a/flowsint-app/src/renderer/src/stores/graph-settings-store.ts +++ b/flowsint-app/src/renderer/src/stores/graph-settings-store.ts @@ -12,11 +12,11 @@ const DEFAULT_SETTINGS = { value: true, description: "Display Flo, your AI assistant." }, - showMinimap: { - type: "boolean", - value: true, - description: "Display the MiniMap on the graph panel." - }, + // showMinimap: { + // type: "boolean", + // value: true, + // description: "Display the MiniMap on the graph panel." + // }, graphViewerThreshold: { type: "select", value: 800, diff --git a/flowsint-core/src/flowsint_core/core/models.py b/flowsint-core/src/flowsint_core/core/models.py index 84baf8e..fa33bb7 100644 --- a/flowsint-core/src/flowsint_core/core/models.py +++ b/flowsint-core/src/flowsint_core/core/models.py @@ -1,5 +1,6 @@ import uuid from datetime import datetime +from flowsint_core.core.types import Role from sqlalchemy import ( String, Text, @@ -11,6 +12,7 @@ from sqlalchemy import ( Column, Enum as SQLEnum, LargeBinary, + UniqueConstraint, ) from sqlalchemy.dialects.postgresql import UUID as PGUUID, ARRAY, JSONB from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship @@ -52,40 +54,18 @@ class Investigation(Base): analyses = relationship("Analysis", back_populates="investigation") chats = relationship("Chat", back_populates="investigation") owner = relationship("Profile", foreign_keys=[owner_id]) + user_roles = relationship( + "InvestigationUserRole", + back_populates="investigation", + passive_deletes=True, + cascade="save-update, merge", + ) __table_args__ = ( Index("idx_investigations_id", "id"), Index("idx_investigations_owner_id", "owner_id"), ) -class InvestigationsProfiles(Base): - __tablename__ = "investigations_profiles" - - id: Mapped[uuid.UUID] = mapped_column(primary_key=True) - created_at = mapped_column(DateTime(timezone=True), server_default=func.now()) - investigation_id = mapped_column( - PGUUID(as_uuid=True), - ForeignKey("investigations.id", onupdate="CASCADE", ondelete="CASCADE"), - ) - profile_id = mapped_column( - PGUUID(as_uuid=True), - ForeignKey("profiles.id", onupdate="CASCADE", ondelete="CASCADE"), - ) - role = mapped_column(String, server_default="member") - - __table_args__ = ( - Index("idx_investigations_profiles_investigation_id", "investigation_id"), - Index("idx_investigations_profiles_profile_id", "profile_id"), - # Uniqueness constraint - Index( - "projects_profiles_unique_profile_project", - "profile_id", - "investigation_id", - unique=True, - ), - ) - - class Log(Base): __tablename__ = "logs" @@ -114,6 +94,7 @@ class Profile(Base): email: Mapped[str] = mapped_column(String, unique=True, nullable=False) hashed_password: Mapped[str] = mapped_column(String, nullable=False) is_active: Mapped[bool] = mapped_column(default=True) + investigation_roles = relationship("InvestigationUserRole", back_populates="user") class Scan(Base): @@ -314,3 +295,45 @@ class Key(Base): Index("idx_keys_owner_id", "owner_id"), Index("idx_keys_service", "name"), ) + + +class InvestigationUserRole(Base): + __tablename__ = "investigation_user_roles" + + id: Mapped[uuid.UUID] = mapped_column( + PGUUID(as_uuid=True), + primary_key=True, + default=uuid.uuid4, + ) + + user_id: Mapped[uuid.UUID] = mapped_column( + PGUUID(as_uuid=True), + ForeignKey("profiles.id", onupdate="CASCADE", ondelete="CASCADE"), + nullable=False, + ) + + investigation_id: Mapped[uuid.UUID] = mapped_column( + PGUUID(as_uuid=True), + ForeignKey("investigations.id", onupdate="CASCADE", ondelete="CASCADE"), + nullable=False, + ) + + roles: Mapped[list[Role]] = mapped_column( + ARRAY(SQLEnum(Role, name="role_enum", create_constraint=True)), + nullable=False, + server_default="{}", + ) + + # Relations ORM + user = relationship( + "Profile", back_populates="investigation_roles", passive_deletes=True + ) + investigation = relationship( + "Investigation", back_populates="user_roles", passive_deletes=True + ) + + __table_args__ = ( + UniqueConstraint("user_id", "investigation_id", name="uq_user_investigation"), + Index("idx_investigation_roles_user_id", "user_id"), + Index("idx_investigation_roles_investigation_id", "investigation_id"), + ) diff --git a/flowsint-core/src/flowsint_core/core/types.py b/flowsint-core/src/flowsint_core/core/types.py index 1f2d0ed..d22b384 100644 --- a/flowsint-core/src/flowsint_core/core/types.py +++ b/flowsint-core/src/flowsint_core/core/types.py @@ -1,3 +1,4 @@ +import enum from pydantic import BaseModel, Field from typing import Dict, Any from datetime import datetime @@ -90,3 +91,9 @@ class FlowBranch(BaseModel): steps: List[FlowStep] = Field( ..., description="List of steps in this branch", title="Steps" ) + + +class Role(str, enum.Enum): + OWNER = "owner" + EDITOR = "editor" + VIEWER = "viewer" diff --git a/flowsint-transforms/src/flowsint_transforms/domain/to_ip.py b/flowsint-transforms/src/flowsint_transforms/domain/to_ip.py index d5a25c1..fbffd73 100644 --- a/flowsint-transforms/src/flowsint_transforms/domain/to_ip.py +++ b/flowsint-transforms/src/flowsint_transforms/domain/to_ip.py @@ -1,5 +1,6 @@ import socket from typing import List, Union +from flowsint_core.core.logger import Logger from flowsint_core.core.transform_base import Transform from flowsint_types.domain import Domain from flowsint_types.ip import Ip @@ -283,7 +284,11 @@ class ResolveTransform(Transform): ip = socket.gethostbyname(d.domain) results.append(Ip(address=ip)) except Exception as e: - print(f"Error resolving {d.domain}: {e}") + Logger.info( + self.sketch_id, + {"message": f"Error resolving {d.domain}: {e}"}, + ) + continue return results def postprocess(self, results: OutputType, original_input: InputType) -> OutputType: