Skip to content

Commit 3dfa00f

Browse files
committed
41526 feat(storage): migrate file service to path-based routing and harden API
1 parent 316d0b8 commit 3dfa00f

57 files changed

Lines changed: 1085 additions & 472 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/src/accounts/tests/views/test_user/test_update.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ def test_put__all_fields__ok(api_client, mocker):
178178
'photo': user.photo,
179179
'is_admin': user.is_admin,
180180
'is_account_owner': user.is_account_owner,
181+
'manager_id': user.manager_id,
182+
'subordinates_ids': [],
181183
},
182184
)
183185

backend/src/processes/management/commands/migrate_file_attachment_to_attachment.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -70,24 +70,33 @@ def handle(self, *args, **options):
7070
access_type = AccessType.RESTRICTED
7171

7272
source_type = SourceType.ACCOUNT
73-
7473
task_id = None
75-
if attr.event_id and attr.event.task_id:
76-
task_id = attr.event.task_id
77-
elif attr.output_id and attr.output.task_id:
78-
task_id = attr.output.task_id
79-
8074
template_id = None
75+
8176
if attr.workflow_id:
77+
source_type = SourceType.WORKFLOW
8278
template_id = attr.workflow.template_id
83-
elif (
84-
attr.event_id and
85-
attr.event.task_id and
86-
attr.event.task.workflow_id
87-
):
88-
template_id = attr.event.task.workflow.template_id
89-
elif attr.output_id and attr.output.workflow_id:
90-
template_id = attr.output.workflow.template_id
79+
elif attr.event_id:
80+
if attr.event.task_id:
81+
source_type = SourceType.TASK
82+
task_id = attr.event.task_id
83+
template_id = (
84+
attr.event.task.workflow.template_id
85+
if attr.event.task.workflow_id
86+
else None
87+
)
88+
else:
89+
source_type = SourceType.WORKFLOW
90+
template_id = (
91+
attr.event.workflow.template_id
92+
if attr.event.workflow_id
93+
else None
94+
)
95+
elif attr.output_id:
96+
source_type = SourceType.TASK
97+
task_id = attr.output.task_id
98+
if attr.output.workflow_id:
99+
template_id = attr.output.workflow.template_id
91100

92101
if not dry_run:
93102
with transaction.atomic():

backend/src/processes/migrations/0245_auto_20251010_0046.py renamed to backend/src/processes/migrations/0253_add_fileattachment_fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
class Migration(migrations.Migration):
77

88
dependencies = [
9-
('processes', '0244_add_reminder'),
9+
('processes', '0252_add_manager_performer_type'),
1010
]
1111

1212
operations = [

backend/src/processes/serializers/workflows/field.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class Meta:
4848
'user_id',
4949
'group_id',
5050
'selections',
51+
'attachments',
5152
)
5253

5354
selections = serializers.SerializerMethodField()

backend/src/processes/services/tasks/field.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -478,8 +478,7 @@ def _sync_storage_attachments(
478478
continue
479479
if created:
480480
service = AttachmentService(user=self.user)
481-
service.instance = attachment
482-
service._create_related()
481+
service.assign_permissions(attachment)
483482

484483
def partial_update(
485484
self,
@@ -515,8 +514,7 @@ def partial_update(
515514
)
516515
if self.instance.type == FieldType.FILE:
517516
self._link_new_attachments(raw_value)
518-
elif self.instance.type in FieldType.TYPES_WITH_SELECTIONS:
519-
self._update_selections(raw_value)
517+
520518
elif self.instance.type in {
521519
FieldType.STRING,
522520
FieldType.TEXT,

backend/src/processes/tests/test_services/test_condition_check/test_service.py

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
)
2828
from src.storage.models import Attachment
2929
from src.storage.enums import SourceType, AccessType
30-
from src.processes.models.workflows.attachment import FileAttachment
3130

3231
UserModel = get_user_model()
3332
pytestmark = pytest.mark.django_db
@@ -1577,11 +1576,11 @@ def test_check__file__exist__has_attachment__ok():
15771576
workflow=workflow,
15781577
account=owner.account,
15791578
)
1580-
FileAttachment.objects.create(
1581-
name='john.cena',
1582-
url='https://john.cena/john.cena',
1583-
size=1488,
1584-
account_id=owner.account_id,
1579+
Attachment.objects.create(
1580+
file_id='12345678-1234-5678-1234-567812345678',
1581+
source_type=SourceType.TASK,
1582+
access_type=AccessType.ACCOUNT,
1583+
account=owner.account,
15851584
output=field,
15861585
)
15871586
condition = Condition.objects.create(
@@ -1626,11 +1625,11 @@ def test_check__file__exist__no_attachment__fail():
16261625
workflow=workflow,
16271626
account=owner.account,
16281627
)
1629-
FileAttachment.objects.create(
1630-
name='john.cena',
1631-
url='https://john.cena/john.cena',
1632-
size=1488,
1633-
account_id=owner.account_id,
1628+
Attachment.objects.create(
1629+
file_id='12345678-1234-5678-1234-567812345678',
1630+
source_type=SourceType.TASK,
1631+
access_type=AccessType.ACCOUNT,
1632+
account=owner.account,
16341633
output=None,
16351634
)
16361635
condition = Condition.objects.create(
@@ -1675,11 +1674,11 @@ def test_check__file__not_exist__no_attachment__ok():
16751674
workflow=workflow,
16761675
account=owner.account,
16771676
)
1678-
FileAttachment.objects.create(
1679-
name='john.cena',
1680-
url='https://john.cena/john.cena',
1681-
size=1488,
1682-
account_id=owner.account_id,
1677+
Attachment.objects.create(
1678+
file_id='12345678-1234-5678-1234-567812345678',
1679+
source_type=SourceType.TASK,
1680+
access_type=AccessType.ACCOUNT,
1681+
account=owner.account,
16831682
output=None,
16841683
)
16851684
condition = Condition.objects.create(
@@ -1724,11 +1723,11 @@ def test_check__file__not_exist__has_attachment__fail():
17241723
workflow=workflow,
17251724
account=owner.account,
17261725
)
1727-
FileAttachment.objects.create(
1728-
name='john.cena',
1729-
url='https://john.cena/john.cena',
1730-
size=1488,
1731-
account_id=owner.account_id,
1726+
Attachment.objects.create(
1727+
file_id='12345678-1234-5678-1234-567812345678',
1728+
source_type=SourceType.TASK,
1729+
access_type=AccessType.ACCOUNT,
1730+
account=owner.account,
17321731
output=field,
17331732
)
17341733
condition = Condition.objects.create(

backend/src/processes/tests/test_services/test_tasks/test_taskfield.py

Lines changed: 45 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
)
3636
from src.storage.models import Attachment
3737
from src.storage.enums import SourceType, AccessType
38-
from src.processes.models.workflows.attachment import FileAttachment
3938

4039
UserModel = get_user_model()
4140
pytestmark = pytest.mark.django_db
@@ -672,11 +671,11 @@ def test__link_new_attachments__ids_none__skip():
672671
workflow=workflow,
673672
account=account,
674673
)
675-
attachment = FileAttachment.objects.create(
676-
name='test.jpg',
677-
url='https://test.test/test.jpg',
678-
size=1234,
679-
account_id=account.id,
674+
attachment = Attachment.objects.create(
675+
file_id='test.jpg',
676+
account=account,
677+
source_type=SourceType.TASK,
678+
access_type=AccessType.RESTRICTED,
680679
)
681680
service = TaskFieldService(instance=task_field, user=user)
682681

@@ -705,33 +704,31 @@ def test__remove_unused_attachments__value_some_deleted__ok():
705704
workflow=workflow,
706705
account=account,
707706
)
708-
attachment_1 = FileAttachment.objects.create(
709-
name='keep.jpg',
710-
url='https://test.test/keep.jpg',
711-
size=100,
712-
account_id=account.id,
707+
attachment_1 = Attachment.objects.create(
708+
file_id='keep.jpg',
709+
account=account,
710+
source_type=SourceType.TASK,
711+
access_type=AccessType.RESTRICTED,
713712
output=task_field,
714713
)
715-
attachment_2 = FileAttachment.objects.create(
716-
name='delete.jpg',
717-
url='https://test.test/delete.jpg',
718-
size=200,
719-
account_id=account.id,
714+
attachment_2 = Attachment.objects.create(
715+
file_id='delete.jpg',
716+
account=account,
717+
source_type=SourceType.TASK,
718+
access_type=AccessType.RESTRICTED,
720719
output=task_field,
721720
)
722721
service = TaskFieldService(instance=task_field, user=user)
723-
value = attachment_1.url
724-
attachment_ids = [str(attachment_1.id)]
722+
markdown_values = [attachment_1.file_id]
725723

726724
# act
727725
service._remove_unused_attachments(
728-
value=value,
729-
attachment_ids=attachment_ids,
726+
markdown_values=markdown_values,
730727
)
731728

732729
# assert
733-
assert FileAttachment.objects.filter(id=attachment_1.id).exists()
734-
assert not FileAttachment.objects.filter(id=attachment_2.id).exists()
730+
assert Attachment.objects.filter(id=attachment_1.id).exists()
731+
assert not Attachment.objects.filter(id=attachment_2.id).exists()
735732

736733

737734
def test__remove_unused_attachments__value_none_deleted__ok():
@@ -750,25 +747,23 @@ def test__remove_unused_attachments__value_none_deleted__ok():
750747
workflow=workflow,
751748
account=account,
752749
)
753-
attachment_1 = FileAttachment.objects.create(
754-
name='keep.jpg',
755-
url='https://test.test/keep.jpg',
756-
size=100,
757-
account_id=account.id,
750+
attachment_1 = Attachment.objects.create(
751+
file_id='keep.jpg',
752+
account=account,
753+
source_type=SourceType.TASK,
754+
access_type=AccessType.RESTRICTED,
758755
output=task_field,
759756
)
760757
service = TaskFieldService(instance=task_field, user=user)
761-
value = attachment_1.url
762-
attachment_ids = [str(attachment_1.id)]
758+
markdown_values = [attachment_1.file_id]
763759

764760
# act
765761
service._remove_unused_attachments(
766-
value=value,
767-
attachment_ids=attachment_ids,
762+
markdown_values=markdown_values,
768763
)
769764

770765
# assert
771-
assert FileAttachment.objects.filter(id=attachment_1.id).exists()
766+
assert Attachment.objects.filter(id=attachment_1.id).exists()
772767

773768

774769
def test__remove_unused_attachments__no_value__ok():
@@ -787,23 +782,22 @@ def test__remove_unused_attachments__no_value__ok():
787782
workflow=workflow,
788783
account=account,
789784
)
790-
attachment_1 = FileAttachment.objects.create(
791-
name='delete.jpg',
792-
url='https://test.test/delete.jpg',
793-
size=100,
794-
account_id=account.id,
785+
attachment_1 = Attachment.objects.create(
786+
file_id='delete.jpg',
787+
account=account,
788+
source_type=SourceType.TASK,
789+
access_type=AccessType.RESTRICTED,
795790
output=task_field,
796791
)
797792
service = TaskFieldService(instance=task_field, user=user)
798793

799794
# act
800795
service._remove_unused_attachments(
801-
value=None,
802-
attachment_ids=None,
796+
markdown_values=None,
803797
)
804798

805799
# assert
806-
assert not FileAttachment.objects.filter(id=attachment_1.id).exists()
800+
assert not Attachment.objects.filter(id=attachment_1.id).exists()
807801

808802

809803
def test__remove_unused_attachments__no_value_no_attachments__ok():
@@ -826,12 +820,11 @@ def test__remove_unused_attachments__no_value_no_attachments__ok():
826820

827821
# act
828822
service._remove_unused_attachments(
829-
value=None,
830-
attachment_ids=None,
823+
markdown_values=None,
831824
)
832825

833826
# assert
834-
assert not FileAttachment.objects.filter(
827+
assert not Attachment.objects.filter(
835828
output=task_field,
836829
).exists()
837830

@@ -1065,7 +1058,10 @@ def test_partial_update__ok(mocker):
10651058
service.partial_update(value=raw_value)
10661059

10671060
# assert
1068-
get_valid_value_mock.assert_called_once_with(raw_value)
1061+
get_valid_value_mock.assert_called_once_with(
1062+
raw_value=raw_value,
1063+
selections=None,
1064+
)
10691065
link_new_attachments_mock.assert_not_called()
10701066
task_field.refresh_from_db()
10711067
assert task_field.value == value
@@ -1383,10 +1379,6 @@ def test_remove_unused_attachments__comment_attachment_unchanged(mocker):
13831379
'src.processes.services.tasks.field.'
13841380
'TaskFieldService._link_new_attachments',
13851381
)
1386-
update_selections_mock = mocker.patch(
1387-
'src.processes.services.tasks.field.'
1388-
'TaskFieldService._update_selections',
1389-
)
13901382
service = TaskFieldService(
13911383
instance=task_field,
13921384
user=user,
@@ -1399,7 +1391,6 @@ def test_remove_unused_attachments__comment_attachment_unchanged(mocker):
13991391
get_valid_value_mock.assert_called_once()
14001392
assert Attachment.objects.filter(id=comment_attachment.id).exists()
14011393
link_new_mock.assert_called_once_with(None)
1402-
update_selections_mock.assert_not_called()
14031394

14041395

14051396
def test__partial_update__no_value_kwarg__ok(mocker):
@@ -1437,7 +1428,10 @@ def test__partial_update__no_value_kwarg__ok(mocker):
14371428
service.partial_update()
14381429

14391430
# assert
1440-
get_valid_value_mock.assert_called_once_with(None)
1431+
get_valid_value_mock.assert_called_once_with(
1432+
raw_value=None,
1433+
selections=None,
1434+
)
14411435
remove_unused_attachments_mock.assert_not_called()
14421436
link_new_attachments_mock.assert_not_called()
14431437

@@ -1938,6 +1932,7 @@ def test__get_valid_checkbox_value__not_in_allowed__raise_exception(mocker):
19381932
get_selections_values_mock.assert_called_once_with()
19391933

19401934

1935+
@override_settings(FILE_DOMAIN='example.com')
19411936
def test_get_valid_file_value__one_file__ok():
19421937

19431938
# arrange

backend/src/storage/migrations/0001_initial.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class Migration(migrations.Migration):
1111

1212
dependencies = [
1313
('accounts', '0139_remove_account_external_id'),
14+
('processes', '0001_initial'),
1415
]
1516

1617
operations = [

backend/src/storage/serializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class AttachmentCheckPermissionSerializer(
3737
"""Serializer for checking file access permissions."""
3838

3939
file_id = serializers.CharField(
40-
max_length=64,
40+
max_length=512,
4141
required=True,
4242
help_text='Unique file identifier',
4343
)

0 commit comments

Comments
 (0)