From 7e1d8111b3013cfa5fd201bb2970774ec43ed5c8 Mon Sep 17 00:00:00 2001 From: Sylvain Boissel Date: Mon, 8 Dec 2025 19:05:33 +0100 Subject: [PATCH 01/11] =?UTF-8?q?=E2=9C=A8(backend)=20manage=20reconciliat?= =?UTF-8?q?ion=20requests=20for=20user=20accounts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For now, the reconciliation requests are imported through CSV in the Django admin, which sends confirmation email to both addresses. When both are checked, the actual reconciliation is processed, in a threefold process (update document acess, update invitations, update user status.) --- CHANGELOG.md | 1 + docs/user_account_reconciliation.md | 19 + src/backend/core/admin.py | 71 +++- src/backend/core/api/viewsets.py | 55 +++ ...onciliationcsvimport_userreconciliation.py | 164 ++++++++ src/backend/core/models.py | 276 +++++++++++- src/backend/core/tasks/user_reconciliation.py | 98 +++++ .../data/example_reconciliation_basic.csv | 6 + .../data/example_reconciliation_error.csv | 2 + .../example_reconciliation_grist_form.csv | 5 + ...xample_reconciliation_grist_form_error.csv | 2 + .../tests/test_models_user_reconciliation.py | 396 ++++++++++++++++++ src/backend/core/urls.py | 5 + .../locale/br_FR/LC_MESSAGES/django.po | 6 +- .../locale/de_DE/LC_MESSAGES/django.po | 6 +- .../locale/en_US/LC_MESSAGES/django.po | 6 +- .../locale/es_ES/LC_MESSAGES/django.po | 6 +- .../locale/fr_FR/LC_MESSAGES/django.po | 6 +- .../locale/it_IT/LC_MESSAGES/django.po | 6 +- .../locale/nl_NL/LC_MESSAGES/django.po | 6 +- .../locale/pt_PT/LC_MESSAGES/django.po | 6 +- .../locale/ru_RU/LC_MESSAGES/django.po | 6 +- .../locale/sl_SI/LC_MESSAGES/django.po | 6 +- .../locale/sv_SE/LC_MESSAGES/django.po | 6 +- .../locale/tr_TR/LC_MESSAGES/django.po | 6 +- .../locale/uk_UA/LC_MESSAGES/django.po | 6 +- .../locale/zh_CN/LC_MESSAGES/django.po | 6 +- src/mail/mjml/user_template.mjml | 62 +++ 28 files changed, 1200 insertions(+), 46 deletions(-) create mode 100644 docs/user_account_reconciliation.md create mode 100644 src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py create mode 100644 src/backend/core/tasks/user_reconciliation.py create mode 100644 src/backend/core/tests/data/example_reconciliation_basic.csv create mode 100644 src/backend/core/tests/data/example_reconciliation_error.csv create mode 100644 src/backend/core/tests/data/example_reconciliation_grist_form.csv create mode 100644 src/backend/core/tests/data/example_reconciliation_grist_form_error.csv create mode 100644 src/backend/core/tests/test_models_user_reconciliation.py create mode 100644 src/mail/mjml/user_template.mjml diff --git a/CHANGELOG.md b/CHANGELOG.md index 10eca5f08d..4fa36e5506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to - ✨(helm) redirecting system #1697 - 📱(frontend) add comments for smaller device #1737 - ✨(project) add custom js support via config #1759 +- ✨(backend) manage reconciliation requests for user accounts #1708 ### Changed diff --git a/docs/user_account_reconciliation.md b/docs/user_account_reconciliation.md new file mode 100644 index 0000000000..59c6280f2a --- /dev/null +++ b/docs/user_account_reconciliation.md @@ -0,0 +1,19 @@ +# User account reconciliation + +It is possible to merge user accounts based on their email addresses. + +Docs does not have an internal process to requests, but it allows the import of a CSV from an external form +(e.g. made with Grist) in the Django admin panel (in "Core" > "User reconciliation CSV imports" > "Add user reconciliation") + +The CSV must contain the following mandatory columns: + +- `active_email`: the email of the user that will remain active after the process. +- `inactive_email`: the email of the user(s) that will be merged into the active user. It is possible to indicate several emails, so the user only has to make one request even if they have more than two accounts. +- `status`: the value must be `pending`. Rows with other values will be ignored. + +The following columns are optional: `active_email_checked` and `inactive_email_checked` (both must contain `0` (False) or `1` (True), and both default to False.) +If present, it allows to indicate that the source form has a way to validate that the user making the request actually controls the email addresses, skipping the need to send confirmation emails (cf. below) + +Once the CSV file is processed, this will create entries in "Core" > "User reconciliations" and send verification emails to validate that the user making the request actually controls the email addresses (unless `active_email_checked` and `inactive_email_checked` were set to `1` in the CSV) + +In "Core" > "User reconciliations", an admin can then select all rows they wish to process and check the action "Process selected user reconciliations". Only rows that have the status `ready` and for which both emails have been validated will be processed. diff --git a/src/backend/core/admin.py b/src/backend/core/admin.py index 8832903079..4f1d1dff88 100644 --- a/src/backend/core/admin.py +++ b/src/backend/core/admin.py @@ -1,12 +1,14 @@ """Admin classes and registrations for core app.""" -from django.contrib import admin +from django.contrib import admin, messages from django.contrib.auth import admin as auth_admin +from django.shortcuts import redirect from django.utils.translation import gettext_lazy as _ from treebeard.admin import TreeAdmin -from . import models +from core import models +from core.tasks.user_reconciliation import user_reconciliation_csv_import_job class TemplateAccessInline(admin.TabularInline): @@ -104,6 +106,71 @@ class UserAdmin(auth_admin.UserAdmin): search_fields = ("id", "sub", "admin_email", "email", "full_name") +@admin.register(models.UserReconciliationCsvImport) +class UserReconciliationCsvImportAdmin(admin.ModelAdmin): + """Admin class for UserReconciliationCsvImport model.""" + + list_display = ("id", "created_at", "status") + + def save_model(self, request, obj, form, change): + """Override save_model to trigger the import task on creation.""" + super().save_model(request, obj, form, change) + + if not change: + user_reconciliation_csv_import_job.delay(obj.pk) + messages.success(request, _("Import job created and queued.")) + return redirect("..") + + +@admin.action(description=_("Process selected user reconciliations")) +def process_reconciliation(_modeladmin, _request, queryset): + """ + Admin action to process selected user reconciliations. + The action will process only entries that are ready and have both emails checked. + + Its action is threefold: + - Transfer document accesses from inactive to active user, updating roles as needed. + - Activate the active user and deactivate the inactive user. + """ + processable_entries = queryset.filter( + status="ready", active_email_checked=True, inactive_email_checked=True + ) + + # Prepare the bulk operations + updated_documentaccess = [] + removed_documentaccess = [] + update_users_active_status = [] + + for entry in processable_entries: + new_updated_documentaccess, new_removed_documentaccess = ( + entry.process_documentaccess_reconciliation() + ) + updated_documentaccess += new_updated_documentaccess + removed_documentaccess += new_removed_documentaccess + + entry.active_user.is_active = True + entry.inactive_user.is_active = False + update_users_active_status.append(entry.active_user) + update_users_active_status.append(entry.inactive_user) + + # Actually perform the bulk operations + models.DocumentAccess.objects.bulk_update(updated_documentaccess, ["user", "role"]) + + if removed_documentaccess: + ids_to_delete = [rd.id for rd in removed_documentaccess] + models.DocumentAccess.objects.filter(id__in=ids_to_delete).delete() + + models.User.objects.bulk_update(update_users_active_status, ["is_active"]) + + +@admin.register(models.UserReconciliation) +class UserReconciliationAdmin(admin.ModelAdmin): + """Admin class for UserReconciliation model.""" + + list_display = ["id", "created_at", "status"] + actions = [process_reconciliation] + + @admin.register(models.Template) class TemplateAdmin(admin.ModelAdmin): """Template admin interface declaration.""" diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index c4a137eed6..a03e3f17b1 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -40,6 +40,8 @@ from rest_framework import filters, status, viewsets from rest_framework import response as drf_response from rest_framework.permissions import AllowAny +from rest_framework.response import Response +from rest_framework.views import APIView from core import authentication, choices, enums, models from core.api.filters import remove_accents @@ -247,6 +249,59 @@ def get_me(self, request): ) +class ReconciliationConfirmView(APIView): + """API endpoint to confirm user reconciliation emails. + + GET /user_reconciliations/{user_type}/{confirmation_id}/ + Marks `active_email_checked` or `inactive_email_checked` to True. + """ + + permission_classes = [AllowAny] + + def get(self, request, user_type, confirmation_id): + """ + Check the confirmation ID and mark the corresponding email as checked. + """ + try: + # validate UUID + uuid_obj = uuid.UUID(confirmation_id) + except ValueError: + return Response( + {"detail": "Badly formatted confirmation id"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if user_type not in ("active", "inactive"): + return Response( + {"detail": "Invalid user_type"}, status=status.HTTP_400_BAD_REQUEST + ) + + lookup = ( + {"active_confirmation_id": uuid_obj} + if user_type == "active" + else {"inactive_confirmation_id": uuid_obj} + ) + + try: + rec = models.UserReconciliation.objects.get(**lookup) + except models.UserReconciliation.DoesNotExist: + return Response( + {"detail": "Reconciliation entry not found"}, + status=status.HTTP_404_NOT_FOUND, + ) + + field_name = ( + "active_email_checked" + if user_type == "active" + else "inactive_email_checked" + ) + if not getattr(rec, field_name): + setattr(rec, field_name, True) + rec.save() + + return Response({"detail": "Confirmation received"}) + + class ResourceAccessViewsetMixin: """Mixin with methods common to all access viewsets.""" diff --git a/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py b/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py new file mode 100644 index 0000000000..fed96eac62 --- /dev/null +++ b/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py @@ -0,0 +1,164 @@ +# Generated by Django 5.2.9 on 2026-01-06 18:46 + +import uuid + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0027_auto_20251120_0956"), + ] + + operations = [ + migrations.CreateModel( + name="UserReconciliationCsvImport", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + help_text="primary key for the record as UUID", + primary_key=True, + serialize=False, + verbose_name="id", + ), + ), + ( + "created_at", + models.DateTimeField( + auto_now_add=True, + help_text="date and time at which a record was created", + verbose_name="created on", + ), + ), + ( + "updated_at", + models.DateTimeField( + auto_now=True, + help_text="date and time at which a record was last updated", + verbose_name="updated on", + ), + ), + ("file", models.FileField(upload_to="imports/")), + ( + "status", + models.CharField( + choices=[ + ("pending", "Pending"), + ("running", "Running"), + ("done", "Done"), + ("error", "Error"), + ], + default="pending", + max_length=20, + ), + ), + ("logs", models.TextField(blank=True)), + ], + options={ + "verbose_name": "user reconciliation CSV import", + "verbose_name_plural": "user reconciliation CSV imports", + }, + ), + migrations.CreateModel( + name="UserReconciliation", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + help_text="primary key for the record as UUID", + primary_key=True, + serialize=False, + verbose_name="id", + ), + ), + ( + "created_at", + models.DateTimeField( + auto_now_add=True, + help_text="date and time at which a record was created", + verbose_name="created on", + ), + ), + ( + "updated_at", + models.DateTimeField( + auto_now=True, + help_text="date and time at which a record was last updated", + verbose_name="updated on", + ), + ), + ( + "active_email", + models.EmailField( + max_length=254, verbose_name="Active email address" + ), + ), + ( + "inactive_email", + models.EmailField( + max_length=254, verbose_name="Email address to deactivate" + ), + ), + ("active_email_checked", models.BooleanField(default=False)), + ("inactive_email_checked", models.BooleanField(default=False)), + ( + "active_confirmation_id", + models.UUIDField( + default=uuid.uuid4, editable=False, null=True, unique=True + ), + ), + ( + "inactive_confirmation_id", + models.UUIDField( + default=uuid.uuid4, editable=False, null=True, unique=True + ), + ), + ( + "status", + models.CharField( + choices=[ + ("pending", "Pending"), + ("ready", "Ready"), + ("done", "Done"), + ("error", "Error"), + ], + default="pending", + max_length=20, + ), + ), + ("logs", models.TextField(blank=True)), + ( + "active_user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="active_user", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "inactive_user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="inactive_user", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name": "user reconciliation", + "verbose_name_plural": "user reconciliations", + "ordering": ["-created_at"], + }, + ), + ] diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 453e683f88..c5f4d3a6f9 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -1,6 +1,7 @@ """ Declare and configure the models for the impress core application """ + # pylint: disable=too-many-lines import hashlib @@ -32,14 +33,14 @@ from timezone_field import TimeZoneField from treebeard.mp_tree import MP_Node, MP_NodeManager, MP_NodeQuerySet -from .choices import ( +from core.choices import ( PRIVILEGED_ROLES, LinkReachChoices, LinkRoleChoices, RoleChoices, get_equivalent_link_definition, ) -from .validators import sub_validator +from core.validators import sub_validator logger = getLogger(__name__) @@ -250,6 +251,37 @@ def _convert_valid_invitations(self): valid_invitations.delete() + def send_email(self, subject, context=None, language=None): + """Generate and send email to the user from a template.""" + emails = [self.email] + context = context or {} + domain = Site.objects.get_current().domain + language = language or get_language() + context.update( + { + "brandname": settings.EMAIL_BRAND_NAME, + "domain": domain, + "logo_img": settings.EMAIL_LOGO_IMG, + } + ) + + with override(language): + msg_html = render_to_string("mail/html/user_template.html", context) + msg_plain = render_to_string("mail/text/user_template.txt", context) + subject = str(subject) # Force translation + + try: + send_mail( + subject.capitalize(), + msg_plain, + settings.EMAIL_FROM, + emails, + html_message=msg_html, + fail_silently=False, + ) + except smtplib.SMTPException as exception: + logger.error("invitation to %s was not sent: %s", emails, exception) + def email_user(self, subject, message, from_email=None, **kwargs): """Email this user.""" if not self.email: @@ -265,6 +297,246 @@ def teams(self): return [] +class UserReconciliation(BaseModel): + """Model to run batch jobs to replace an active user by another one""" + + active_email = models.EmailField(_("Active email address")) + inactive_email = models.EmailField(_("Email address to deactivate")) + active_email_checked = models.BooleanField(default=False) + inactive_email_checked = models.BooleanField(default=False) + active_user = models.ForeignKey( + User, + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="active_user", + ) + inactive_user = models.ForeignKey( + User, + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="inactive_user", + ) + active_confirmation_id = models.UUIDField( + default=uuid.uuid4, unique=True, editable=False, null=True + ) + inactive_confirmation_id = models.UUIDField( + default=uuid.uuid4, unique=True, editable=False, null=True + ) + + status = models.CharField( + max_length=20, + choices=[ + ("pending", _("Pending")), + ("ready", _("Ready")), + ("done", _("Done")), + ("error", _("Error")), + ], + default="pending", + ) + logs = models.TextField(blank=True) + + class Meta: + verbose_name = _("user reconciliation") + verbose_name_plural = _("user reconciliations") + ordering = ["-created_at"] + + def __str__(self): + return f"Reconciliation from {self.inactive_email} to {self.active_email}" + + def save(self, *args, **kwargs): + """ + For pending queries, identify the actual users and send validation emails + """ + if self.status == "pending": + self.active_user = User.objects.filter(email=self.active_email).first() + self.inactive_user = User.objects.filter(email=self.inactive_email).first() + + if self.active_user and self.inactive_user: + if not self.active_email_checked: + self.send_reconciliation_confirm_email( + self.active_user, "active", self.active_confirmation_id + ) + if not self.inactive_email_checked: + self.send_reconciliation_confirm_email( + self.inactive_user, "inactive", self.inactive_confirmation_id + ) + self.status = "ready" + else: + self.status = "error" + self.logs = "Error: Both active and inactive users need to exist." + + super().save(*args, **kwargs) + + def process_documentaccess_reconciliation(self): + """ + Process the reconciliation by transferring document accesses from the inactive user + to the active user. + """ + updated_accesses = [] + removed_accesses = [] + inactive_accesses = DocumentAccess.objects.filter(user=self.inactive_user) + + # Check documents where the active user already has access + documents_with_both_users = inactive_accesses.values_list("document", flat=True) + existing_accesses = DocumentAccess.objects.filter(user=self.active_user).filter( + document__in=documents_with_both_users + ) + existing_roles_per_doc = dict(existing_accesses.values_list("document", "role")) + + for entry in inactive_accesses: + if entry.document_id in existing_roles_per_doc: + # Update role if needed + existing_role = existing_roles_per_doc[entry.document_id] + max_role = RoleChoices.max(entry.role, existing_role) + if existing_role != max_role: + existing_access = existing_accesses.get(document=entry.document) + existing_access.role = max_role + updated_accesses.append(existing_access) + removed_accesses.append(entry) + else: + entry.user = self.active_user + updated_accesses.append(entry) + + self.logs += f"""Requested update for {len(updated_accesses)} DocumentAccess items + and deletion for {len(removed_accesses)} DocumentAccess items.\n""" + self.status = "done" + self.send_reconciliation_done_email() + + self.save() + + return updated_accesses, removed_accesses + + def send_reconciliation_confirm_email( + self, user, user_type, confirmation_id, language=None + ): + """Method allowing to send confirmation email for reconciliation requests.""" + language = language or get_language() + domain = Site.objects.get_current().domain + + with override(language): + subject = _("Confirm by clicking the link to start the reconciliation") + context = { + "title": subject, + "message_bold": _( + "You have requested a reconciliation of your user accounts on Docs." + ), + "message": _( + """To confirm that you are the one who initiated the request + and that this email belongs to you:""" + ), + "link": f"{domain}/user_reconciliations/{user_type}/{confirmation_id}/", + "link_label": str(_("Click here")), + "button_label": str(_("Confirm")), + } + + user.send_email(subject, context, language) + + def send_reconciliation_done_email(self, language=None): + """Method allowing to send done email for reconciliation requests.""" + language = language or get_language() + domain = Site.objects.get_current().domain + + with override(language): + subject = _("Your accounts have been merged") + context = { + "title": subject, + "message_bold": _("Your reconciliation request has been processed."), + "message": _("New documents are likely associated with your account:"), + "link": f"{domain}/", + "link_label": str(_("Click here to see")), + "button_label": str(_("See my documents")), + } + + self.active_user.send_email(subject, context, language) + + +class UserReconciliationCsvImport(BaseModel): + """Model to import reconciliations requests from an external source + (eg, )""" + + file = models.FileField(upload_to="imports/") + status = models.CharField( + max_length=20, + choices=[ + ("pending", _("Pending")), + ("running", _("Running")), + ("done", _("Done")), + ("error", _("Error")), + ], + default="pending", + ) + logs = models.TextField(blank=True) + + class Meta: + verbose_name = _("user reconciliation CSV import") + verbose_name_plural = _("user reconciliation CSV imports") + + def __str__(self): + return f"User reconciliation CSV import {self.id}" + + def send_email(self, subject, emails, context=None, language=None): + """Generate and send email to the user from a template.""" + context = context or {} + domain = Site.objects.get_current().domain + language = language or get_language() + context.update( + { + "brandname": settings.EMAIL_BRAND_NAME, + "domain": domain, + "logo_img": settings.EMAIL_LOGO_IMG, + } + ) + + with override(language): + msg_html = render_to_string("mail/html/user_template.html", context) + msg_plain = render_to_string("mail/text/user_template.txt", context) + subject = str(subject) # Force translation + + try: + send_mail( + subject.capitalize(), + msg_plain, + settings.EMAIL_FROM, + emails, + html_message=msg_html, + fail_silently=False, + ) + except smtplib.SMTPException as exception: + logger.error("invitation to %s was not sent: %s", emails, exception) + + def send_reconciliation_error_email(self, email_1, email_2, language=None): + """Method allowing to send email for reconciliation requests with errors.""" + language = language or get_language() + domain = Site.objects.get_current().domain + + emails = [email_1, email_2] + + with override(language): + subject = _("Reconciliation of your Docs accounts not completed") + context = { + "title": subject, + "message_bold": _("Your request for reconciliation was unsuccessful."), + "message": _( + """Reconciliation failed for the following email addresses: + + - {email1} + - {email2} + + Please check for typos. + + You can submit another request with the valid email addresses. + """ + ).format(email1=email_1, email2=email_2), + "link": f"{domain}/", + "link_label": str(_("Click here")), + "button_label": str(_("Make a new request")), + } + + self.send_email(subject, emails, context, language) + + class BaseAccess(BaseModel): """Base model for accesses to handle resources.""" diff --git a/src/backend/core/tasks/user_reconciliation.py b/src/backend/core/tasks/user_reconciliation.py new file mode 100644 index 0000000000..020ca5427c --- /dev/null +++ b/src/backend/core/tasks/user_reconciliation.py @@ -0,0 +1,98 @@ +"""Processing tasks for user reconciliation CSV imports.""" + +import csv +import traceback +import uuid + +from django.core.exceptions import ValidationError +from django.core.validators import validate_email +from django.db import IntegrityError + +from botocore.exceptions import ClientError + +from core.models import UserReconciliation, UserReconciliationCsvImport + +from impress.celery_app import app + + +@app.task +def user_reconciliation_csv_import_job(job_id): + """Process a UserReconciliationCsvImport job. + Creates UserReconciliation entries from the CSV file. + + Does some sanity checks on the data: + - active_email and inactive_email must be valid email addresses + - active_email and inactive_email cannot be the same + """ + # Imports the CSV file, breaks it into UserReconciliation items + job = UserReconciliationCsvImport.objects.get(id=job_id) + job.status = "running" + job.save() + + try: + with job.file.open(mode="r") as f: + reader = csv.DictReader(f) + rec_entries_created = 0 + for row in reader: + status = row["status"] + + if status == "pending": + active_email_checked = row.get("active_email_checked", "0") == "1" + inactive_email_checked = ( + row.get("inactive_email_checked", "0") == "1" + ) + + active_email = row["active_email"] + inactive_emails = row["inactive_email"].split("|") + try: + validate_email(active_email) + except ValidationError as e: + job.send_reconciliation_error_email( + active_email, inactive_emails[0] + ) + job.status = "error" + job.logs = f"{e!s}\n{traceback.format_exc()}" + + for inactive_email in inactive_emails: + try: + validate_email(inactive_email) + except ValidationError as e: + job.send_reconciliation_error_email( + active_email, inactive_email + ) + job.status = "error" + job.logs = f"{e!s}\n{traceback.format_exc()}" + if inactive_email == active_email: + raise ValueError( + "Active and inactive emails cannot be the same." + ) + + rec_entry = UserReconciliation.objects.create( + active_email=active_email, + inactive_email=inactive_email, + active_email_checked=active_email_checked, + inactive_email_checked=inactive_email_checked, + active_confirmation_id=uuid.uuid4(), + inactive_confirmation_id=uuid.uuid4(), + status="pending", + ) + rec_entry.save() + rec_entries_created += 1 + + job.status = "done" + job.logs = f"""Import completed successfully. {reader.line_num} rows processed. + {rec_entries_created} reconciliation entries created.""" + except ( + csv.Error, + KeyError, + ValueError, + ValidationError, + IntegrityError, + OSError, + ClientError, + ) as e: + # Catch expected I/O/CSV/model errors and record traceback in logs for debugging + job.status = "error" + job.logs = f"{e!s}\n{traceback.format_exc()}" + finally: + job.save() diff --git a/src/backend/core/tests/data/example_reconciliation_basic.csv b/src/backend/core/tests/data/example_reconciliation_basic.csv new file mode 100644 index 0000000000..2f3450d712 --- /dev/null +++ b/src/backend/core/tests/data/example_reconciliation_basic.csv @@ -0,0 +1,6 @@ +active_email,inactive_email,active_email_checked,inactive_email_checked,status +"user.test40@example.com","user.test41@example.com",0,0,pending +"user.test42@example.com","user.test43@example.com",0,1,pending +"user.test44@example.com","user.test45@example.com",1,0,pending +"user.test46@example.com","user.test47@example.com",1,1,pending +"user.test48@example.com","user.test49@example.com",1,1,pending \ No newline at end of file diff --git a/src/backend/core/tests/data/example_reconciliation_error.csv b/src/backend/core/tests/data/example_reconciliation_error.csv new file mode 100644 index 0000000000..6f8d71abc9 --- /dev/null +++ b/src/backend/core/tests/data/example_reconciliation_error.csv @@ -0,0 +1,2 @@ +active_email,inactive_email,active_email_checked,inactive_email_checked,status +"user.test40@example.com",,0,0,pending diff --git a/src/backend/core/tests/data/example_reconciliation_grist_form.csv b/src/backend/core/tests/data/example_reconciliation_grist_form.csv new file mode 100644 index 0000000000..d2b76bcb67 --- /dev/null +++ b/src/backend/core/tests/data/example_reconciliation_grist_form.csv @@ -0,0 +1,5 @@ +merge_accept,active_email,inactive_email,status +true,user.test10@example.com,user.test11@example.com|user.test12@example.com,pending +true,user.test30@example.com,user.test31@example.com|user.test32@example.com|user.test33@example.com|user.test34@example.com|user.test35@example.com,pending +true,user.test20@example.com,user.test21@example.com,pending +true,user.test22@example.com,user.test23@example.com,pending diff --git a/src/backend/core/tests/data/example_reconciliation_grist_form_error.csv b/src/backend/core/tests/data/example_reconciliation_grist_form_error.csv new file mode 100644 index 0000000000..934816f4b6 --- /dev/null +++ b/src/backend/core/tests/data/example_reconciliation_grist_form_error.csv @@ -0,0 +1,2 @@ +merge_accept,active_email,inactive_email,status +true,user.test20@example.com,user.test20@example.com,pending diff --git a/src/backend/core/tests/test_models_user_reconciliation.py b/src/backend/core/tests/test_models_user_reconciliation.py new file mode 100644 index 0000000000..3d74ee5a5e --- /dev/null +++ b/src/backend/core/tests/test_models_user_reconciliation.py @@ -0,0 +1,396 @@ +""" +Unit tests for the UserReconciliationCsvImport model +""" + +import uuid +from pathlib import Path + +from django.core import mail +from django.core.files.base import ContentFile + +import pytest + +from core import factories, models +from core.admin import process_reconciliation +from core.tasks.user_reconciliation import user_reconciliation_csv_import_job + +pytestmark = pytest.mark.django_db + + +@pytest.fixture(name="import_example_csv_basic") +def fixture_import_example_csv_basic(): + """ + Import an example CSV file for user reconciliation + and return the created import object. + """ + # Create users referenced in the CSV + for i in range(40, 50): + factories.UserFactory(email=f"user.test{i}@example.com") + + example_csv_path = Path(__file__).parent / "data/example_reconciliation_basic.csv" + with open(example_csv_path, "rb") as f: + csv_file = ContentFile(f.read(), name="example_reconciliation_basic.csv") + csv_import = models.UserReconciliationCsvImport(file=csv_file) + csv_import.save() + + return csv_import + + +@pytest.fixture(name="import_example_csv_grist_form") +def fixture_import_example_csv_grist_form(): + """ + Import an example CSV file for user reconciliation + and return the created import object. + """ + # Create users referenced in the CSV + for i in range(10, 40): + factories.UserFactory(email=f"user.test{i}@example.com") + + example_csv_path = ( + Path(__file__).parent / "data/example_reconciliation_grist_form.csv" + ) + with open(example_csv_path, "rb") as f: + csv_file = ContentFile(f.read(), name="example_reconciliation_grist_form.csv") + csv_import = models.UserReconciliationCsvImport(file=csv_file) + csv_import.save() + + return csv_import + + +def test_user_reconciliation_csv_import_entry_is_created(import_example_csv_basic): + """Test that a UserReconciliationCsvImport entry is created correctly.""" + assert import_example_csv_basic.status == "pending" + assert import_example_csv_basic.file.name.endswith( + "example_reconciliation_basic.csv" + ) + + +def test_user_reconciliation_csv_import_entry_is_created_grist_form( + import_example_csv_grist_form, +): + """Test that a UserReconciliationCsvImport entry is created correctly.""" + assert import_example_csv_grist_form.status == "pending" + assert import_example_csv_grist_form.file.name.endswith( + "example_reconciliation_grist_form.csv" + ) + + +def test_incorrect_csv_format_handling(): + """Test that an incorrectly formatted CSV file is handled gracefully.""" + example_csv_path = Path(__file__).parent / "data/example_reconciliation_error.csv" + with open(example_csv_path, "rb") as f: + csv_file = ContentFile(f.read(), name="example_reconciliation_error.csv") + csv_import = models.UserReconciliationCsvImport(file=csv_file) + csv_import.save() + + assert csv_import.status == "pending" + + user_reconciliation_csv_import_job(csv_import.id) + csv_import.refresh_from_db() + + assert "This field cannot be blank" in csv_import.logs + assert csv_import.status == "error" + + # pylint: disable-next=no-member + assert len(mail.outbox) == 1 + + # pylint: disable-next=no-member + email = mail.outbox[0] + + assert email.to == ["user.test40@example.com", ""] + email_content = " ".join(email.body.split()) + + assert "Reconciliation of your Docs accounts not completed" in email_content + + +def test_incorrect_csv_data_handling_grist_form(): + """Test that a CSV file with incorrect data is handled gracefully.""" + example_csv_path = ( + Path(__file__).parent / "data/example_reconciliation_grist_form_error.csv" + ) + with open(example_csv_path, "rb") as f: + csv_file = ContentFile( + f.read(), name="example_reconciliation_grist_form_error.csv" + ) + csv_import = models.UserReconciliationCsvImport(file=csv_file) + csv_import.save() + + assert csv_import.status == "pending" + + user_reconciliation_csv_import_job(csv_import.id) + csv_import.refresh_from_db() + + assert "Active and inactive emails cannot be the same." in csv_import.logs + assert csv_import.status == "error" + + +def test_job_creates_reconciliation_entries(import_example_csv_basic): + """Test that the CSV import job creates UserReconciliation entries.""" + assert import_example_csv_basic.status == "pending" + user_reconciliation_csv_import_job(import_example_csv_basic.id) + + # Verify the job status changed + import_example_csv_basic.refresh_from_db() + assert import_example_csv_basic.status == "done" + assert "Import completed successfully." in import_example_csv_basic.logs + assert "6 rows processed." in import_example_csv_basic.logs + assert "5 reconciliation entries created." in import_example_csv_basic.logs + + # Verify reconciliation entries were created + reconciliations = models.UserReconciliation.objects.all() + assert reconciliations.count() == 5 + + +def test_job_creates_reconciliation_entries_grist_form(import_example_csv_grist_form): + """Test that the CSV import job creates UserReconciliation entries.""" + assert import_example_csv_grist_form.status == "pending" + user_reconciliation_csv_import_job(import_example_csv_grist_form.id) + + # Verify the job status changed + import_example_csv_grist_form.refresh_from_db() + assert "Import completed successfully" in import_example_csv_grist_form.logs + assert import_example_csv_grist_form.status == "done" + + # Verify reconciliation entries were created + reconciliations = models.UserReconciliation.objects.all() + assert reconciliations.count() == 9 + + +def test_csv_import_reconciliation_data_is_correct(import_example_csv_basic): + """Test that the data in created UserReconciliation entries matches the CSV.""" + user_reconciliation_csv_import_job(import_example_csv_basic.id) + + reconciliations = models.UserReconciliation.objects.order_by("created_at") + first_entry = reconciliations.first() + + assert first_entry.active_email == "user.test40@example.com" + assert first_entry.inactive_email == "user.test41@example.com" + assert first_entry.active_email_checked is False + assert first_entry.inactive_email_checked is False + + for rec in reconciliations: + assert rec.status == "ready" + + +@pytest.fixture(name="user_reconciliation_users_and_docs") +def fixture_user_reconciliation_users_and_docs(): + """Fixture to create two users with overlapping document accesses + for reconciliation tests.""" + user_1 = factories.UserFactory(email="user.test1@example.com") + user_2 = factories.UserFactory(email="user.test2@example.com") + + # Create 10 distinct document accesses for each user + userdocs_u1 = [ + factories.UserDocumentAccessFactory(user=user_1, role="editor") + for _ in range(10) + ] + userdocs_u2 = [ + factories.UserDocumentAccessFactory(user=user_2, role="editor") + for _ in range(10) + ] + + # Make the first 3 documents of each list shared with the other user + # with a lower role + for ud in userdocs_u1[0:3]: + factories.UserDocumentAccessFactory( + user=user_2, document=ud.document, role="reader" + ) + + for ud in userdocs_u2[0:3]: + factories.UserDocumentAccessFactory( + user=user_1, document=ud.document, role="reader" + ) + + # Make the next 3 documents of each list shared with the other user + # with a higher role + for ud in userdocs_u1[3:6]: + factories.UserDocumentAccessFactory( + user=user_2, document=ud.document, role="owner" + ) + + for ud in userdocs_u2[3:6]: + factories.UserDocumentAccessFactory( + user=user_1, document=ud.document, role="owner" + ) + + return (user_1, user_2, userdocs_u1, userdocs_u2) + + +def test_user_reconciliation_is_created(user_reconciliation_users_and_docs): + """Test that a UserReconciliation entry can be created and saved.""" + user_1, user_2, _userdocs_u1, _userdocs_u2 = user_reconciliation_users_and_docs + rec = models.UserReconciliation.objects.create( + active_email=user_1.email, + inactive_email=user_2.email, + active_email_checked=False, + inactive_email_checked=True, + active_confirmation_id=uuid.uuid4(), + inactive_confirmation_id=uuid.uuid4(), + status="pending", + ) + + rec.save() + assert rec.status == "ready" + + +def test_user_reconciliation_verification_emails_are_sent( + user_reconciliation_users_and_docs, +): + """Test that both UserReconciliation verification emails are sent.""" + user_1, user_2, _userdocs_u1, _userdocs_u2 = user_reconciliation_users_and_docs + rec = models.UserReconciliation.objects.create( + active_email=user_1.email, + inactive_email=user_2.email, + active_email_checked=False, + inactive_email_checked=False, + active_confirmation_id=uuid.uuid4(), + inactive_confirmation_id=uuid.uuid4(), + status="pending", + ) + + rec.save() + + # pylint: disable-next=no-member + assert len(mail.outbox) == 2 + + # pylint: disable-next=no-member + email_1 = mail.outbox[0] + + assert email_1.to == [user_1.email] + email_1_content = " ".join(email_1.body.split()) + + assert ( + "You have requested a reconciliation of your user accounts on Docs." + in email_1_content + ) + active_confirmation_id = rec.active_confirmation_id + inactive_confirmation_id = rec.inactive_confirmation_id + assert f"user_reconciliations/active/{active_confirmation_id}/" in email_1_content + + # pylint: disable-next=no-member + email_2 = mail.outbox[1] + + assert email_2.to == [user_2.email] + email_2_content = " ".join(email_2.body.split()) + + assert ( + "You have requested a reconciliation of your user accounts on Docs." + in email_2_content + ) + + assert ( + f"user_reconciliations/inactive/{inactive_confirmation_id}/" in email_2_content + ) + + +def test_user_reconciliation_only_starts_if_checks_are_made( + user_reconciliation_users_and_docs, +): + """Test that the admin action does not process entries + unless both email checks are confirmed. + """ + user_1, user_2, _userdocs_u1, _userdocs_u2 = user_reconciliation_users_and_docs + + # Create a reconciliation entry where only one email has been checked + rec = models.UserReconciliation.objects.create( + active_email=user_1.email, + inactive_email=user_2.email, + active_email_checked=True, + inactive_email_checked=False, + status="pending", + ) + rec.save() + + # Capture counts before running admin action + accesses_before_active = models.DocumentAccess.objects.filter(user=user_1).count() + accesses_before_inactive = models.DocumentAccess.objects.filter(user=user_2).count() + users_active_before = (user_1.is_active, user_2.is_active) + + # Call the admin action with the queryset containing our single rec + qs = models.UserReconciliation.objects.filter(id=rec.id) + process_reconciliation(None, None, qs) + + # Reload from DB and assert nothing was processed (checks prevent processing) + rec.refresh_from_db() + user_1.refresh_from_db() + user_2.refresh_from_db() + + assert rec.status == "ready" + assert ( + models.DocumentAccess.objects.filter(user=user_1).count() + == accesses_before_active + ) + assert ( + models.DocumentAccess.objects.filter(user=user_2).count() + == accesses_before_inactive + ) + assert (user_1.is_active, user_2.is_active) == users_active_before + + +def test_process_documentaccess_reconciliation( + user_reconciliation_users_and_docs, +): + """Use the fixture to verify accesses are consolidated on the active user.""" + user_1, user_2, userdocs_u1, userdocs_u2 = user_reconciliation_users_and_docs + + u1_2 = userdocs_u1[2] + u1_5 = userdocs_u1[5] + u2doc1 = userdocs_u2[1].document + u2doc5 = userdocs_u2[5].document + + rec = models.UserReconciliation.objects.create( + active_email=user_1.email, + inactive_email=user_2.email, + active_user=user_1, + inactive_user=user_2, + active_email_checked=True, + inactive_email_checked=True, + status="ready", + ) + + qs = models.UserReconciliation.objects.filter(id=rec.id) + process_reconciliation(None, None, qs) + + rec.refresh_from_db() + user_1.refresh_from_db() + user_2.refresh_from_db() + u1_2.refresh_from_db( + from_queryset=models.DocumentAccess.objects.select_for_update() + ) + u1_5.refresh_from_db( + from_queryset=models.DocumentAccess.objects.select_for_update() + ) + + # After processing, inactive user should have no accesses + # and active user should have one access per union document + # with the highest role + assert rec.status == "done" + assert "Requested update for 10 DocumentAccess items" in rec.logs + assert "and deletion for 12 DocumentAccess items" in rec.logs + assert models.DocumentAccess.objects.filter(user=user_2).count() == 0 + assert models.DocumentAccess.objects.filter(user=user_1).count() == 20 + assert u1_2.role == "editor" + assert u1_5.role == "owner" + + assert ( + models.DocumentAccess.objects.filter(user=user_1, document=u2doc1).first().role + == "editor" + ) + assert ( + models.DocumentAccess.objects.filter(user=user_1, document=u2doc5).first().role + == "owner" + ) + + assert user_1.is_active is True + assert user_2.is_active is False + + # pylint: disable-next=no-member + assert len(mail.outbox) == 1 + + # pylint: disable-next=no-member + email = mail.outbox[0] + + assert email.to == [user_1.email] + email_content = " ".join(email.body.split()) + + assert "Your accounts have been merged" in email_content diff --git a/src/backend/core/urls.py b/src/backend/core/urls.py index a24ebc9977..9ae062cd44 100644 --- a/src/backend/core/urls.py +++ b/src/backend/core/urls.py @@ -7,6 +7,7 @@ from rest_framework.routers import DefaultRouter from core.api import viewsets +from core.api.viewsets import ReconciliationConfirmView # - Main endpoints router = DefaultRouter() @@ -60,6 +61,10 @@ r"^documents/(?P[0-9a-z-]*)/threads/(?P[0-9a-z-]*)/", include(thread_related_router.urls), ), + path( + "user_reconciliations///", + ReconciliationConfirmView.as_view(), + ), ] ), ), diff --git a/src/backend/locale/br_FR/LC_MESSAGES/django.po b/src/backend/locale/br_FR/LC_MESSAGES/django.po index 3a946232e0..1c321e7409 100644 --- a/src/backend/locale/br_FR/LC_MESSAGES/django.po +++ b/src/backend/locale/br_FR/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Breton\n" "Language: br_FR\n" @@ -79,7 +79,7 @@ msgstr "Doare korf" msgid "Format" msgstr "Stumm" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "eilenn {title}" diff --git a/src/backend/locale/de_DE/LC_MESSAGES/django.po b/src/backend/locale/de_DE/LC_MESSAGES/django.po index 8edff31665..7215f3d124 100644 --- a/src/backend/locale/de_DE/LC_MESSAGES/django.po +++ b/src/backend/locale/de_DE/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: German\n" "Language: de_DE\n" @@ -79,7 +79,7 @@ msgstr "Typ" msgid "Format" msgstr "Format" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "Kopie von {title}" diff --git a/src/backend/locale/en_US/LC_MESSAGES/django.po b/src/backend/locale/en_US/LC_MESSAGES/django.po index 8ff076d118..90f6c72c5e 100644 --- a/src/backend/locale/en_US/LC_MESSAGES/django.po +++ b/src/backend/locale/en_US/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: English\n" "Language: en_US\n" @@ -79,7 +79,7 @@ msgstr "" msgid "Format" msgstr "" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "" diff --git a/src/backend/locale/es_ES/LC_MESSAGES/django.po b/src/backend/locale/es_ES/LC_MESSAGES/django.po index 828a1b41ab..c4f38cad08 100644 --- a/src/backend/locale/es_ES/LC_MESSAGES/django.po +++ b/src/backend/locale/es_ES/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Spanish\n" "Language: es_ES\n" @@ -79,7 +79,7 @@ msgstr "Tipo de Cuerpo" msgid "Format" msgstr "Formato" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "copia de {title}" diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.po b/src/backend/locale/fr_FR/LC_MESSAGES/django.po index af9e20d654..a0d47f414a 100644 --- a/src/backend/locale/fr_FR/LC_MESSAGES/django.po +++ b/src/backend/locale/fr_FR/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: French\n" "Language: fr_FR\n" @@ -79,7 +79,7 @@ msgstr "Type de corps" msgid "Format" msgstr "Format" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "copie de {title}" diff --git a/src/backend/locale/it_IT/LC_MESSAGES/django.po b/src/backend/locale/it_IT/LC_MESSAGES/django.po index 47dd1dba60..10f4a41abc 100644 --- a/src/backend/locale/it_IT/LC_MESSAGES/django.po +++ b/src/backend/locale/it_IT/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Italian\n" "Language: it_IT\n" @@ -79,7 +79,7 @@ msgstr "" msgid "Format" msgstr "Formato" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "copia di {title}" diff --git a/src/backend/locale/nl_NL/LC_MESSAGES/django.po b/src/backend/locale/nl_NL/LC_MESSAGES/django.po index d1ebbae35d..b52b0a61d5 100644 --- a/src/backend/locale/nl_NL/LC_MESSAGES/django.po +++ b/src/backend/locale/nl_NL/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Dutch\n" "Language: nl_NL\n" @@ -79,7 +79,7 @@ msgstr "Text type" msgid "Format" msgstr "Formaat" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "kopie van {title}" diff --git a/src/backend/locale/pt_PT/LC_MESSAGES/django.po b/src/backend/locale/pt_PT/LC_MESSAGES/django.po index fd65edc63c..8a99781550 100644 --- a/src/backend/locale/pt_PT/LC_MESSAGES/django.po +++ b/src/backend/locale/pt_PT/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Portuguese\n" "Language: pt_PT\n" @@ -79,7 +79,7 @@ msgstr "Tipo de corpo" msgid "Format" msgstr "Formato" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "cópia de {title}" diff --git a/src/backend/locale/ru_RU/LC_MESSAGES/django.po b/src/backend/locale/ru_RU/LC_MESSAGES/django.po index eb5a1c2103..8853268c60 100644 --- a/src/backend/locale/ru_RU/LC_MESSAGES/django.po +++ b/src/backend/locale/ru_RU/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Russian\n" "Language: ru_RU\n" @@ -79,7 +79,7 @@ msgstr "Тип сообщения" msgid "Format" msgstr "Формат" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "копия {title}" diff --git a/src/backend/locale/sl_SI/LC_MESSAGES/django.po b/src/backend/locale/sl_SI/LC_MESSAGES/django.po index 5ee111c665..a7e7e2b634 100644 --- a/src/backend/locale/sl_SI/LC_MESSAGES/django.po +++ b/src/backend/locale/sl_SI/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Slovenian\n" "Language: sl_SI\n" @@ -79,7 +79,7 @@ msgstr "Vrsta telesa" msgid "Format" msgstr "Oblika" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "" diff --git a/src/backend/locale/sv_SE/LC_MESSAGES/django.po b/src/backend/locale/sv_SE/LC_MESSAGES/django.po index 51d8cc6fd0..b6c7a980b4 100644 --- a/src/backend/locale/sv_SE/LC_MESSAGES/django.po +++ b/src/backend/locale/sv_SE/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Swedish\n" "Language: sv_SE\n" @@ -79,7 +79,7 @@ msgstr "" msgid "Format" msgstr "Format" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "" diff --git a/src/backend/locale/tr_TR/LC_MESSAGES/django.po b/src/backend/locale/tr_TR/LC_MESSAGES/django.po index cdf4bd5d8e..1e179aee97 100644 --- a/src/backend/locale/tr_TR/LC_MESSAGES/django.po +++ b/src/backend/locale/tr_TR/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Turkish\n" "Language: tr_TR\n" @@ -79,7 +79,7 @@ msgstr "" msgid "Format" msgstr "" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "" diff --git a/src/backend/locale/uk_UA/LC_MESSAGES/django.po b/src/backend/locale/uk_UA/LC_MESSAGES/django.po index 20128a23ff..b2c08c8d1e 100644 --- a/src/backend/locale/uk_UA/LC_MESSAGES/django.po +++ b/src/backend/locale/uk_UA/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Ukrainian\n" "Language: uk_UA\n" @@ -79,7 +79,7 @@ msgstr "Тип вмісту" msgid "Format" msgstr "Формат" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "копія {title}" diff --git a/src/backend/locale/zh_CN/LC_MESSAGES/django.po b/src/backend/locale/zh_CN/LC_MESSAGES/django.po index 7e140f51a3..d5190b5d64 100644 --- a/src/backend/locale/zh_CN/LC_MESSAGES/django.po +++ b/src/backend/locale/zh_CN/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Chinese Simplified\n" "Language: zh_CN\n" @@ -79,7 +79,7 @@ msgstr "正文类型" msgid "Format" msgstr "格式" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "{title} 的副本" diff --git a/src/mail/mjml/user_template.mjml b/src/mail/mjml/user_template.mjml new file mode 100644 index 0000000000..2dfd46ac3f --- /dev/null +++ b/src/mail/mjml/user_template.mjml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + +

{{title|capfirst}}

+
+ + + {{message_bold|capfirst}} + + + {{message|capfirst}} + {{link_label}} + + + {{button_label}} + + + + {% blocktrans %} + Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. + {% endblocktrans %} + + + +

+ {% blocktrans %} + Brought to you by {{brandname}} + {% endblocktrans %} +

+
+
+
+
+
+
From a5111809bb402d40fbc0edcdc06cc54a604f7837 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Thu, 15 Jan 2026 16:30:47 +0100 Subject: [PATCH 02/11] =?UTF-8?q?=E2=9C=A8(frontend)=20add=20email=20valid?= =?UTF-8?q?ation=20pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/UserReconciliation.test.tsx | 62 +++++++++++ .../impress/src/features/auth/api/index.ts | 1 + .../auth/api/useUserReconciliations.tsx | 51 +++++++++ .../src/features/auth/assets/rocket.gif | Bin 0 -> 66119 bytes .../auth/components/UserReconciliation.tsx | 104 ++++++++++++++++++ .../src/features/auth/components/index.ts | 1 + .../active/[id]/index.tsx | 40 +++++++ .../inactive/[id]/index.tsx | 40 +++++++ src/frontend/apps/impress/vitest.setup.ts | 19 ++++ 9 files changed, 318 insertions(+) create mode 100644 src/frontend/apps/impress/src/features/auth/__tests__/UserReconciliation.test.tsx create mode 100644 src/frontend/apps/impress/src/features/auth/api/useUserReconciliations.tsx create mode 100644 src/frontend/apps/impress/src/features/auth/assets/rocket.gif create mode 100644 src/frontend/apps/impress/src/features/auth/components/UserReconciliation.tsx create mode 100644 src/frontend/apps/impress/src/pages/user_reconciliations/active/[id]/index.tsx create mode 100644 src/frontend/apps/impress/src/pages/user_reconciliations/inactive/[id]/index.tsx diff --git a/src/frontend/apps/impress/src/features/auth/__tests__/UserReconciliation.test.tsx b/src/frontend/apps/impress/src/features/auth/__tests__/UserReconciliation.test.tsx new file mode 100644 index 0000000000..4f2d39597a --- /dev/null +++ b/src/frontend/apps/impress/src/features/auth/__tests__/UserReconciliation.test.tsx @@ -0,0 +1,62 @@ +import { render, screen, waitFor } from '@testing-library/react'; +import fetchMock from 'fetch-mock'; +import React from 'react'; +import { describe, expect, test } from 'vitest'; + +import { AppWrapper } from '@/tests/utils'; + +import { UserReconciliation } from '../components/UserReconciliation'; + +describe('UserReconciliation', () => { + beforeEach(() => { + fetchMock.reset(); + }); + + ['active', 'inactive'].forEach((type) => { + test(`renders when reconciliation is a ${type} success`, async () => { + fetchMock.get( + `http://test.jest/api/v1.0/user_reconciliations/${type}/123456/`, + { details: 'Success' }, + ); + + render( + , + { + wrapper: AppWrapper, + }, + ); + + await waitFor(() => { + expect(fetchMock.calls().length).toBe(1); + }); + + expect( + await screen.findByText(/Email validated successfully !/i), + ).toBeInTheDocument(); + }); + }); + + test('renders when reconciliation fails', async () => { + fetchMock.get( + `http://test.jest/api/v1.0/user_reconciliations/active/invalid-id/`, + { + throws: new Error('invalid id'), + }, + ); + + render(, { + wrapper: AppWrapper, + }); + + await waitFor(() => { + expect(fetchMock.calls().length).toBe(1); + }); + + expect( + await screen.findByText(/An error occurred during email validation./i), + ).toBeInTheDocument(); + }); +}); diff --git a/src/frontend/apps/impress/src/features/auth/api/index.ts b/src/frontend/apps/impress/src/features/auth/api/index.ts index ce8db5d434..a07760a0d0 100644 --- a/src/frontend/apps/impress/src/features/auth/api/index.ts +++ b/src/frontend/apps/impress/src/features/auth/api/index.ts @@ -1,2 +1,3 @@ export * from './useAuthQuery'; +export * from './useUserReconciliations'; export * from './types'; diff --git a/src/frontend/apps/impress/src/features/auth/api/useUserReconciliations.tsx b/src/frontend/apps/impress/src/features/auth/api/useUserReconciliations.tsx new file mode 100644 index 0000000000..9185563db9 --- /dev/null +++ b/src/frontend/apps/impress/src/features/auth/api/useUserReconciliations.tsx @@ -0,0 +1,51 @@ +import { UseQueryOptions, useQuery } from '@tanstack/react-query'; + +import { APIError, errorCauses, fetchAPI } from '@/api'; + +type UserReconciliationResponse = { + details: string; +}; + +interface UserReconciliationProps { + type: 'active' | 'inactive'; + reconciliationId?: string; +} + +export const userReconciliations = async ({ + type, + reconciliationId, +}: UserReconciliationProps): Promise => { + const response = await fetchAPI( + `user_reconciliations/${type}/${reconciliationId}/`, + ); + + if (!response.ok) { + throw new APIError( + 'Failed to do the user reconciliation', + await errorCauses(response), + ); + } + + return response.json() as Promise; +}; + +export const KEY_USER_RECONCILIATIONS = 'user_reconciliations'; + +export function useUserReconciliationsQuery( + param: UserReconciliationProps, + queryConfig?: UseQueryOptions< + UserReconciliationResponse, + APIError, + UserReconciliationResponse + >, +) { + return useQuery< + UserReconciliationResponse, + APIError, + UserReconciliationResponse + >({ + queryKey: [KEY_USER_RECONCILIATIONS, param], + queryFn: () => userReconciliations(param), + ...queryConfig, + }); +} diff --git a/src/frontend/apps/impress/src/features/auth/assets/rocket.gif b/src/frontend/apps/impress/src/features/auth/assets/rocket.gif new file mode 100644 index 0000000000000000000000000000000000000000..d0f183267f1ffcbd45e5c0ad04136e85a1acf918 GIT binary patch literal 66119 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 b3En;McX=L>SQz&2Myd9>@CABwWa<9_oPkp{ literal 0 HcmV?d00001 diff --git a/src/frontend/apps/impress/src/features/auth/components/UserReconciliation.tsx b/src/frontend/apps/impress/src/features/auth/components/UserReconciliation.tsx new file mode 100644 index 0000000000..40a1e5e146 --- /dev/null +++ b/src/frontend/apps/impress/src/features/auth/components/UserReconciliation.tsx @@ -0,0 +1,104 @@ +import { Button } from '@gouvfr-lasuite/cunningham-react'; +import Image from 'next/image'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +import error_img from '@/assets/icons/error-planetes.png'; +import { Box, Icon, Loading, StyledLink, Text } from '@/components'; + +import { useUserReconciliationsQuery } from '../api'; +import success_gif from '../assets/rocket.gif'; + +const StyledButton = styled(Button)` + width: fit-content; +`; + +interface UserReconciliationProps { + type: 'active' | 'inactive'; + reconciliationId: string; +} + +export const UserReconciliation = ({ + type, + reconciliationId, +}: UserReconciliationProps) => { + const { t } = useTranslation(); + const { data: userReconciliations, isError } = useUserReconciliationsQuery({ + type, + reconciliationId, + }); + + if (!userReconciliations && !isError) { + return ( + + ); + } + + let render = ( + <> + + + {t('Email validated successfully !')} + + + ); + + if (isError) { + render = ( + <> + + + {t('An error occurred during email validation.')} + + + ); + } + + return ( + + {render} + + + + + } + > + {t('Home')} + + + + + ); +}; diff --git a/src/frontend/apps/impress/src/features/auth/components/index.ts b/src/frontend/apps/impress/src/features/auth/components/index.ts index 26ebaf2e8b..0a91bf374a 100644 --- a/src/frontend/apps/impress/src/features/auth/components/index.ts +++ b/src/frontend/apps/impress/src/features/auth/components/index.ts @@ -1,3 +1,4 @@ export * from './Auth'; export * from './ButtonLogin'; export * from './UserAvatar'; +export * from './UserReconciliation'; diff --git a/src/frontend/apps/impress/src/pages/user_reconciliations/active/[id]/index.tsx b/src/frontend/apps/impress/src/pages/user_reconciliations/active/[id]/index.tsx new file mode 100644 index 0000000000..7fa8c27a21 --- /dev/null +++ b/src/frontend/apps/impress/src/pages/user_reconciliations/active/[id]/index.tsx @@ -0,0 +1,40 @@ +import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { ReactElement } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { UserReconciliation } from '@/features/auth/components/UserReconciliation'; +import { PageLayout } from '@/layouts'; +import { NextPageWithLayout } from '@/types/next'; + +const Page: NextPageWithLayout = () => { + const { t } = useTranslation(); + const { + query: { id }, + } = useRouter(); + + if (typeof id !== 'string') { + return null; + } + + return ( + <> + + + {`${t('User reconciliation')} - ${t('Docs')}`} + + + + + ); +}; + +Page.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default Page; diff --git a/src/frontend/apps/impress/src/pages/user_reconciliations/inactive/[id]/index.tsx b/src/frontend/apps/impress/src/pages/user_reconciliations/inactive/[id]/index.tsx new file mode 100644 index 0000000000..c10cbb38e7 --- /dev/null +++ b/src/frontend/apps/impress/src/pages/user_reconciliations/inactive/[id]/index.tsx @@ -0,0 +1,40 @@ +import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { ReactElement } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { UserReconciliation } from '@/features/auth/components/UserReconciliation'; +import { PageLayout } from '@/layouts'; +import { NextPageWithLayout } from '@/types/next'; + +const Page: NextPageWithLayout = () => { + const { t } = useTranslation(); + const { + query: { id }, + } = useRouter(); + + if (typeof id !== 'string') { + return null; + } + + return ( + <> + + + {`${t('User reconciliation')} - ${t('Docs')}`} + + + + + ); +}; + +Page.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default Page; diff --git a/src/frontend/apps/impress/vitest.setup.ts b/src/frontend/apps/impress/vitest.setup.ts index de0945cfe7..6e449e8b75 100644 --- a/src/frontend/apps/impress/vitest.setup.ts +++ b/src/frontend/apps/impress/vitest.setup.ts @@ -1,4 +1,23 @@ import '@testing-library/jest-dom/vitest'; import * as dotenv from 'dotenv'; +import React from 'react'; +import { vi } from 'vitest'; dotenv.config({ path: './.env.test', quiet: true }); + +vi.mock('next/image', () => ({ + __esModule: true, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + default: (props: any) => { + const { + src, + alt = '', + unoptimized: _unoptimized, + priority: _priority, + ...rest + } = props; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const resolved = typeof src === 'string' ? src : src?.src; + return React.createElement('img', { src: resolved, alt, ...rest }); + }, +})); From 032413a9c34779902dde1c4a3fa08cd39b587d38 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Thu, 15 Jan 2026 16:31:39 +0100 Subject: [PATCH 03/11] fix --- src/backend/core/api/viewsets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index a03e3f17b1..789345204e 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -264,7 +264,7 @@ def get(self, request, user_type, confirmation_id): """ try: # validate UUID - uuid_obj = uuid.UUID(confirmation_id) + uuid_obj = uuid.UUID(str(confirmation_id)) except ValueError: return Response( {"detail": "Badly formatted confirmation id"}, From dc8db608765e4113edf6fc3747b0889ffc6c576c Mon Sep 17 00:00:00 2001 From: Sylvain Boissel Date: Tue, 27 Jan 2026 18:13:30 +0100 Subject: [PATCH 04/11] =?UTF-8?q?=E2=9C=A8(backend)=20reconciliation=20req?= =?UTF-8?q?uests:=20use=20a=20source=20unique=20id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changes the way the reconciliation requests CSV imports are processed and requires the CSV to provide a unique id for each request. Rows with errors are also now handled better so that they don't fail the whole import. --- docs/user_account_reconciliation.md | 2 +- src/backend/core/admin.py | 4 +- src/backend/core/api/viewsets.py | 4 +- ...onciliationcsvimport_userreconciliation.py | 20 +++- src/backend/core/models.py | 30 +++-- src/backend/core/tasks/user_reconciliation.py | 111 +++++++++++------- .../data/example_reconciliation_basic.csv | 12 +- .../data/example_reconciliation_error.csv | 4 +- .../example_reconciliation_grist_form.csv | 10 +- ...xample_reconciliation_grist_form_error.csv | 4 +- .../example_reconciliation_missing_column.csv | 6 + .../tests/test_models_user_reconciliation.py | 88 ++++++++++++-- 12 files changed, 206 insertions(+), 89 deletions(-) create mode 100644 src/backend/core/tests/data/example_reconciliation_missing_column.csv diff --git a/docs/user_account_reconciliation.md b/docs/user_account_reconciliation.md index 59c6280f2a..dce16c0ae3 100644 --- a/docs/user_account_reconciliation.md +++ b/docs/user_account_reconciliation.md @@ -9,7 +9,7 @@ The CSV must contain the following mandatory columns: - `active_email`: the email of the user that will remain active after the process. - `inactive_email`: the email of the user(s) that will be merged into the active user. It is possible to indicate several emails, so the user only has to make one request even if they have more than two accounts. -- `status`: the value must be `pending`. Rows with other values will be ignored. +- `id`: a unique row id, so that entries already processed in a previous import are ignored. The following columns are optional: `active_email_checked` and `inactive_email_checked` (both must contain `0` (False) or `1` (True), and both default to False.) If present, it allows to indicate that the source form has a way to validate that the user making the request actually controls the email addresses, skipping the need to send confirmation emails (cf. below) diff --git a/src/backend/core/admin.py b/src/backend/core/admin.py index 4f1d1dff88..40bf32ec65 100644 --- a/src/backend/core/admin.py +++ b/src/backend/core/admin.py @@ -110,7 +110,7 @@ class UserAdmin(auth_admin.UserAdmin): class UserReconciliationCsvImportAdmin(admin.ModelAdmin): """Admin class for UserReconciliationCsvImport model.""" - list_display = ("id", "created_at", "status") + list_display = ("id", "__str__", "created_at", "status") def save_model(self, request, obj, form, change): """Override save_model to trigger the import task on creation.""" @@ -167,7 +167,7 @@ def process_reconciliation(_modeladmin, _request, queryset): class UserReconciliationAdmin(admin.ModelAdmin): """Admin class for UserReconciliation model.""" - list_display = ["id", "created_at", "status"] + list_display = ["id", "__str__", "created_at", "status"] actions = [process_reconciliation] diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 789345204e..66b4021b33 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -277,9 +277,9 @@ def get(self, request, user_type, confirmation_id): ) lookup = ( - {"active_confirmation_id": uuid_obj} + {"active_email_confirmation_id": uuid_obj} if user_type == "active" - else {"inactive_confirmation_id": uuid_obj} + else {"inactive_email_confirmation_id": uuid_obj} ) try: diff --git a/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py b/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py index fed96eac62..21a3a2862b 100644 --- a/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py +++ b/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.9 on 2026-01-06 18:46 +# Generated by Django 5.2.10 on 2026-01-27 15:50 import uuid @@ -43,7 +43,10 @@ class Migration(migrations.Migration): verbose_name="updated on", ), ), - ("file", models.FileField(upload_to="imports/")), + ( + "file", + models.FileField(upload_to="imports/", verbose_name="CSV file"), + ), ( "status", models.CharField( @@ -109,17 +112,26 @@ class Migration(migrations.Migration): ("active_email_checked", models.BooleanField(default=False)), ("inactive_email_checked", models.BooleanField(default=False)), ( - "active_confirmation_id", + "active_email_confirmation_id", models.UUIDField( default=uuid.uuid4, editable=False, null=True, unique=True ), ), ( - "inactive_confirmation_id", + "inactive_email_confirmation_id", models.UUIDField( default=uuid.uuid4, editable=False, null=True, unique=True ), ), + ( + "source_unique_id", + models.CharField( + blank=True, + max_length=100, + null=True, + verbose_name="Unique ID in the source file", + ), + ), ( "status", models.CharField( diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 637c6d4f3e..e327a8c86c 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -318,12 +318,18 @@ class UserReconciliation(BaseModel): blank=True, related_name="inactive_user", ) - active_confirmation_id = models.UUIDField( + active_email_confirmation_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, null=True ) - inactive_confirmation_id = models.UUIDField( + inactive_email_confirmation_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, null=True ) + source_unique_id = models.CharField( + max_length=100, + blank=True, + null=True, + verbose_name=_("Unique ID in the source file"), + ) status = models.CharField( max_length=20, @@ -356,11 +362,13 @@ def save(self, *args, **kwargs): if self.active_user and self.inactive_user: if not self.active_email_checked: self.send_reconciliation_confirm_email( - self.active_user, "active", self.active_confirmation_id + self.active_user, "active", self.active_email_confirmation_id ) if not self.inactive_email_checked: self.send_reconciliation_confirm_email( - self.inactive_user, "inactive", self.inactive_confirmation_id + self.inactive_user, + "inactive", + self.inactive_email_confirmation_id, ) self.status = "ready" else: @@ -456,7 +464,7 @@ class UserReconciliationCsvImport(BaseModel): """Model to import reconciliations requests from an external source (eg, )""" - file = models.FileField(upload_to="imports/") + file = models.FileField(upload_to="imports/", verbose_name=_("CSV file")) status = models.CharField( max_length=20, choices=[ @@ -506,12 +514,14 @@ def send_email(self, subject, emails, context=None, language=None): except smtplib.SMTPException as exception: logger.error("invitation to %s was not sent: %s", emails, exception) - def send_reconciliation_error_email(self, email_1, email_2, language=None): + def send_reconciliation_error_email( + self, recipient_email, other_email, language=None + ): """Method allowing to send email for reconciliation requests with errors.""" language = language or get_language() domain = Site.objects.get_current().domain - emails = [email_1, email_2] + emails = [recipient_email] with override(language): subject = _("Reconciliation of your Docs accounts not completed") @@ -521,14 +531,14 @@ def send_reconciliation_error_email(self, email_1, email_2, language=None): "message": _( """Reconciliation failed for the following email addresses: - - {email1} - - {email2} + - {recipient_email} + - {other_email} Please check for typos. You can submit another request with the valid email addresses. """ - ).format(email1=email_1, email2=email_2), + ).format(recipient_email=recipient_email, other_email=other_email), "link": f"{domain}/", "link_label": str(_("Click here")), "button_label": str(_("Make a new request")), diff --git a/src/backend/core/tasks/user_reconciliation.py b/src/backend/core/tasks/user_reconciliation.py index 020ca5427c..7a3b517466 100644 --- a/src/backend/core/tasks/user_reconciliation.py +++ b/src/backend/core/tasks/user_reconciliation.py @@ -23,76 +23,103 @@ def user_reconciliation_csv_import_job(job_id): Does some sanity checks on the data: - active_email and inactive_email must be valid email addresses - active_email and inactive_email cannot be the same + + Rows with errors are logged in the job logs and skipped, but do not cause + the entire job to fail or prevent the next rows from being processed. """ # Imports the CSV file, breaks it into UserReconciliation items job = UserReconciliationCsvImport.objects.get(id=job_id) job.status = "running" job.save() + rec_entries_created = 0 + rows_with_errors = 0 + already_processed_source_ids = 0 + try: with job.file.open(mode="r") as f: reader = csv.DictReader(f) - rec_entries_created = 0 + + if not {"active_email", "inactive_email", "id"}.issubset(reader.fieldnames): + raise KeyError( + "CSV is missing mandatory columns: active_email, inactive_email, id" + ) + for row in reader: - status = row["status"] + source_unique_id = row["id"].strip() + + # Skip entries if they already exist with this source_unique_id + if UserReconciliation.objects.filter( + source_unique_id=source_unique_id + ).exists(): + already_processed_source_ids += 1 + continue + + active_email_checked = row.get("active_email_checked", "0") == "1" + inactive_email_checked = row.get("inactive_email_checked", "0") == "1" - if status == "pending": - active_email_checked = row.get("active_email_checked", "0") == "1" - inactive_email_checked = ( - row.get("inactive_email_checked", "0") == "1" + active_email = row["active_email"] + inactive_emails = row["inactive_email"].split("|") + try: + validate_email(active_email) + except ValidationError: + job.send_reconciliation_error_email( + recipient_email=inactive_emails[0], other_email=active_email ) + job.logs += ( + f"Invalid active email address on row {source_unique_id}." + ) + rows_with_errors += 1 + continue - active_email = row["active_email"] - inactive_emails = row["inactive_email"].split("|") + for inactive_email in inactive_emails: try: - validate_email(active_email) - except ValidationError as e: + validate_email(inactive_email) + except (ValidationError, ValueError): job.send_reconciliation_error_email( - active_email, inactive_emails[0] + recipient_email=active_email, other_email=inactive_email ) - job.status = "error" - job.logs = f"{e!s}\n{traceback.format_exc()}" - - for inactive_email in inactive_emails: - try: - validate_email(inactive_email) - except ValidationError as e: - job.send_reconciliation_error_email( - active_email, inactive_email - ) - job.status = "error" - job.logs = f"{e!s}\n{traceback.format_exc()}" - if inactive_email == active_email: - raise ValueError( - "Active and inactive emails cannot be the same." - ) - - rec_entry = UserReconciliation.objects.create( - active_email=active_email, - inactive_email=inactive_email, - active_email_checked=active_email_checked, - inactive_email_checked=inactive_email_checked, - active_confirmation_id=uuid.uuid4(), - inactive_confirmation_id=uuid.uuid4(), - status="pending", + job.logs += f"Invalid inactive email address on row {source_unique_id}.\n" + rows_with_errors += 1 + continue + if inactive_email == active_email: + job.logs += ( + f"Error on row {source_unique_id}: " + f"{active_email} set as both active and inactive email.\n" ) - rec_entry.save() - rec_entries_created += 1 + rows_with_errors += 1 + continue + + _rec_entry = UserReconciliation.objects.create( + active_email=active_email, + inactive_email=inactive_email, + active_email_checked=active_email_checked, + inactive_email_checked=inactive_email_checked, + active_email_confirmation_id=uuid.uuid4(), + inactive_email_confirmation_id=uuid.uuid4(), + source_unique_id=source_unique_id, + status="pending", + ) + rec_entries_created += 1 job.status = "done" - job.logs = f"""Import completed successfully. {reader.line_num} rows processed. - {rec_entries_created} reconciliation entries created.""" + job.logs += ( + f"Import completed successfully. {reader.line_num} rows processed." + f"{rec_entries_created} reconciliation entries created." + f" {already_processed_source_ids} rows were already processed." + f"{rows_with_errors} rows had errors." + ) except ( csv.Error, KeyError, - ValueError, ValidationError, + ValueError, IntegrityError, OSError, ClientError, ) as e: # Catch expected I/O/CSV/model errors and record traceback in logs for debugging job.status = "error" - job.logs = f"{e!s}\n{traceback.format_exc()}" + job.logs += f"{e!s}\n{traceback.format_exc()}" finally: job.save() diff --git a/src/backend/core/tests/data/example_reconciliation_basic.csv b/src/backend/core/tests/data/example_reconciliation_basic.csv index 2f3450d712..7e9f7fd727 100644 --- a/src/backend/core/tests/data/example_reconciliation_basic.csv +++ b/src/backend/core/tests/data/example_reconciliation_basic.csv @@ -1,6 +1,6 @@ -active_email,inactive_email,active_email_checked,inactive_email_checked,status -"user.test40@example.com","user.test41@example.com",0,0,pending -"user.test42@example.com","user.test43@example.com",0,1,pending -"user.test44@example.com","user.test45@example.com",1,0,pending -"user.test46@example.com","user.test47@example.com",1,1,pending -"user.test48@example.com","user.test49@example.com",1,1,pending \ No newline at end of file +active_email,inactive_email,active_email_checked,inactive_email_checked,status,id +"user.test40@example.com","user.test41@example.com",0,0,pending,1 +"user.test42@example.com","user.test43@example.com",0,1,pending,2 +"user.test44@example.com","user.test45@example.com",1,0,pending,3 +"user.test46@example.com","user.test47@example.com",1,1,pending,4 +"user.test48@example.com","user.test49@example.com",1,1,pending,5 \ No newline at end of file diff --git a/src/backend/core/tests/data/example_reconciliation_error.csv b/src/backend/core/tests/data/example_reconciliation_error.csv index 6f8d71abc9..2ac7939616 100644 --- a/src/backend/core/tests/data/example_reconciliation_error.csv +++ b/src/backend/core/tests/data/example_reconciliation_error.csv @@ -1,2 +1,2 @@ -active_email,inactive_email,active_email_checked,inactive_email_checked,status -"user.test40@example.com",,0,0,pending +active_email,inactive_email,active_email_checked,inactive_email_checked,status,id +"user.test40@example.com",,0,0,pending,40 diff --git a/src/backend/core/tests/data/example_reconciliation_grist_form.csv b/src/backend/core/tests/data/example_reconciliation_grist_form.csv index d2b76bcb67..b92fe03679 100644 --- a/src/backend/core/tests/data/example_reconciliation_grist_form.csv +++ b/src/backend/core/tests/data/example_reconciliation_grist_form.csv @@ -1,5 +1,5 @@ -merge_accept,active_email,inactive_email,status -true,user.test10@example.com,user.test11@example.com|user.test12@example.com,pending -true,user.test30@example.com,user.test31@example.com|user.test32@example.com|user.test33@example.com|user.test34@example.com|user.test35@example.com,pending -true,user.test20@example.com,user.test21@example.com,pending -true,user.test22@example.com,user.test23@example.com,pending +merge_accept,active_email,inactive_email,status,id +true,user.test10@example.com,user.test11@example.com|user.test12@example.com,pending,10 +true,user.test30@example.com,user.test31@example.com|user.test32@example.com|user.test33@example.com|user.test34@example.com|user.test35@example.com,pending,11 +true,user.test20@example.com,user.test21@example.com,pending,12 +true,user.test22@example.com,user.test23@example.com,pending,13 \ No newline at end of file diff --git a/src/backend/core/tests/data/example_reconciliation_grist_form_error.csv b/src/backend/core/tests/data/example_reconciliation_grist_form_error.csv index 934816f4b6..86d92ca31b 100644 --- a/src/backend/core/tests/data/example_reconciliation_grist_form_error.csv +++ b/src/backend/core/tests/data/example_reconciliation_grist_form_error.csv @@ -1,2 +1,2 @@ -merge_accept,active_email,inactive_email,status -true,user.test20@example.com,user.test20@example.com,pending +merge_accept,active_email,inactive_email,status,id +true,user.test20@example.com,user.test20@example.com,pending,20 diff --git a/src/backend/core/tests/data/example_reconciliation_missing_column.csv b/src/backend/core/tests/data/example_reconciliation_missing_column.csv new file mode 100644 index 0000000000..2f3450d712 --- /dev/null +++ b/src/backend/core/tests/data/example_reconciliation_missing_column.csv @@ -0,0 +1,6 @@ +active_email,inactive_email,active_email_checked,inactive_email_checked,status +"user.test40@example.com","user.test41@example.com",0,0,pending +"user.test42@example.com","user.test43@example.com",0,1,pending +"user.test44@example.com","user.test45@example.com",1,0,pending +"user.test46@example.com","user.test47@example.com",1,1,pending +"user.test48@example.com","user.test49@example.com",1,1,pending \ No newline at end of file diff --git a/src/backend/core/tests/test_models_user_reconciliation.py b/src/backend/core/tests/test_models_user_reconciliation.py index 3d74ee5a5e..1e8f65bc51 100644 --- a/src/backend/core/tests/test_models_user_reconciliation.py +++ b/src/backend/core/tests/test_models_user_reconciliation.py @@ -76,6 +76,30 @@ def test_user_reconciliation_csv_import_entry_is_created_grist_form( def test_incorrect_csv_format_handling(): + """Test that an incorrectly formatted CSV file is handled gracefully.""" + example_csv_path = ( + Path(__file__).parent / "data/example_reconciliation_missing_column.csv" + ) + with open(example_csv_path, "rb") as f: + csv_file = ContentFile( + f.read(), name="example_reconciliation_missing_column.csv" + ) + csv_import = models.UserReconciliationCsvImport(file=csv_file) + csv_import.save() + + assert csv_import.status == "pending" + + user_reconciliation_csv_import_job(csv_import.id) + csv_import.refresh_from_db() + + assert ( + "CSV is missing mandatory columns: active_email, inactive_email, id" + in csv_import.logs + ) + assert csv_import.status == "error" + + +def test_incorrect_email_format_handling(): """Test that an incorrectly formatted CSV file is handled gracefully.""" example_csv_path = Path(__file__).parent / "data/example_reconciliation_error.csv" with open(example_csv_path, "rb") as f: @@ -88,8 +112,8 @@ def test_incorrect_csv_format_handling(): user_reconciliation_csv_import_job(csv_import.id) csv_import.refresh_from_db() - assert "This field cannot be blank" in csv_import.logs - assert csv_import.status == "error" + assert "Invalid inactive email address on row 40" in csv_import.logs + assert csv_import.status == "done" # pylint: disable-next=no-member assert len(mail.outbox) == 1 @@ -97,7 +121,7 @@ def test_incorrect_csv_format_handling(): # pylint: disable-next=no-member email = mail.outbox[0] - assert email.to == ["user.test40@example.com", ""] + assert email.to == ["user.test40@example.com"] email_content = " ".join(email.body.split()) assert "Reconciliation of your Docs accounts not completed" in email_content @@ -120,8 +144,11 @@ def test_incorrect_csv_data_handling_grist_form(): user_reconciliation_csv_import_job(csv_import.id) csv_import.refresh_from_db() - assert "Active and inactive emails cannot be the same." in csv_import.logs - assert csv_import.status == "error" + assert ( + "user.test20@example.com set as both active and inactive email" + in csv_import.logs + ) + assert csv_import.status == "done" def test_job_creates_reconciliation_entries(import_example_csv_basic): @@ -141,6 +168,37 @@ def test_job_creates_reconciliation_entries(import_example_csv_basic): assert reconciliations.count() == 5 +def test_job_does_not_create_duplicated_reconciliation_entries( + import_example_csv_basic, +): + """Test that the CSV import job doesn't create UserReconciliation entries + for source unique IDs that have already been processed.""" + + _already_created_entry = models.UserReconciliation.objects.create( + active_email="user.test40@example.com", + inactive_email="user.test41@example.com", + active_email_checked=0, + inactive_email_checked=0, + status="pending", + source_unique_id=1, + ) + + assert import_example_csv_basic.status == "pending" + user_reconciliation_csv_import_job(import_example_csv_basic.id) + + # Verify the job status changed + import_example_csv_basic.refresh_from_db() + assert import_example_csv_basic.status == "done" + assert "Import completed successfully." in import_example_csv_basic.logs + assert "6 rows processed." in import_example_csv_basic.logs + assert "4 reconciliation entries created." in import_example_csv_basic.logs + assert "1 rows were already processed." in import_example_csv_basic.logs + + # Verify the correct number of reconciliation entries were created + reconciliations = models.UserReconciliation.objects.all() + assert reconciliations.count() == 5 + + def test_job_creates_reconciliation_entries_grist_form(import_example_csv_grist_form): """Test that the CSV import job creates UserReconciliation entries.""" assert import_example_csv_grist_form.status == "pending" @@ -224,8 +282,8 @@ def test_user_reconciliation_is_created(user_reconciliation_users_and_docs): inactive_email=user_2.email, active_email_checked=False, inactive_email_checked=True, - active_confirmation_id=uuid.uuid4(), - inactive_confirmation_id=uuid.uuid4(), + active_email_confirmation_id=uuid.uuid4(), + inactive_email_confirmation_id=uuid.uuid4(), status="pending", ) @@ -243,8 +301,8 @@ def test_user_reconciliation_verification_emails_are_sent( inactive_email=user_2.email, active_email_checked=False, inactive_email_checked=False, - active_confirmation_id=uuid.uuid4(), - inactive_confirmation_id=uuid.uuid4(), + active_email_confirmation_id=uuid.uuid4(), + inactive_email_confirmation_id=uuid.uuid4(), status="pending", ) @@ -263,9 +321,12 @@ def test_user_reconciliation_verification_emails_are_sent( "You have requested a reconciliation of your user accounts on Docs." in email_1_content ) - active_confirmation_id = rec.active_confirmation_id - inactive_confirmation_id = rec.inactive_confirmation_id - assert f"user_reconciliations/active/{active_confirmation_id}/" in email_1_content + active_email_confirmation_id = rec.active_email_confirmation_id + inactive_email_confirmation_id = rec.inactive_email_confirmation_id + assert ( + f"user_reconciliations/active/{active_email_confirmation_id}/" + in email_1_content + ) # pylint: disable-next=no-member email_2 = mail.outbox[1] @@ -279,7 +340,8 @@ def test_user_reconciliation_verification_emails_are_sent( ) assert ( - f"user_reconciliations/inactive/{inactive_confirmation_id}/" in email_2_content + f"user_reconciliations/inactive/{inactive_email_confirmation_id}/" + in email_2_content ) From c1214410738b3e0a0cba975d5b46475441d4cdfc Mon Sep 17 00:00:00 2001 From: Sylvain Boissel Date: Tue, 27 Jan 2026 18:13:30 +0100 Subject: [PATCH 05/11] =?UTF-8?q?=E2=9C=A8(backend)=20reconciliation=20req?= =?UTF-8?q?uests:=20use=20a=20source=20unique=20id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changes the way the reconciliation requests CSV imports are processed and requires the CSV to provide a unique id for each request. Rows with errors are also now handled better so that they don't fail the whole import. --- docs/user_account_reconciliation.md | 2 +- src/backend/core/admin.py | 4 +- src/backend/core/api/viewsets.py | 4 +- ...remove_templateaccess_template_and_more.py | 26 ++ ...onciliationcsvimport_userreconciliation.py | 20 +- src/backend/core/models.py | 30 +- src/backend/core/services/mime_types.py | 8 + src/backend/core/tasks/user_reconciliation.py | 111 +++-- .../data/example_reconciliation_basic.csv | 12 +- .../data/example_reconciliation_error.csv | 4 +- .../example_reconciliation_grist_form.csv | 10 +- ...xample_reconciliation_grist_form_error.csv | 4 +- .../example_reconciliation_missing_column.csv | 6 + .../test_api_documents_create_with_file.py | 413 ++++++++++++++++++ .../tests/test_models_user_reconciliation.py | 88 +++- .../test_services_converter_orchestration.py | 93 ++++ .../tests/test_services_docspec_converter.py | 117 +++++ .../app-impress/assets/test_import.docx | Bin 0 -> 248980 bytes .../app-impress/assets/test_import.md | 60 +++ .../__tests__/app-impress/doc-import.spec.ts | 181 ++++++++ .../apps/impress/src/assets/icons/doc-all.svg | 20 + .../doc-management/api/useDocsFavorite.tsx | 72 +++ .../docs/docs-grid/api/useImportDoc.tsx | 125 ++++++ .../docs/docs-grid/hooks/useImport.tsx | 116 +++++ .../impress/templates/docspec_deployment.yaml | 108 +++++ src/helm/impress/templates/docspec_svc.yaml | 20 + 26 files changed, 1565 insertions(+), 89 deletions(-) create mode 100644 src/backend/core/migrations/0028_remove_templateaccess_template_and_more.py create mode 100644 src/backend/core/services/mime_types.py create mode 100644 src/backend/core/tests/data/example_reconciliation_missing_column.csv create mode 100644 src/backend/core/tests/documents/test_api_documents_create_with_file.py create mode 100644 src/backend/core/tests/test_services_converter_orchestration.py create mode 100644 src/backend/core/tests/test_services_docspec_converter.py create mode 100644 src/frontend/apps/e2e/__tests__/app-impress/assets/test_import.docx create mode 100644 src/frontend/apps/e2e/__tests__/app-impress/assets/test_import.md create mode 100644 src/frontend/apps/e2e/__tests__/app-impress/doc-import.spec.ts create mode 100644 src/frontend/apps/impress/src/assets/icons/doc-all.svg create mode 100644 src/frontend/apps/impress/src/features/docs/doc-management/api/useDocsFavorite.tsx create mode 100644 src/frontend/apps/impress/src/features/docs/docs-grid/api/useImportDoc.tsx create mode 100644 src/frontend/apps/impress/src/features/docs/docs-grid/hooks/useImport.tsx create mode 100644 src/helm/impress/templates/docspec_deployment.yaml create mode 100644 src/helm/impress/templates/docspec_svc.yaml diff --git a/docs/user_account_reconciliation.md b/docs/user_account_reconciliation.md index 59c6280f2a..dce16c0ae3 100644 --- a/docs/user_account_reconciliation.md +++ b/docs/user_account_reconciliation.md @@ -9,7 +9,7 @@ The CSV must contain the following mandatory columns: - `active_email`: the email of the user that will remain active after the process. - `inactive_email`: the email of the user(s) that will be merged into the active user. It is possible to indicate several emails, so the user only has to make one request even if they have more than two accounts. -- `status`: the value must be `pending`. Rows with other values will be ignored. +- `id`: a unique row id, so that entries already processed in a previous import are ignored. The following columns are optional: `active_email_checked` and `inactive_email_checked` (both must contain `0` (False) or `1` (True), and both default to False.) If present, it allows to indicate that the source form has a way to validate that the user making the request actually controls the email addresses, skipping the need to send confirmation emails (cf. below) diff --git a/src/backend/core/admin.py b/src/backend/core/admin.py index 4f1d1dff88..40bf32ec65 100644 --- a/src/backend/core/admin.py +++ b/src/backend/core/admin.py @@ -110,7 +110,7 @@ class UserAdmin(auth_admin.UserAdmin): class UserReconciliationCsvImportAdmin(admin.ModelAdmin): """Admin class for UserReconciliationCsvImport model.""" - list_display = ("id", "created_at", "status") + list_display = ("id", "__str__", "created_at", "status") def save_model(self, request, obj, form, change): """Override save_model to trigger the import task on creation.""" @@ -167,7 +167,7 @@ def process_reconciliation(_modeladmin, _request, queryset): class UserReconciliationAdmin(admin.ModelAdmin): """Admin class for UserReconciliation model.""" - list_display = ["id", "created_at", "status"] + list_display = ["id", "__str__", "created_at", "status"] actions = [process_reconciliation] diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 789345204e..66b4021b33 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -277,9 +277,9 @@ def get(self, request, user_type, confirmation_id): ) lookup = ( - {"active_confirmation_id": uuid_obj} + {"active_email_confirmation_id": uuid_obj} if user_type == "active" - else {"inactive_confirmation_id": uuid_obj} + else {"inactive_email_confirmation_id": uuid_obj} ) try: diff --git a/src/backend/core/migrations/0028_remove_templateaccess_template_and_more.py b/src/backend/core/migrations/0028_remove_templateaccess_template_and_more.py new file mode 100644 index 0000000000..5de5e370dc --- /dev/null +++ b/src/backend/core/migrations/0028_remove_templateaccess_template_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 5.2.9 on 2026-01-09 14:18 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0027_auto_20251120_0956"), + ] + + operations = [ + migrations.RemoveField( + model_name="templateaccess", + name="template", + ), + migrations.RemoveField( + model_name="templateaccess", + name="user", + ), + migrations.DeleteModel( + name="Template", + ), + migrations.DeleteModel( + name="TemplateAccess", + ), + ] diff --git a/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py b/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py index fed96eac62..21a3a2862b 100644 --- a/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py +++ b/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.9 on 2026-01-06 18:46 +# Generated by Django 5.2.10 on 2026-01-27 15:50 import uuid @@ -43,7 +43,10 @@ class Migration(migrations.Migration): verbose_name="updated on", ), ), - ("file", models.FileField(upload_to="imports/")), + ( + "file", + models.FileField(upload_to="imports/", verbose_name="CSV file"), + ), ( "status", models.CharField( @@ -109,17 +112,26 @@ class Migration(migrations.Migration): ("active_email_checked", models.BooleanField(default=False)), ("inactive_email_checked", models.BooleanField(default=False)), ( - "active_confirmation_id", + "active_email_confirmation_id", models.UUIDField( default=uuid.uuid4, editable=False, null=True, unique=True ), ), ( - "inactive_confirmation_id", + "inactive_email_confirmation_id", models.UUIDField( default=uuid.uuid4, editable=False, null=True, unique=True ), ), + ( + "source_unique_id", + models.CharField( + blank=True, + max_length=100, + null=True, + verbose_name="Unique ID in the source file", + ), + ), ( "status", models.CharField( diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 637c6d4f3e..e327a8c86c 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -318,12 +318,18 @@ class UserReconciliation(BaseModel): blank=True, related_name="inactive_user", ) - active_confirmation_id = models.UUIDField( + active_email_confirmation_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, null=True ) - inactive_confirmation_id = models.UUIDField( + inactive_email_confirmation_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, null=True ) + source_unique_id = models.CharField( + max_length=100, + blank=True, + null=True, + verbose_name=_("Unique ID in the source file"), + ) status = models.CharField( max_length=20, @@ -356,11 +362,13 @@ def save(self, *args, **kwargs): if self.active_user and self.inactive_user: if not self.active_email_checked: self.send_reconciliation_confirm_email( - self.active_user, "active", self.active_confirmation_id + self.active_user, "active", self.active_email_confirmation_id ) if not self.inactive_email_checked: self.send_reconciliation_confirm_email( - self.inactive_user, "inactive", self.inactive_confirmation_id + self.inactive_user, + "inactive", + self.inactive_email_confirmation_id, ) self.status = "ready" else: @@ -456,7 +464,7 @@ class UserReconciliationCsvImport(BaseModel): """Model to import reconciliations requests from an external source (eg, )""" - file = models.FileField(upload_to="imports/") + file = models.FileField(upload_to="imports/", verbose_name=_("CSV file")) status = models.CharField( max_length=20, choices=[ @@ -506,12 +514,14 @@ def send_email(self, subject, emails, context=None, language=None): except smtplib.SMTPException as exception: logger.error("invitation to %s was not sent: %s", emails, exception) - def send_reconciliation_error_email(self, email_1, email_2, language=None): + def send_reconciliation_error_email( + self, recipient_email, other_email, language=None + ): """Method allowing to send email for reconciliation requests with errors.""" language = language or get_language() domain = Site.objects.get_current().domain - emails = [email_1, email_2] + emails = [recipient_email] with override(language): subject = _("Reconciliation of your Docs accounts not completed") @@ -521,14 +531,14 @@ def send_reconciliation_error_email(self, email_1, email_2, language=None): "message": _( """Reconciliation failed for the following email addresses: - - {email1} - - {email2} + - {recipient_email} + - {other_email} Please check for typos. You can submit another request with the valid email addresses. """ - ).format(email1=email_1, email2=email_2), + ).format(recipient_email=recipient_email, other_email=other_email), "link": f"{domain}/", "link_label": str(_("Click here")), "button_label": str(_("Make a new request")), diff --git a/src/backend/core/services/mime_types.py b/src/backend/core/services/mime_types.py new file mode 100644 index 0000000000..ab0535a989 --- /dev/null +++ b/src/backend/core/services/mime_types.py @@ -0,0 +1,8 @@ +"""MIME type constants for document conversion.""" + +BLOCKNOTE = "application/vnd.blocknote+json" +YJS = "application/vnd.yjs.doc" +MARKDOWN = "text/markdown" +JSON = "application/json" +DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document" +HTML = "text/html" diff --git a/src/backend/core/tasks/user_reconciliation.py b/src/backend/core/tasks/user_reconciliation.py index 020ca5427c..7a3b517466 100644 --- a/src/backend/core/tasks/user_reconciliation.py +++ b/src/backend/core/tasks/user_reconciliation.py @@ -23,76 +23,103 @@ def user_reconciliation_csv_import_job(job_id): Does some sanity checks on the data: - active_email and inactive_email must be valid email addresses - active_email and inactive_email cannot be the same + + Rows with errors are logged in the job logs and skipped, but do not cause + the entire job to fail or prevent the next rows from being processed. """ # Imports the CSV file, breaks it into UserReconciliation items job = UserReconciliationCsvImport.objects.get(id=job_id) job.status = "running" job.save() + rec_entries_created = 0 + rows_with_errors = 0 + already_processed_source_ids = 0 + try: with job.file.open(mode="r") as f: reader = csv.DictReader(f) - rec_entries_created = 0 + + if not {"active_email", "inactive_email", "id"}.issubset(reader.fieldnames): + raise KeyError( + "CSV is missing mandatory columns: active_email, inactive_email, id" + ) + for row in reader: - status = row["status"] + source_unique_id = row["id"].strip() + + # Skip entries if they already exist with this source_unique_id + if UserReconciliation.objects.filter( + source_unique_id=source_unique_id + ).exists(): + already_processed_source_ids += 1 + continue + + active_email_checked = row.get("active_email_checked", "0") == "1" + inactive_email_checked = row.get("inactive_email_checked", "0") == "1" - if status == "pending": - active_email_checked = row.get("active_email_checked", "0") == "1" - inactive_email_checked = ( - row.get("inactive_email_checked", "0") == "1" + active_email = row["active_email"] + inactive_emails = row["inactive_email"].split("|") + try: + validate_email(active_email) + except ValidationError: + job.send_reconciliation_error_email( + recipient_email=inactive_emails[0], other_email=active_email ) + job.logs += ( + f"Invalid active email address on row {source_unique_id}." + ) + rows_with_errors += 1 + continue - active_email = row["active_email"] - inactive_emails = row["inactive_email"].split("|") + for inactive_email in inactive_emails: try: - validate_email(active_email) - except ValidationError as e: + validate_email(inactive_email) + except (ValidationError, ValueError): job.send_reconciliation_error_email( - active_email, inactive_emails[0] + recipient_email=active_email, other_email=inactive_email ) - job.status = "error" - job.logs = f"{e!s}\n{traceback.format_exc()}" - - for inactive_email in inactive_emails: - try: - validate_email(inactive_email) - except ValidationError as e: - job.send_reconciliation_error_email( - active_email, inactive_email - ) - job.status = "error" - job.logs = f"{e!s}\n{traceback.format_exc()}" - if inactive_email == active_email: - raise ValueError( - "Active and inactive emails cannot be the same." - ) - - rec_entry = UserReconciliation.objects.create( - active_email=active_email, - inactive_email=inactive_email, - active_email_checked=active_email_checked, - inactive_email_checked=inactive_email_checked, - active_confirmation_id=uuid.uuid4(), - inactive_confirmation_id=uuid.uuid4(), - status="pending", + job.logs += f"Invalid inactive email address on row {source_unique_id}.\n" + rows_with_errors += 1 + continue + if inactive_email == active_email: + job.logs += ( + f"Error on row {source_unique_id}: " + f"{active_email} set as both active and inactive email.\n" ) - rec_entry.save() - rec_entries_created += 1 + rows_with_errors += 1 + continue + + _rec_entry = UserReconciliation.objects.create( + active_email=active_email, + inactive_email=inactive_email, + active_email_checked=active_email_checked, + inactive_email_checked=inactive_email_checked, + active_email_confirmation_id=uuid.uuid4(), + inactive_email_confirmation_id=uuid.uuid4(), + source_unique_id=source_unique_id, + status="pending", + ) + rec_entries_created += 1 job.status = "done" - job.logs = f"""Import completed successfully. {reader.line_num} rows processed. - {rec_entries_created} reconciliation entries created.""" + job.logs += ( + f"Import completed successfully. {reader.line_num} rows processed." + f"{rec_entries_created} reconciliation entries created." + f" {already_processed_source_ids} rows were already processed." + f"{rows_with_errors} rows had errors." + ) except ( csv.Error, KeyError, - ValueError, ValidationError, + ValueError, IntegrityError, OSError, ClientError, ) as e: # Catch expected I/O/CSV/model errors and record traceback in logs for debugging job.status = "error" - job.logs = f"{e!s}\n{traceback.format_exc()}" + job.logs += f"{e!s}\n{traceback.format_exc()}" finally: job.save() diff --git a/src/backend/core/tests/data/example_reconciliation_basic.csv b/src/backend/core/tests/data/example_reconciliation_basic.csv index 2f3450d712..7e9f7fd727 100644 --- a/src/backend/core/tests/data/example_reconciliation_basic.csv +++ b/src/backend/core/tests/data/example_reconciliation_basic.csv @@ -1,6 +1,6 @@ -active_email,inactive_email,active_email_checked,inactive_email_checked,status -"user.test40@example.com","user.test41@example.com",0,0,pending -"user.test42@example.com","user.test43@example.com",0,1,pending -"user.test44@example.com","user.test45@example.com",1,0,pending -"user.test46@example.com","user.test47@example.com",1,1,pending -"user.test48@example.com","user.test49@example.com",1,1,pending \ No newline at end of file +active_email,inactive_email,active_email_checked,inactive_email_checked,status,id +"user.test40@example.com","user.test41@example.com",0,0,pending,1 +"user.test42@example.com","user.test43@example.com",0,1,pending,2 +"user.test44@example.com","user.test45@example.com",1,0,pending,3 +"user.test46@example.com","user.test47@example.com",1,1,pending,4 +"user.test48@example.com","user.test49@example.com",1,1,pending,5 \ No newline at end of file diff --git a/src/backend/core/tests/data/example_reconciliation_error.csv b/src/backend/core/tests/data/example_reconciliation_error.csv index 6f8d71abc9..2ac7939616 100644 --- a/src/backend/core/tests/data/example_reconciliation_error.csv +++ b/src/backend/core/tests/data/example_reconciliation_error.csv @@ -1,2 +1,2 @@ -active_email,inactive_email,active_email_checked,inactive_email_checked,status -"user.test40@example.com",,0,0,pending +active_email,inactive_email,active_email_checked,inactive_email_checked,status,id +"user.test40@example.com",,0,0,pending,40 diff --git a/src/backend/core/tests/data/example_reconciliation_grist_form.csv b/src/backend/core/tests/data/example_reconciliation_grist_form.csv index d2b76bcb67..b92fe03679 100644 --- a/src/backend/core/tests/data/example_reconciliation_grist_form.csv +++ b/src/backend/core/tests/data/example_reconciliation_grist_form.csv @@ -1,5 +1,5 @@ -merge_accept,active_email,inactive_email,status -true,user.test10@example.com,user.test11@example.com|user.test12@example.com,pending -true,user.test30@example.com,user.test31@example.com|user.test32@example.com|user.test33@example.com|user.test34@example.com|user.test35@example.com,pending -true,user.test20@example.com,user.test21@example.com,pending -true,user.test22@example.com,user.test23@example.com,pending +merge_accept,active_email,inactive_email,status,id +true,user.test10@example.com,user.test11@example.com|user.test12@example.com,pending,10 +true,user.test30@example.com,user.test31@example.com|user.test32@example.com|user.test33@example.com|user.test34@example.com|user.test35@example.com,pending,11 +true,user.test20@example.com,user.test21@example.com,pending,12 +true,user.test22@example.com,user.test23@example.com,pending,13 \ No newline at end of file diff --git a/src/backend/core/tests/data/example_reconciliation_grist_form_error.csv b/src/backend/core/tests/data/example_reconciliation_grist_form_error.csv index 934816f4b6..86d92ca31b 100644 --- a/src/backend/core/tests/data/example_reconciliation_grist_form_error.csv +++ b/src/backend/core/tests/data/example_reconciliation_grist_form_error.csv @@ -1,2 +1,2 @@ -merge_accept,active_email,inactive_email,status -true,user.test20@example.com,user.test20@example.com,pending +merge_accept,active_email,inactive_email,status,id +true,user.test20@example.com,user.test20@example.com,pending,20 diff --git a/src/backend/core/tests/data/example_reconciliation_missing_column.csv b/src/backend/core/tests/data/example_reconciliation_missing_column.csv new file mode 100644 index 0000000000..2f3450d712 --- /dev/null +++ b/src/backend/core/tests/data/example_reconciliation_missing_column.csv @@ -0,0 +1,6 @@ +active_email,inactive_email,active_email_checked,inactive_email_checked,status +"user.test40@example.com","user.test41@example.com",0,0,pending +"user.test42@example.com","user.test43@example.com",0,1,pending +"user.test44@example.com","user.test45@example.com",1,0,pending +"user.test46@example.com","user.test47@example.com",1,1,pending +"user.test48@example.com","user.test49@example.com",1,1,pending \ No newline at end of file diff --git a/src/backend/core/tests/documents/test_api_documents_create_with_file.py b/src/backend/core/tests/documents/test_api_documents_create_with_file.py new file mode 100644 index 0000000000..3cd6dda2ea --- /dev/null +++ b/src/backend/core/tests/documents/test_api_documents_create_with_file.py @@ -0,0 +1,413 @@ +""" +Tests for Documents API endpoint in impress's core app: create with file upload +""" + +from base64 import b64decode, binascii +from io import BytesIO +from unittest.mock import patch + +import pytest +from rest_framework.test import APIClient + +from core import factories +from core.models import Document +from core.services import mime_types +from core.services.converter_services import ( + ConversionError, + ServiceUnavailableError, +) + +pytestmark = pytest.mark.django_db + + +def test_api_documents_create_with_file_anonymous(): + """Anonymous users should not be allowed to create documents with file upload.""" + # Create a fake DOCX file + file_content = b"fake docx content" + file = BytesIO(file_content) + file.name = "test_document.docx" + + response = APIClient().post( + "/api/v1.0/documents/", + { + "file": file, + }, + format="multipart", + ) + + assert response.status_code == 401 + assert not Document.objects.exists() + + +@patch("core.services.converter_services.Converter.convert") +def test_api_documents_create_with_docx_file_success(mock_convert): + """ + Authenticated users should be able to create documents by uploading a DOCX file. + The file should be converted to YJS format and the title should be set from filename. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + # Mock the conversion + converted_yjs = "base64encodedyjscontent" + mock_convert.return_value = converted_yjs + + # Create a fake DOCX file + file_content = b"fake docx content" + file = BytesIO(file_content) + file.name = "My Important Document.docx" + + response = client.post( + "/api/v1.0/documents/", + { + "file": file, + }, + format="multipart", + ) + + assert response.status_code == 201 + document = Document.objects.get() + assert document.title == "My Important Document.docx" + assert document.content == converted_yjs + assert document.accesses.filter(role="owner", user=user).exists() + + # Verify the converter was called correctly + mock_convert.assert_called_once_with( + file_content, + content_type=mime_types.DOCX, + accept=mime_types.YJS, + ) + + +@patch("core.services.converter_services.Converter.convert") +def test_api_documents_create_with_markdown_file_success(mock_convert): + """ + Authenticated users should be able to create documents by uploading a Markdown file. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + # Mock the conversion + converted_yjs = "base64encodedyjscontent" + mock_convert.return_value = converted_yjs + + # Create a fake Markdown file + file_content = b"# Test Document\n\nThis is a test." + file = BytesIO(file_content) + file.name = "readme.md" + + response = client.post( + "/api/v1.0/documents/", + { + "file": file, + }, + format="multipart", + ) + + assert response.status_code == 201 + document = Document.objects.get() + assert document.title == "readme.md" + assert document.content == converted_yjs + assert document.accesses.filter(role="owner", user=user).exists() + + # Verify the converter was called correctly + mock_convert.assert_called_once_with( + file_content, + content_type=mime_types.MARKDOWN, + accept=mime_types.YJS, + ) + + +@patch("core.services.converter_services.Converter.convert") +def test_api_documents_create_with_file_and_explicit_title(mock_convert): + """ + When both file and title are provided, the filename should override the title. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + # Mock the conversion + converted_yjs = "base64encodedyjscontent" + mock_convert.return_value = converted_yjs + + # Create a fake DOCX file + file_content = b"fake docx content" + file = BytesIO(file_content) + file.name = "Uploaded Document.docx" + + response = client.post( + "/api/v1.0/documents/", + { + "file": file, + "title": "This should be overridden", + }, + format="multipart", + ) + + assert response.status_code == 201 + document = Document.objects.get() + # The filename should take precedence + assert document.title == "Uploaded Document.docx" + + +def test_api_documents_create_with_empty_file(): + """ + Creating a document with an empty file should fail with a validation error. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + # Create an empty file + file = BytesIO(b"") + file.name = "empty.docx" + + response = client.post( + "/api/v1.0/documents/", + { + "file": file, + }, + format="multipart", + ) + + assert response.status_code == 400 + assert response.json() == {"file": ["The submitted file is empty."]} + assert not Document.objects.exists() + + +@patch("core.services.converter_services.Converter.convert") +def test_api_documents_create_with_file_conversion_error(mock_convert): + """ + When conversion fails, the API should return a 400 error with appropriate message. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + # Mock the conversion to raise an error + mock_convert.side_effect = ConversionError("Failed to convert document") + + # Create a fake DOCX file + file_content = b"fake invalid docx content" + file = BytesIO(file_content) + file.name = "corrupted.docx" + + response = client.post( + "/api/v1.0/documents/", + { + "file": file, + }, + format="multipart", + ) + + assert response.status_code == 400 + assert response.json() == {"file": ["Could not convert file content"]} + assert not Document.objects.exists() + + +@patch("core.services.converter_services.Converter.convert") +def test_api_documents_create_with_file_service_unavailable(mock_convert): + """ + When the conversion service is unavailable, appropriate error should be returned. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + # Mock the conversion to raise ServiceUnavailableError + mock_convert.side_effect = ServiceUnavailableError( + "Failed to connect to conversion service" + ) + + # Create a fake DOCX file + file_content = b"fake docx content" + file = BytesIO(file_content) + file.name = "document.docx" + + response = client.post( + "/api/v1.0/documents/", + { + "file": file, + }, + format="multipart", + ) + + assert response.status_code == 400 + assert response.json() == {"file": ["Could not convert file content"]} + assert not Document.objects.exists() + + +def test_api_documents_create_without_file_still_works(): + """ + Creating a document without a file should still work as before (backward compatibility). + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/documents/", + { + "title": "Regular document without file", + }, + format="json", + ) + + assert response.status_code == 201 + document = Document.objects.get() + assert document.title == "Regular document without file" + assert document.content is None + assert document.accesses.filter(role="owner", user=user).exists() + + +@patch("core.services.converter_services.Converter.convert") +def test_api_documents_create_with_file_null_value(mock_convert): + """ + Passing file=null should be treated as no file upload. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + response = client.post( + "/api/v1.0/documents/", + { + "title": "Document with null file", + "file": None, + }, + format="json", + ) + + assert response.status_code == 201 + document = Document.objects.get() + assert document.title == "Document with null file" + # Converter should not have been called + mock_convert.assert_not_called() + + +@patch("core.services.converter_services.Converter.convert") +def test_api_documents_create_with_file_preserves_content_format(mock_convert): + """ + Verify that the converted content is stored correctly in the document. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + # Mock the conversion with realistic base64-encoded YJS data + converted_yjs = "AQMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICA=" + mock_convert.return_value = converted_yjs + + # Create a fake DOCX file + file_content = b"fake docx with complex formatting" + file = BytesIO(file_content) + file.name = "complex_document.docx" + + response = client.post( + "/api/v1.0/documents/", + { + "file": file, + }, + format="multipart", + ) + + assert response.status_code == 201 + document = Document.objects.get() + + # Verify the content is stored as returned by the converter + assert document.content == converted_yjs + + # Verify it's valid base64 (can be decoded) + try: + b64decode(converted_yjs) + except binascii.Error: + pytest.fail("Content should be valid base64-encoded data") + + +@patch("core.services.converter_services.Converter.convert") +def test_api_documents_create_with_file_unicode_filename(mock_convert): + """ + Test that Unicode characters in filenames are handled correctly. + """ + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + # Mock the conversion + converted_yjs = "base64encodedyjscontent" + mock_convert.return_value = converted_yjs + + # Create a file with Unicode characters in the name + file_content = b"fake docx content" + file = BytesIO(file_content) + file.name = "文档-télécharger-документ.docx" + + response = client.post( + "/api/v1.0/documents/", + { + "file": file, + }, + format="multipart", + ) + + assert response.status_code == 201 + document = Document.objects.get() + assert document.title == "文档-télécharger-документ.docx" + + +def test_api_documents_create_with_file_max_size_exceeded(settings): + """ + The uploaded file should not exceed the maximum size in settings. + """ + settings.CONVERSION_FILE_MAX_SIZE = 1 # 1 byte for test + + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + file = BytesIO(b"a" * (10)) + file.name = "test.docx" + + response = client.post( + "/api/v1.0/documents/", + { + "file": file, + }, + format="multipart", + ) + + assert response.status_code == 400 + + assert response.json() == {"file": ["File size exceeds the maximum limit of 0 MB."]} + + +def test_api_documents_create_with_file_extension_not_allowed(settings): + """ + The uploaded file should not have an allowed extension. + """ + settings.CONVERSION_FILE_EXTENSIONS_ALLOWED = [".docx"] + + user = factories.UserFactory() + client = APIClient() + client.force_login(user) + + file = BytesIO(b"fake docx content") + file.name = "test.md" + + response = client.post( + "/api/v1.0/documents/", + { + "file": file, + }, + format="multipart", + ) + + assert response.status_code == 400 + assert response.json() == { + "file": [ + "File extension .md is not allowed. Allowed extensions are: ['.docx']." + ] + } diff --git a/src/backend/core/tests/test_models_user_reconciliation.py b/src/backend/core/tests/test_models_user_reconciliation.py index 3d74ee5a5e..1e8f65bc51 100644 --- a/src/backend/core/tests/test_models_user_reconciliation.py +++ b/src/backend/core/tests/test_models_user_reconciliation.py @@ -76,6 +76,30 @@ def test_user_reconciliation_csv_import_entry_is_created_grist_form( def test_incorrect_csv_format_handling(): + """Test that an incorrectly formatted CSV file is handled gracefully.""" + example_csv_path = ( + Path(__file__).parent / "data/example_reconciliation_missing_column.csv" + ) + with open(example_csv_path, "rb") as f: + csv_file = ContentFile( + f.read(), name="example_reconciliation_missing_column.csv" + ) + csv_import = models.UserReconciliationCsvImport(file=csv_file) + csv_import.save() + + assert csv_import.status == "pending" + + user_reconciliation_csv_import_job(csv_import.id) + csv_import.refresh_from_db() + + assert ( + "CSV is missing mandatory columns: active_email, inactive_email, id" + in csv_import.logs + ) + assert csv_import.status == "error" + + +def test_incorrect_email_format_handling(): """Test that an incorrectly formatted CSV file is handled gracefully.""" example_csv_path = Path(__file__).parent / "data/example_reconciliation_error.csv" with open(example_csv_path, "rb") as f: @@ -88,8 +112,8 @@ def test_incorrect_csv_format_handling(): user_reconciliation_csv_import_job(csv_import.id) csv_import.refresh_from_db() - assert "This field cannot be blank" in csv_import.logs - assert csv_import.status == "error" + assert "Invalid inactive email address on row 40" in csv_import.logs + assert csv_import.status == "done" # pylint: disable-next=no-member assert len(mail.outbox) == 1 @@ -97,7 +121,7 @@ def test_incorrect_csv_format_handling(): # pylint: disable-next=no-member email = mail.outbox[0] - assert email.to == ["user.test40@example.com", ""] + assert email.to == ["user.test40@example.com"] email_content = " ".join(email.body.split()) assert "Reconciliation of your Docs accounts not completed" in email_content @@ -120,8 +144,11 @@ def test_incorrect_csv_data_handling_grist_form(): user_reconciliation_csv_import_job(csv_import.id) csv_import.refresh_from_db() - assert "Active and inactive emails cannot be the same." in csv_import.logs - assert csv_import.status == "error" + assert ( + "user.test20@example.com set as both active and inactive email" + in csv_import.logs + ) + assert csv_import.status == "done" def test_job_creates_reconciliation_entries(import_example_csv_basic): @@ -141,6 +168,37 @@ def test_job_creates_reconciliation_entries(import_example_csv_basic): assert reconciliations.count() == 5 +def test_job_does_not_create_duplicated_reconciliation_entries( + import_example_csv_basic, +): + """Test that the CSV import job doesn't create UserReconciliation entries + for source unique IDs that have already been processed.""" + + _already_created_entry = models.UserReconciliation.objects.create( + active_email="user.test40@example.com", + inactive_email="user.test41@example.com", + active_email_checked=0, + inactive_email_checked=0, + status="pending", + source_unique_id=1, + ) + + assert import_example_csv_basic.status == "pending" + user_reconciliation_csv_import_job(import_example_csv_basic.id) + + # Verify the job status changed + import_example_csv_basic.refresh_from_db() + assert import_example_csv_basic.status == "done" + assert "Import completed successfully." in import_example_csv_basic.logs + assert "6 rows processed." in import_example_csv_basic.logs + assert "4 reconciliation entries created." in import_example_csv_basic.logs + assert "1 rows were already processed." in import_example_csv_basic.logs + + # Verify the correct number of reconciliation entries were created + reconciliations = models.UserReconciliation.objects.all() + assert reconciliations.count() == 5 + + def test_job_creates_reconciliation_entries_grist_form(import_example_csv_grist_form): """Test that the CSV import job creates UserReconciliation entries.""" assert import_example_csv_grist_form.status == "pending" @@ -224,8 +282,8 @@ def test_user_reconciliation_is_created(user_reconciliation_users_and_docs): inactive_email=user_2.email, active_email_checked=False, inactive_email_checked=True, - active_confirmation_id=uuid.uuid4(), - inactive_confirmation_id=uuid.uuid4(), + active_email_confirmation_id=uuid.uuid4(), + inactive_email_confirmation_id=uuid.uuid4(), status="pending", ) @@ -243,8 +301,8 @@ def test_user_reconciliation_verification_emails_are_sent( inactive_email=user_2.email, active_email_checked=False, inactive_email_checked=False, - active_confirmation_id=uuid.uuid4(), - inactive_confirmation_id=uuid.uuid4(), + active_email_confirmation_id=uuid.uuid4(), + inactive_email_confirmation_id=uuid.uuid4(), status="pending", ) @@ -263,9 +321,12 @@ def test_user_reconciliation_verification_emails_are_sent( "You have requested a reconciliation of your user accounts on Docs." in email_1_content ) - active_confirmation_id = rec.active_confirmation_id - inactive_confirmation_id = rec.inactive_confirmation_id - assert f"user_reconciliations/active/{active_confirmation_id}/" in email_1_content + active_email_confirmation_id = rec.active_email_confirmation_id + inactive_email_confirmation_id = rec.inactive_email_confirmation_id + assert ( + f"user_reconciliations/active/{active_email_confirmation_id}/" + in email_1_content + ) # pylint: disable-next=no-member email_2 = mail.outbox[1] @@ -279,7 +340,8 @@ def test_user_reconciliation_verification_emails_are_sent( ) assert ( - f"user_reconciliations/inactive/{inactive_confirmation_id}/" in email_2_content + f"user_reconciliations/inactive/{inactive_email_confirmation_id}/" + in email_2_content ) diff --git a/src/backend/core/tests/test_services_converter_orchestration.py b/src/backend/core/tests/test_services_converter_orchestration.py new file mode 100644 index 0000000000..90ac66d346 --- /dev/null +++ b/src/backend/core/tests/test_services_converter_orchestration.py @@ -0,0 +1,93 @@ +"""Test Converter orchestration services.""" + +from unittest.mock import MagicMock, patch + +from core.services import mime_types +from core.services.converter_services import Converter + + +@patch("core.services.converter_services.DocSpecConverter") +@patch("core.services.converter_services.YdocConverter") +def test_converter_docx_to_yjs_orchestration(mock_ydoc_class, mock_docspec_class): + """Test that DOCX to YJS conversion uses both DocSpec and Ydoc converters.""" + # Setup mocks + mock_docspec = MagicMock() + mock_ydoc = MagicMock() + mock_docspec_class.return_value = mock_docspec + mock_ydoc_class.return_value = mock_ydoc + + # Mock the conversion chain: DOCX -> BlockNote -> YJS + blocknote_data = b'[{"type": "paragraph", "content": "test"}]' + yjs_data = "base64encodedyjs" + + mock_docspec.convert.return_value = blocknote_data + mock_ydoc.convert.return_value = yjs_data + + # Execute conversion + converter = Converter() + docx_data = b"fake docx data" + result = converter.convert(docx_data, mime_types.DOCX, mime_types.YJS) + + # Verify the orchestration + mock_docspec.convert.assert_called_once_with( + docx_data, mime_types.DOCX, mime_types.BLOCKNOTE + ) + mock_ydoc.convert.assert_called_once_with( + blocknote_data, mime_types.BLOCKNOTE, mime_types.YJS + ) + assert result == yjs_data + + +@patch("core.services.converter_services.YdocConverter") +def test_converter_markdown_to_yjs_delegation(mock_ydoc_class): + """Test that Markdown to YJS conversion is delegated to YdocConverter.""" + mock_ydoc = MagicMock() + mock_ydoc_class.return_value = mock_ydoc + + yjs_data = "base64encodedyjs" + mock_ydoc.convert.return_value = yjs_data + + converter = Converter() + markdown_data = "# Test Document" + result = converter.convert(markdown_data, mime_types.MARKDOWN, mime_types.YJS) + + mock_ydoc.convert.assert_called_once_with( + markdown_data, mime_types.MARKDOWN, mime_types.YJS + ) + assert result == yjs_data + + +@patch("core.services.converter_services.YdocConverter") +def test_converter_yjs_to_html_delegation(mock_ydoc_class): + """Test that YJS to HTML conversion is delegated to YdocConverter.""" + mock_ydoc = MagicMock() + mock_ydoc_class.return_value = mock_ydoc + + html_data = "

Test Document

" + mock_ydoc.convert.return_value = html_data + + converter = Converter() + yjs_data = b"yjs binary data" + result = converter.convert(yjs_data, mime_types.YJS, mime_types.HTML) + + mock_ydoc.convert.assert_called_once_with(yjs_data, mime_types.YJS, mime_types.HTML) + assert result == html_data + + +@patch("core.services.converter_services.YdocConverter") +def test_converter_blocknote_to_yjs_delegation(mock_ydoc_class): + """Test that BlockNote to YJS conversion is delegated to YdocConverter.""" + mock_ydoc = MagicMock() + mock_ydoc_class.return_value = mock_ydoc + + yjs_data = "base64encodedyjs" + mock_ydoc.convert.return_value = yjs_data + + converter = Converter() + blocknote_data = b'[{"type": "paragraph"}]' + result = converter.convert(blocknote_data, mime_types.BLOCKNOTE, mime_types.YJS) + + mock_ydoc.convert.assert_called_once_with( + blocknote_data, mime_types.BLOCKNOTE, mime_types.YJS + ) + assert result == yjs_data diff --git a/src/backend/core/tests/test_services_docspec_converter.py b/src/backend/core/tests/test_services_docspec_converter.py new file mode 100644 index 0000000000..16f4a5f521 --- /dev/null +++ b/src/backend/core/tests/test_services_docspec_converter.py @@ -0,0 +1,117 @@ +"""Test DocSpec converter services.""" + +from unittest.mock import MagicMock, patch + +import pytest +import requests + +from core.services import mime_types +from core.services.converter_services import ( + DocSpecConverter, + ServiceUnavailableError, + ValidationError, +) + + +def test_docspec_convert_empty_data(): + """Should raise ValidationError when data is empty.""" + converter = DocSpecConverter() + with pytest.raises(ValidationError, match="Input data cannot be empty"): + converter.convert("", mime_types.DOCX, mime_types.BLOCKNOTE) + + +def test_docspec_convert_none_input(): + """Should raise ValidationError when input is None.""" + converter = DocSpecConverter() + with pytest.raises(ValidationError, match="Input data cannot be empty"): + converter.convert(None, mime_types.DOCX, mime_types.BLOCKNOTE) + + +def test_docspec_convert_unsupported_content_type(): + """Should raise ValidationError when content type is not DOCX.""" + converter = DocSpecConverter() + with pytest.raises( + ValidationError, match="Conversion from text/plain to .* is not supported" + ): + converter.convert(b"test data", "text/plain", mime_types.BLOCKNOTE) + + +def test_docspec_convert_unsupported_accept(): + """Should raise ValidationError when accept type is not BLOCKNOTE.""" + converter = DocSpecConverter() + with pytest.raises( + ValidationError, + match=f"Conversion from {mime_types.DOCX} to {mime_types.YJS} is not supported", + ): + converter.convert(b"test data", mime_types.DOCX, mime_types.YJS) + + +@patch("requests.post") +def test_docspec_convert_service_unavailable(mock_post): + """Should raise ServiceUnavailableError when service is unavailable.""" + converter = DocSpecConverter() + mock_post.side_effect = requests.RequestException("Connection error") + + with pytest.raises( + ServiceUnavailableError, + match="Failed to connect to DocSpec conversion service", + ): + converter.convert(b"test data", mime_types.DOCX, mime_types.BLOCKNOTE) + + +@patch("requests.post") +def test_docspec_convert_http_error(mock_post): + """Should raise ServiceUnavailableError when HTTP error occurs.""" + converter = DocSpecConverter() + mock_response = MagicMock() + mock_response.raise_for_status.side_effect = requests.HTTPError("HTTP Error") + mock_post.return_value = mock_response + + with pytest.raises( + ServiceUnavailableError, + match="Failed to connect to DocSpec conversion service", + ): + converter.convert(b"test data", mime_types.DOCX, mime_types.BLOCKNOTE) + + +@patch("requests.post") +def test_docspec_convert_timeout(mock_post): + """Should raise ServiceUnavailableError when request times out.""" + converter = DocSpecConverter() + mock_post.side_effect = requests.Timeout("Request timed out") + + with pytest.raises( + ServiceUnavailableError, + match="Failed to connect to DocSpec conversion service", + ): + converter.convert(b"test data", mime_types.DOCX, mime_types.BLOCKNOTE) + + +@patch("requests.post") +def test_docspec_convert_success(mock_post, settings): + """Test successful DOCX to BlockNote conversion.""" + settings.DOCSPEC_API_URL = "http://docspec.test/convert" + settings.CONVERSION_API_TIMEOUT = 5 + settings.CONVERSION_API_SECURE = False + + converter = DocSpecConverter() + + expected_content = b'[{"type": "paragraph", "content": "test"}]' + mock_response = MagicMock() + mock_response.content = expected_content + mock_response.raise_for_status.return_value = None + mock_post.return_value = mock_response + + docx_data = b"fake docx binary data" + result = converter.convert(docx_data, mime_types.DOCX, mime_types.BLOCKNOTE) + + assert result == expected_content + + # Verify the request was made correctly + mock_post.assert_called_once_with( + "http://docspec.test/convert", + headers={"Accept": mime_types.BLOCKNOTE}, + files={"file": ("document.docx", docx_data, mime_types.DOCX)}, + timeout=5, + verify=False, + ) diff --git a/src/frontend/apps/e2e/__tests__/app-impress/assets/test_import.docx b/src/frontend/apps/e2e/__tests__/app-impress/assets/test_import.docx new file mode 100644 index 0000000000000000000000000000000000000000..8db66a06a08a62fb5d0e53ae9cca1470d99551ef GIT binary patch literal 248980 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

    aKIFJtx4uQ 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 literal 0 HcmV?d00001 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/assets/test_import.md b/src/frontend/apps/e2e/__tests__/app-impress/assets/test_import.md new file mode 100644 index 0000000000..461c52de18 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-impress/assets/test_import.md @@ -0,0 +1,60 @@ +![473389927-e4ff1794-69f3-460a-85f8-fec993cd74d6.png](http://localhost:3000/assets/logo-suite-numerique.png)![497094770-53e5f8e2-c93e-4a0b-a82f-cd184fd03f51.svg](http://localhost:3000/assets/icon-docs.svg) + +# Lorem Ipsum import Document + +## Introduction + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam auctor, nisl eget ultricies tincidunt, nisl nisl aliquam nisl, eget ultricies nisl nisl eget nisl. + +### Subsection 1.1 + +* **Bold text**: Lorem ipsum dolor sit amet. + +* *Italic text*: Consectetur adipiscing elit. + +* ~~Strikethrough text~~: Nullam auctor, nisl eget ultricies tincidunt. + +1. First item in an ordered list. + +2. Second item in an ordered list. + + * Indented bullet point. + + * Another indented bullet point. + +3. Third item in an ordered list. + +### Subsection 1.2 + +**Code block:** + +```js +const hello_world = () => { + console.log("Hello, world!"); +} +``` + +**Blockquote:** + +> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam auctor, nisl eget ultricies tincidunt. + +**Horizontal rule:** + +*** + +**Table:** + +| Syntax | Description | +| --------- | ----------- | +| Header | Title | +| Paragraph | Text | + +**Inline code:** + +Use the `printf()` function. + +**Link:** [Example](http://localhost:3000/) + +## Conclusion + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam auctor, nisl eget ultricies tincidunt, nisl nisl aliquam nisl, eget ultricies nisl nisl eget nisl. diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-import.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-import.spec.ts new file mode 100644 index 0000000000..f5269bced0 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-import.spec.ts @@ -0,0 +1,181 @@ +import { readFileSync } from 'fs'; +import path from 'path'; + +import { Page, expect, test } from '@playwright/test'; + +import { getEditor } from './utils-editor'; + +test.beforeEach(async ({ page }) => { + await page.goto('/'); +}); + +test.describe('Doc Import', () => { + test('it imports 2 docs with the import icon', async ({ page }) => { + const fileChooserPromise = page.waitForEvent('filechooser'); + await page.getByLabel('Open the upload dialog').click(); + + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles([ + path.join(__dirname, 'assets/test_import.docx'), + path.join(__dirname, 'assets/test_import.md'), + ]); + + await expect( + page.getByText( + 'The document "test_import.docx" has been successfully imported', + ), + ).toBeVisible(); + await expect( + page.getByText( + 'The document "test_import.md" has been successfully imported', + ), + ).toBeVisible(); + + const docsGrid = page.getByTestId('docs-grid'); + await expect(docsGrid.getByText('test_import.docx').first()).toBeVisible(); + await expect(docsGrid.getByText('test_import.md').first()).toBeVisible(); + + // Check content of imported md + await docsGrid.getByText('test_import.md').first().click(); + const editor = await getEditor({ page }); + + const contentCheck = async (isMDCheck = false) => { + await expect( + editor.getByRole('heading', { + name: 'Lorem Ipsum import Document', + level: 1, + }), + ).toBeVisible(); + await expect( + editor.getByRole('heading', { + name: 'Introduction', + level: 2, + }), + ).toBeVisible(); + await expect( + editor.getByRole('heading', { + name: 'Subsection 1.1', + level: 3, + }), + ).toBeVisible(); + await expect( + editor + .locator('div[data-content-type="bulletListItem"] strong') + .getByText('Bold text'), + ).toBeVisible(); + await expect( + editor + .locator('div[data-content-type="codeBlock"]') + .getByText('hello_world'), + ).toBeVisible(); + await expect( + editor + .locator('div[data-content-type="table"] td') + .getByText('Paragraph'), + ).toBeVisible(); + await expect( + editor.locator('a[href="http://localhost:3000/"]').getByText('Example'), + ).toBeVisible(); + + /* eslint-disable playwright/no-conditional-expect */ + if (isMDCheck) { + await expect( + editor.locator( + 'img[src="http://localhost:3000/assets/logo-suite-numerique.png"]', + ), + ).toBeVisible(); + await expect( + editor.locator( + 'img[src="http://localhost:3000/assets/icon-docs.svg"]', + ), + ).toBeVisible(); + } else { + await expect(editor.locator('img')).toHaveCount(2); + } + /* eslint-enable playwright/no-conditional-expect */ + + /** + * Divider are not supported in docx import in DocSpec 2.4.4 + */ + /* eslint-disable playwright/no-conditional-expect */ + if (isMDCheck) { + await expect( + editor.locator('div[data-content-type="divider"] hr'), + ).toBeVisible(); + } + /* eslint-enable playwright/no-conditional-expect */ + }; + + await contentCheck(true); + + // Check content of imported docx + await page.getByLabel('Back to homepage').first().click(); + await docsGrid.getByText('test_import.docx').first().click(); + + await contentCheck(); + }); + + test('it imports 2 docs with the drag and drop area', async ({ page }) => { + const docsGrid = page.getByTestId('docs-grid'); + await expect(docsGrid).toBeVisible(); + + await dragAndDropFiles(page, "[data-testid='docs-grid']", [ + { + filePath: path.join(__dirname, 'assets/test_import.docx'), + fileName: 'test_import.docx', + fileType: + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + }, + { + filePath: path.join(__dirname, 'assets/test_import.md'), + fileName: 'test_import.md', + fileType: 'text/markdown', + }, + ]); + + // Wait for success messages + await expect( + page.getByText( + 'The document "test_import.docx" has been successfully imported', + ), + ).toBeVisible(); + await expect( + page.getByText( + 'The document "test_import.md" has been successfully imported', + ), + ).toBeVisible(); + + await expect(docsGrid.getByText('test_import.docx').first()).toBeVisible(); + await expect(docsGrid.getByText('test_import.md').first()).toBeVisible(); + }); +}); + +const dragAndDropFiles = async ( + page: Page, + selector: string, + files: Array<{ filePath: string; fileName: string; fileType?: string }>, +) => { + const filesData = files.map((file) => ({ + bufferData: `data:application/octet-stream;base64,${readFileSync(file.filePath).toString('base64')}`, + fileName: file.fileName, + fileType: file.fileType || '', + })); + + const dataTransfer = await page.evaluateHandle(async (filesInfo) => { + const dt = new DataTransfer(); + + for (const fileInfo of filesInfo) { + const blobData = await fetch(fileInfo.bufferData).then((res) => + res.blob(), + ); + const file = new File([blobData], fileInfo.fileName, { + type: fileInfo.fileType, + }); + dt.items.add(file); + } + + return dt; + }, filesData); + + await page.dispatchEvent(selector, 'drop', { dataTransfer }); +}; diff --git a/src/frontend/apps/impress/src/assets/icons/doc-all.svg b/src/frontend/apps/impress/src/assets/icons/doc-all.svg new file mode 100644 index 0000000000..a4e61a5aa0 --- /dev/null +++ b/src/frontend/apps/impress/src/assets/icons/doc-all.svg @@ -0,0 +1,20 @@ + + + + + + diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocsFavorite.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocsFavorite.tsx new file mode 100644 index 0000000000..3baa451024 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocsFavorite.tsx @@ -0,0 +1,72 @@ +import { UseQueryOptions, useQuery } from '@tanstack/react-query'; + +import { + APIError, + APIList, + InfiniteQueryConfig, + errorCauses, + fetchAPI, + useAPIInfiniteQuery, +} from '@/api'; + +import { Doc } from '../types'; + +export type DocsFavoriteParams = { + page: number; +}; + +export type DocsFavoriteResponse = APIList; +export const getDocsFavorite = async ( + params: DocsFavoriteParams, +): Promise => { + const searchParams = new URLSearchParams(); + + if (params.page) { + searchParams.set('page', params.page.toString()); + } + + const response = await fetchAPI( + `documents/favorite_list/?${searchParams.toString()}`, + ); + + if (!response.ok) { + throw new APIError( + 'Failed to get the favorite docs', + await errorCauses(response), + ); + } + + return response.json() as Promise; +}; + +export const KEY_LIST_FAVORITE_DOC = 'docs_favorite_list'; + +type UseDocsOptions = UseQueryOptions< + DocsFavoriteResponse, + APIError, + DocsFavoriteResponse +>; +type UseInfiniteDocsOptions = InfiniteQueryConfig; + +export function useDocsFavorite( + params: DocsFavoriteParams, + queryConfig?: UseDocsOptions, +) { + return useQuery({ + queryKey: [KEY_LIST_FAVORITE_DOC, params], + queryFn: () => getDocsFavorite(params), + ...queryConfig, + }); +} + +export const useInfiniteDocsFavorite = ( + params: DocsFavoriteParams, + queryConfig?: UseInfiniteDocsOptions, +) => { + return useAPIInfiniteQuery( + KEY_LIST_FAVORITE_DOC, + getDocsFavorite, + params, + queryConfig, + ); +}; diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/api/useImportDoc.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/api/useImportDoc.tsx new file mode 100644 index 0000000000..d4b9d1e936 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/api/useImportDoc.tsx @@ -0,0 +1,125 @@ +import { + VariantType, + useToastProvider, +} from '@gouvfr-lasuite/cunningham-react'; +import { + UseMutationOptions, + useMutation, + useQueryClient, +} from '@tanstack/react-query'; +import { useTranslation } from 'react-i18next'; + +import { + APIError, + UseInfiniteQueryResultAPI, + errorCauses, + fetchAPI, +} from '@/api'; +import { Doc, DocsResponse, KEY_LIST_DOC } from '@/docs/doc-management'; + +export enum ContentTypes { + Docx = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + Markdown = 'text/markdown', + OctetStream = 'application/octet-stream', +} + +export const importDoc = async ([file, mimeType]: [ + File, + string, +]): Promise => { + const form = new FormData(); + + form.append( + 'file', + new File([file], file.name, { + type: mimeType, + lastModified: file.lastModified, + }), + ); + + const response = await fetchAPI(`documents/`, { + method: 'POST', + body: form, + withoutContentType: true, + }); + + if (!response.ok) { + throw new APIError('Failed to import the doc', await errorCauses(response)); + } + + return response.json() as Promise; +}; + +type UseImportDocOptions = UseMutationOptions; + +export function useImportDoc(props?: UseImportDocOptions) { + const { toast } = useToastProvider(); + const queryClient = useQueryClient(); + const { t } = useTranslation(); + + return useMutation({ + mutationFn: importDoc, + ...props, + onSuccess: (...successProps) => { + const importedDoc = successProps[0]; + + const updateDocsListCache = (isCreatorMe: boolean | undefined) => { + queryClient.setQueriesData>( + { + queryKey: [ + KEY_LIST_DOC, + { + page: 1, + ordering: undefined, + is_creator_me: isCreatorMe, + title: undefined, + is_favorite: undefined, + }, + ], + }, + (oldData) => { + if (!oldData || oldData?.pages.length === 0) { + return oldData; + } + + return { + ...oldData, + pages: oldData.pages.map((page, index) => { + // Add the new doc to the first page only + if (index === 0) { + return { + ...page, + results: [importedDoc, ...page.results], + }; + } + return page; + }), + }; + }, + ); + }; + + updateDocsListCache(undefined); + updateDocsListCache(true); + + toast( + t('The document "{{documentName}}" has been successfully imported', { + documentName: importedDoc.title || '', + }), + VariantType.SUCCESS, + ); + + props?.onSuccess?.(...successProps); + }, + onError: (...errorProps) => { + toast( + t(`The document "{{documentName}}" import has failed`, { + documentName: errorProps?.[1][0].name || '', + }), + VariantType.ERROR, + ); + + props?.onError?.(...errorProps); + }, + }); +} diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/hooks/useImport.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/hooks/useImport.tsx new file mode 100644 index 0000000000..9721f26777 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/hooks/useImport.tsx @@ -0,0 +1,116 @@ +import { + VariantType, + useToastProvider, +} from '@gouvfr-lasuite/cunningham-react'; +import { t } from 'i18next'; +import { useMemo } from 'react'; +import { useDropzone } from 'react-dropzone'; + +import { useConfig } from '@/core'; + +import { ContentTypes, useImportDoc } from '../api/useImportDoc'; + +interface UseImportProps { + onDragOver: (isDragOver: boolean) => void; +} + +export const useImport = ({ onDragOver }: UseImportProps) => { + const { toast } = useToastProvider(); + const { data: config } = useConfig(); + + const MAX_FILE_SIZE = useMemo(() => { + const maxSizeInBytes = config?.CONVERSION_FILE_MAX_SIZE ?? 10 * 1024 * 1024; // Default to 10MB + + const units = ['bytes', 'KB', 'MB', 'GB']; + let size = maxSizeInBytes; + let unitIndex = 0; + + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex += 1; + } + + return { + bytes: maxSizeInBytes, + text: `${Math.round(size * 10) / 10}${units[unitIndex]}`, + }; + }, [config?.CONVERSION_FILE_MAX_SIZE]); + + const ACCEPT = useMemo(() => { + const extensions = config?.CONVERSION_FILE_EXTENSIONS_ALLOWED; + const accept: { [key: string]: string[] } = {}; + + if (extensions && extensions.length > 0) { + extensions.forEach((ext) => { + switch (ext.toLowerCase()) { + case '.docx': + accept[ContentTypes.Docx] = ['.docx']; + break; + case '.md': + case '.markdown': + accept[ContentTypes.Markdown] = ['.md']; + break; + default: + break; + } + }); + } else { + // Default to docx and md if no configuration is provided + accept[ContentTypes.Docx] = ['.docx']; + accept[ContentTypes.Markdown] = ['.md']; + } + + return accept; + }, [config?.CONVERSION_FILE_EXTENSIONS_ALLOWED]); + + const { getRootProps, getInputProps, open } = useDropzone({ + accept: ACCEPT, + maxSize: MAX_FILE_SIZE.bytes, + onDrop(acceptedFiles) { + onDragOver(false); + for (const file of acceptedFiles) { + importDoc([file, file.type]); + } + }, + onDragEnter: () => { + onDragOver(true); + }, + onDragLeave: () => { + onDragOver(false); + }, + onDropRejected(fileRejections) { + fileRejections.forEach((rejection) => { + const isFileTooLarge = rejection.errors.some( + (error) => error.code === 'file-too-large', + ); + + if (isFileTooLarge) { + toast( + t( + 'The document "{{documentName}}" is too large. Maximum file size is {{maxFileSize}}.', + { + documentName: rejection.file.name, + maxFileSize: MAX_FILE_SIZE.text, + }, + ), + VariantType.ERROR, + ); + } else { + toast( + t( + `The document "{{documentName}}" import has failed (only .docx and .md files are allowed)`, + { + documentName: rejection.file.name, + }, + ), + VariantType.ERROR, + ); + } + }); + }, + noClick: true, + }); + const { mutate: importDoc } = useImportDoc(); + + return { getRootProps, getInputProps, open }; +}; diff --git a/src/helm/impress/templates/docspec_deployment.yaml b/src/helm/impress/templates/docspec_deployment.yaml new file mode 100644 index 0000000000..984b98d1bb --- /dev/null +++ b/src/helm/impress/templates/docspec_deployment.yaml @@ -0,0 +1,108 @@ +{{- if .Values.docSpec.enabled -}} +{{- $envVars := include "impress.common.env" (list . .Values.docSpec) -}} +{{- $fullName := include "impress.docSpec.fullname" . -}} +{{- $component := "docspec" -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "impress.common.labels" (list . $component) | nindent 4 }} +spec: + replicas: {{ .Values.docSpec.replicas }} + selector: + matchLabels: + {{- include "impress.common.selectorLabels" (list . $component) | nindent 6 }} + template: + metadata: + labels: + {{- include "impress.common.selectorLabels" (list . $component) | nindent 8 }} + spec: + {{- if $.Values.image.credentials }} + imagePullSecrets: + - name: {{ include "impress.secret.dockerconfigjson.name" (dict "fullname" (include "impress.fullname" .) "imageCredentials" $.Values.image.credentials) }} + {{- end}} + containers: + - name: {{ .Chart.Name }}-docspec + image: "{{ .Values.docSpec.image.repository }}:{{ .Values.docSpec.image.tag }}" + imagePullPolicy: {{ .Values.docSpec.image.pullPolicy }} + {{- with .Values.docSpec.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.docSpec.args }} + args: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if $envVars }} + env: + {{- $envVars | indent 12 }} + {{- end }} + {{- with .Values.docSpec.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.docSpec.service.targetPort }} + protocol: TCP + {{- if .Values.docSpec.probes.liveness }} + livenessProbe: + {{- include "impress.probes.abstract" (merge .Values.docSpec.probes.liveness (dict "targetPort" .Values.docSpec.service.targetPort )) | nindent 12 }} + {{- end }} + {{- if .Values.docSpec.probes.readiness }} + readinessProbe: + {{- include "impress.probes.abstract" (merge .Values.docSpec.probes.readiness (dict "targetPort" .Values.docSpec.service.targetPort )) | nindent 12 }} + {{- end }} + {{- with .Values.docSpec.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.docSpec.extraVolumeMounts }} + volumeMounts: + {{- range .Values.docSpec.extraVolumeMounts }} + - name: {{ .name }} + mountPath: {{ .mountPath }} + {{- if .subPath }} + subPath: {{ .subPath }} + {{- end }} + {{- if .readOnly }} + readOnly: {{ .readOnly }} + {{- end }} + {{- end }} + {{- end }} + {{- with .Values.docSpec.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.docSpec.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.docSpec.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.docSpec.extraVolumes }} + volumes: + {{- range .Values.docSpec.extraVolumes }} + - name: {{ .name }} + {{- if .persistentVolumeClaim }} + persistentVolumeClaim: + {{- toYaml .persistentVolumeClaim | nindent 12 }} + {{- else if .emptyDir }} + emptyDir: + {{- toYaml .emptyDir | nindent 12 }} + {{- else if .configMap }} + configMap: + {{- toYaml .configMap | nindent 12 }} + {{- else if .secret }} + secret: + {{- toYaml .secret | nindent 12 }} + {{- else }} + emptyDir: {} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/src/helm/impress/templates/docspec_svc.yaml b/src/helm/impress/templates/docspec_svc.yaml new file mode 100644 index 0000000000..f393446cc2 --- /dev/null +++ b/src/helm/impress/templates/docspec_svc.yaml @@ -0,0 +1,20 @@ +{{- if .Values.docSpec.enabled -}} +{{- $fullName := include "impress.docSpec.fullname" . -}} +{{- $component := "docspec" -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "impress.common.labels" (list . $component) | nindent 4 }} +spec: + type: {{ .Values.docSpec.service.type }} + ports: + - port: {{ .Values.docSpec.service.port }} + targetPort: {{ .Values.docSpec.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "impress.common.selectorLabels" (list . $component) | nindent 4 }} +{{- end }} From c65ab34ffbaa6d5a822b619622a114788f03cbbf Mon Sep 17 00:00:00 2001 From: Sylvain Boissel Date: Mon, 2 Feb 2026 16:56:01 +0100 Subject: [PATCH 06/11] =?UTF-8?q?=E2=9C=A8(backend)=20reconciliation=20req?= =?UTF-8?q?uests:=20use=20the=20standard=20email=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This removes the new email template in favor of using the already existing one, by removing the extra formating. --- docs/env.md | 1 + src/backend/core/admin.py | 7 - src/backend/core/api/viewsets.py | 2 +- ...nciliationcsvimport_userreconciliation.py} | 6 +- src/backend/core/models.py | 58 ++++---- src/backend/core/tasks/user_reconciliation.py | 132 ++++++++++-------- .../tests/test_models_user_reconciliation.py | 4 +- src/backend/core/urls.py | 2 +- src/backend/impress/settings.py | 5 + .../__tests__/UserReconciliation.test.tsx | 4 +- .../auth/api/useUserReconciliations.tsx | 2 +- src/mail/mjml/user_template.mjml | 62 -------- 12 files changed, 118 insertions(+), 167 deletions(-) rename src/backend/core/migrations/{0028_userreconciliationcsvimport_userreconciliation.py => 0029_userreconciliationcsvimport_userreconciliation.py} (96%) delete mode 100644 src/mail/mjml/user_template.mjml diff --git a/docs/env.md b/docs/env.md index 186cfb3051..bd72e41848 100644 --- a/docs/env.md +++ b/docs/env.md @@ -118,6 +118,7 @@ These are the environment variables you can set for the `impress-backend` contai | THEME_CUSTOMIZATION_FILE_PATH | Full path to the file customizing the theme. An example is provided in src/backend/impress/configuration/theme/default.json | BASE_DIR/impress/configuration/theme/default.json | | TRASHBIN_CUTOFF_DAYS | Trashbin cutoff | 30 | | USER_OIDC_ESSENTIAL_CLAIMS | Essential claims in OIDC token | [] | +| USER_RECONCILIATION_FORM_URL | URL of a third-party form for user reconciliation requests | | | Y_PROVIDER_API_BASE_URL | Y Provider url | | | Y_PROVIDER_API_KEY | Y provider API key | | diff --git a/src/backend/core/admin.py b/src/backend/core/admin.py index 964e57be43..d2034c67f6 100644 --- a/src/backend/core/admin.py +++ b/src/backend/core/admin.py @@ -162,13 +162,6 @@ class UserReconciliationAdmin(admin.ModelAdmin): actions = [process_reconciliation] -@admin.register(models.Template) -class TemplateAdmin(admin.ModelAdmin): - """Template admin interface declaration.""" - - inlines = (TemplateAccessInline,) - - class DocumentAccessInline(admin.TabularInline): """Inline admin class for document accesses.""" diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index 1f74d14662..d0fbc2c85d 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -254,7 +254,7 @@ def get_me(self, request): class ReconciliationConfirmView(APIView): """API endpoint to confirm user reconciliation emails. - GET /user_reconciliations/{user_type}/{confirmation_id}/ + GET /user-reconciliations/{user_type}/{confirmation_id}/ Marks `active_email_checked` or `inactive_email_checked` to True. """ diff --git a/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py b/src/backend/core/migrations/0029_userreconciliationcsvimport_userreconciliation.py similarity index 96% rename from src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py rename to src/backend/core/migrations/0029_userreconciliationcsvimport_userreconciliation.py index 21a3a2862b..f9aefee063 100644 --- a/src/backend/core/migrations/0028_userreconciliationcsvimport_userreconciliation.py +++ b/src/backend/core/migrations/0029_userreconciliationcsvimport_userreconciliation.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.10 on 2026-01-27 15:50 +# Generated by Django 5.2.10 on 2026-02-02 16:58 import uuid @@ -9,7 +9,7 @@ class Migration(migrations.Migration): dependencies = [ - ("core", "0027_auto_20251120_0956"), + ("core", "0028_remove_templateaccess_template_and_more"), ] operations = [ @@ -65,6 +65,7 @@ class Migration(migrations.Migration): options={ "verbose_name": "user reconciliation CSV import", "verbose_name_plural": "user reconciliation CSV imports", + "db_table": "impress_user_reconciliation_csv_import", }, ), migrations.CreateModel( @@ -170,6 +171,7 @@ class Migration(migrations.Migration): options={ "verbose_name": "user reconciliation", "verbose_name_plural": "user reconciliations", + "db_table": "impress_user_reconciliation", "ordering": ["-created_at"], }, ), diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 46aa4b963f..31ff11ece8 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -266,8 +266,8 @@ def send_email(self, subject, context=None, language=None): ) with override(language): - msg_html = render_to_string("mail/html/user_template.html", context) - msg_plain = render_to_string("mail/text/user_template.txt", context) + msg_html = render_to_string("mail/html/template.html", context) + msg_plain = render_to_string("mail/text/template.txt", context) subject = str(subject) # Force translation try: @@ -344,6 +344,7 @@ class UserReconciliation(BaseModel): logs = models.TextField(blank=True) class Meta: + db_table = "impress_user_reconciliation" verbose_name = _("user reconciliation") verbose_name_plural = _("user reconciliations") ordering = ["-created_at"] @@ -423,18 +424,18 @@ def send_reconciliation_confirm_email( language = language or get_language() domain = Site.objects.get_current().domain + message = _( + """You have requested a reconciliation of your user accounts on Docs. + To confirm that you are the one who initiated the request + and that this email belongs to you:""" + ) + with override(language): subject = _("Confirm by clicking the link to start the reconciliation") context = { "title": subject, - "message_bold": _( - "You have requested a reconciliation of your user accounts on Docs." - ), - "message": _( - """To confirm that you are the one who initiated the request - and that this email belongs to you:""" - ), - "link": f"{domain}/user_reconciliations/{user_type}/{confirmation_id}/", + "message": message, + "link": f"{domain}/user-reconciliations/{user_type}/{confirmation_id}/", "link_label": str(_("Click here")), "button_label": str(_("Confirm")), } @@ -446,12 +447,16 @@ def send_reconciliation_done_email(self, language=None): language = language or get_language() domain = Site.objects.get_current().domain + message = _( + """Your reconciliation request has been processed. + New documents are likely associated with your account:""" + ) + with override(language): subject = _("Your accounts have been merged") context = { "title": subject, - "message_bold": _("Your reconciliation request has been processed."), - "message": _("New documents are likely associated with your account:"), + "message": message, "link": f"{domain}/", "link_label": str(_("Click here to see")), "button_label": str(_("See my documents")), @@ -478,6 +483,7 @@ class UserReconciliationCsvImport(BaseModel): logs = models.TextField(blank=True) class Meta: + db_table = "impress_user_reconciliation_csv_import" verbose_name = _("user reconciliation CSV import") verbose_name_plural = _("user reconciliation CSV imports") @@ -498,8 +504,8 @@ def send_email(self, subject, emails, context=None, language=None): ) with override(language): - msg_html = render_to_string("mail/html/user_template.html", context) - msg_plain = render_to_string("mail/text/user_template.txt", context) + msg_html = render_to_string("mail/html/template.html", context) + msg_plain = render_to_string("mail/text/template.txt", context) subject = str(subject) # Force translation try: @@ -519,27 +525,23 @@ def send_reconciliation_error_email( ): """Method allowing to send email for reconciliation requests with errors.""" language = language or get_language() - domain = Site.objects.get_current().domain emails = [recipient_email] + message = _( + """Your request for reconciliation was unsuccessful. + Reconciliation failed for the following email addresses: + {recipient_email}, {other_email}. + Please check for typos. + You can submit another request with the valid email addresses.""" + ).format(recipient_email=recipient_email, other_email=other_email) + with override(language): subject = _("Reconciliation of your Docs accounts not completed") context = { "title": subject, - "message_bold": _("Your request for reconciliation was unsuccessful."), - "message": _( - """Reconciliation failed for the following email addresses: - - - {recipient_email} - - {other_email} - - Please check for typos. - - You can submit another request with the valid email addresses. - """ - ).format(recipient_email=recipient_email, other_email=other_email), - "link": f"{domain}/", + "message": message, + "link": settings.USER_RECONCILIATION_FORM_URL, "link_label": str(_("Click here")), "button_label": str(_("Make a new request")), } diff --git a/src/backend/core/tasks/user_reconciliation.py b/src/backend/core/tasks/user_reconciliation.py index 7a3b517466..364a628d60 100644 --- a/src/backend/core/tasks/user_reconciliation.py +++ b/src/backend/core/tasks/user_reconciliation.py @@ -15,6 +15,68 @@ from impress.celery_app import app +def _process_row(row, job, counters): + """Process a single row from the CSV file.""" + + source_unique_id = row["id"].strip() + + # Skip entries if they already exist with this source_unique_id + if UserReconciliation.objects.filter(source_unique_id=source_unique_id).exists(): + counters["already_processed_source_ids"] += 1 + return counters + + active_email_checked = row.get("active_email_checked", "0") == "1" + inactive_email_checked = row.get("inactive_email_checked", "0") == "1" + + active_email = row["active_email"] + inactive_emails = row["inactive_email"].split("|") + try: + validate_email(active_email) + except ValidationError: + job.send_reconciliation_error_email( + recipient_email=inactive_emails[0], other_email=active_email + ) + job.logs += f"Invalid active email address on row {source_unique_id}." + counters["rows_with_errors"] += 1 + return counters + + for inactive_email in inactive_emails: + try: + validate_email(inactive_email) + except (ValidationError, ValueError): + job.send_reconciliation_error_email( + recipient_email=active_email, other_email=inactive_email + ) + job.logs += f"Invalid inactive email address on row {source_unique_id}.\n" + counters["rows_with_errors"] += 1 + continue + + if inactive_email == active_email: + job.send_reconciliation_error_email( + recipient_email=active_email, other_email=inactive_email + ) + job.logs += ( + f"Error on row {source_unique_id}: " + f"{active_email} set as both active and inactive email.\n" + ) + counters["rows_with_errors"] += 1 + continue + + _rec_entry = UserReconciliation.objects.create( + active_email=active_email, + inactive_email=inactive_email, + active_email_checked=active_email_checked, + inactive_email_checked=inactive_email_checked, + active_email_confirmation_id=uuid.uuid4(), + inactive_email_confirmation_id=uuid.uuid4(), + source_unique_id=source_unique_id, + status="pending", + ) + counters["rec_entries_created"] += 1 + + return counters + + @app.task def user_reconciliation_csv_import_job(job_id): """Process a UserReconciliationCsvImport job. @@ -32,9 +94,11 @@ def user_reconciliation_csv_import_job(job_id): job.status = "running" job.save() - rec_entries_created = 0 - rows_with_errors = 0 - already_processed_source_ids = 0 + counters = { + "rec_entries_created": 0, + "rows_with_errors": 0, + "already_processed_source_ids": 0, + } try: with job.file.open(mode="r") as f: @@ -46,68 +110,14 @@ def user_reconciliation_csv_import_job(job_id): ) for row in reader: - source_unique_id = row["id"].strip() - - # Skip entries if they already exist with this source_unique_id - if UserReconciliation.objects.filter( - source_unique_id=source_unique_id - ).exists(): - already_processed_source_ids += 1 - continue - - active_email_checked = row.get("active_email_checked", "0") == "1" - inactive_email_checked = row.get("inactive_email_checked", "0") == "1" - - active_email = row["active_email"] - inactive_emails = row["inactive_email"].split("|") - try: - validate_email(active_email) - except ValidationError: - job.send_reconciliation_error_email( - recipient_email=inactive_emails[0], other_email=active_email - ) - job.logs += ( - f"Invalid active email address on row {source_unique_id}." - ) - rows_with_errors += 1 - continue - - for inactive_email in inactive_emails: - try: - validate_email(inactive_email) - except (ValidationError, ValueError): - job.send_reconciliation_error_email( - recipient_email=active_email, other_email=inactive_email - ) - job.logs += f"Invalid inactive email address on row {source_unique_id}.\n" - rows_with_errors += 1 - continue - if inactive_email == active_email: - job.logs += ( - f"Error on row {source_unique_id}: " - f"{active_email} set as both active and inactive email.\n" - ) - rows_with_errors += 1 - continue - - _rec_entry = UserReconciliation.objects.create( - active_email=active_email, - inactive_email=inactive_email, - active_email_checked=active_email_checked, - inactive_email_checked=inactive_email_checked, - active_email_confirmation_id=uuid.uuid4(), - inactive_email_confirmation_id=uuid.uuid4(), - source_unique_id=source_unique_id, - status="pending", - ) - rec_entries_created += 1 + counters = _process_row(row, job, counters) job.status = "done" job.logs += ( f"Import completed successfully. {reader.line_num} rows processed." - f"{rec_entries_created} reconciliation entries created." - f" {already_processed_source_ids} rows were already processed." - f"{rows_with_errors} rows had errors." + f" {counters['rec_entries_created']} reconciliation entries created." + f" {counters['already_processed_source_ids']} rows were already processed." + f" {counters['rows_with_errors']} rows had errors." ) except ( csv.Error, diff --git a/src/backend/core/tests/test_models_user_reconciliation.py b/src/backend/core/tests/test_models_user_reconciliation.py index 1e8f65bc51..dba2231036 100644 --- a/src/backend/core/tests/test_models_user_reconciliation.py +++ b/src/backend/core/tests/test_models_user_reconciliation.py @@ -324,7 +324,7 @@ def test_user_reconciliation_verification_emails_are_sent( active_email_confirmation_id = rec.active_email_confirmation_id inactive_email_confirmation_id = rec.inactive_email_confirmation_id assert ( - f"user_reconciliations/active/{active_email_confirmation_id}/" + f"user-reconciliations/active/{active_email_confirmation_id}/" in email_1_content ) @@ -340,7 +340,7 @@ def test_user_reconciliation_verification_emails_are_sent( ) assert ( - f"user_reconciliations/inactive/{inactive_email_confirmation_id}/" + f"user-reconciliations/inactive/{inactive_email_confirmation_id}/" in email_2_content ) diff --git a/src/backend/core/urls.py b/src/backend/core/urls.py index 42a3449979..97ffb24ea1 100644 --- a/src/backend/core/urls.py +++ b/src/backend/core/urls.py @@ -61,7 +61,7 @@ include(thread_related_router.urls), ), path( - "user_reconciliations///", + "user-reconciliations///", ReconciliationConfirmView.as_view(), ), ] diff --git a/src/backend/impress/settings.py b/src/backend/impress/settings.py index 7510160316..aaf982cb1d 100755 --- a/src/backend/impress/settings.py +++ b/src/backend/impress/settings.py @@ -869,6 +869,11 @@ class Base(Configuration): ), ) + # User accounts management + USER_RECONCILIATION_FORM_URL = values.Value( + None, environ_name="USER_RECONCILIATION_FORM_URL", environ_prefix=None + ) + LASUITE_MARKETING = { "BACKEND": values.Value( "lasuite.marketing.backends.dummy.DummyBackend", diff --git a/src/frontend/apps/impress/src/features/auth/__tests__/UserReconciliation.test.tsx b/src/frontend/apps/impress/src/features/auth/__tests__/UserReconciliation.test.tsx index 4f2d39597a..17ae5b9117 100644 --- a/src/frontend/apps/impress/src/features/auth/__tests__/UserReconciliation.test.tsx +++ b/src/frontend/apps/impress/src/features/auth/__tests__/UserReconciliation.test.tsx @@ -15,7 +15,7 @@ describe('UserReconciliation', () => { ['active', 'inactive'].forEach((type) => { test(`renders when reconciliation is a ${type} success`, async () => { fetchMock.get( - `http://test.jest/api/v1.0/user_reconciliations/${type}/123456/`, + `http://test.jest/api/v1.0/user-reconciliations/${type}/123456/`, { details: 'Success' }, ); @@ -41,7 +41,7 @@ describe('UserReconciliation', () => { test('renders when reconciliation fails', async () => { fetchMock.get( - `http://test.jest/api/v1.0/user_reconciliations/active/invalid-id/`, + `http://test.jest/api/v1.0/user-reconciliations/active/invalid-id/`, { throws: new Error('invalid id'), }, diff --git a/src/frontend/apps/impress/src/features/auth/api/useUserReconciliations.tsx b/src/frontend/apps/impress/src/features/auth/api/useUserReconciliations.tsx index 9185563db9..aa7a88ab46 100644 --- a/src/frontend/apps/impress/src/features/auth/api/useUserReconciliations.tsx +++ b/src/frontend/apps/impress/src/features/auth/api/useUserReconciliations.tsx @@ -16,7 +16,7 @@ export const userReconciliations = async ({ reconciliationId, }: UserReconciliationProps): Promise => { const response = await fetchAPI( - `user_reconciliations/${type}/${reconciliationId}/`, + `user-reconciliations/${type}/${reconciliationId}/`, ); if (!response.ok) { diff --git a/src/mail/mjml/user_template.mjml b/src/mail/mjml/user_template.mjml deleted file mode 100644 index 2dfd46ac3f..0000000000 --- a/src/mail/mjml/user_template.mjml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - -

    {{title|capfirst}}

    - - - - {{message_bold|capfirst}} - - - {{message|capfirst}} -
    {{link_label}} - - - {{button_label}} - - - - {% blocktrans %} - Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. - {% endblocktrans %} - - - -

    - {% blocktrans %} - Brought to you by {{brandname}} - {% endblocktrans %} -

    -
    - - - - - From 313e14db4b408164c9f57476ef7006b945652b47 Mon Sep 17 00:00:00 2001 From: Sylvain Boissel Date: Tue, 3 Feb 2026 10:48:31 +0100 Subject: [PATCH 07/11] =?UTF-8?q?=E2=9C=A8(backend)=20process=20reconcilia?= =?UTF-8?q?tion=20requests=20as=20transactions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Process each reconciliation requests individually as a transaction, with the bulk updates/deletes made for each requests, to avoid interference between requests and unwanted states --- src/backend/core/admin.py | 29 ++---------------------- src/backend/core/models.py | 46 ++++++++++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/src/backend/core/admin.py b/src/backend/core/admin.py index d2034c67f6..b9964de22d 100644 --- a/src/backend/core/admin.py +++ b/src/backend/core/admin.py @@ -2,6 +2,7 @@ from django.contrib import admin, messages from django.contrib.auth import admin as auth_admin +from django.db import transaction from django.shortcuts import redirect from django.utils.translation import gettext_lazy as _ @@ -119,39 +120,13 @@ def process_reconciliation(_modeladmin, _request, queryset): Admin action to process selected user reconciliations. The action will process only entries that are ready and have both emails checked. - Its action is threefold: - - Transfer document accesses from inactive to active user, updating roles as needed. - - Activate the active user and deactivate the inactive user. """ processable_entries = queryset.filter( status="ready", active_email_checked=True, inactive_email_checked=True ) - # Prepare the bulk operations - updated_documentaccess = [] - removed_documentaccess = [] - update_users_active_status = [] - for entry in processable_entries: - new_updated_documentaccess, new_removed_documentaccess = ( - entry.process_documentaccess_reconciliation() - ) - updated_documentaccess += new_updated_documentaccess - removed_documentaccess += new_removed_documentaccess - - entry.active_user.is_active = True - entry.inactive_user.is_active = False - update_users_active_status.append(entry.active_user) - update_users_active_status.append(entry.inactive_user) - - # Actually perform the bulk operations - models.DocumentAccess.objects.bulk_update(updated_documentaccess, ["user", "role"]) - - if removed_documentaccess: - ids_to_delete = [rd.id for rd in removed_documentaccess] - models.DocumentAccess.objects.filter(id__in=ids_to_delete).delete() - - models.User.objects.bulk_update(update_users_active_status, ["is_active"]) + entry.process_reconciliation_request() @admin.register(models.UserReconciliation) diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 31ff11ece8..1a465a521b 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -378,9 +378,44 @@ def save(self, *args, **kwargs): super().save(*args, **kwargs) - def process_documentaccess_reconciliation(self): + @transaction.atomic + def process_reconciliation_request(self): + """ + Process the reconciliation request as a transaction. + + Its action is threefold: + - Transfer document accesses from inactive to active user, updating roles as needed. + - Activate the active user and deactivate the inactive user. + - Update the reconciliation entry itself. + """ + + # Prepare the data to perform the reconciliation on + updated_accesses, removed_accesses = ( + self.prepare_documentaccess_reconciliation() + ) + self.active_user.is_active = True + self.inactive_user.is_active = False + + # Actually perform the bulk operations + DocumentAccess.objects.bulk_update(updated_accesses, ["user", "role"]) + + if removed_accesses: + ids_to_delete = [rd.id for rd in removed_accesses] + DocumentAccess.objects.filter(id__in=ids_to_delete).delete() + + User.objects.bulk_update([self.active_user, self.inactive_user], ["is_active"]) + + # Wrap up the reconciliation entry + self.logs += f"""Requested update for {len(updated_accesses)} DocumentAccess items + and deletion for {len(removed_accesses)} DocumentAccess items.\n""" + self.status = "done" + self.save() + + self.send_reconciliation_done_email() + + def prepare_documentaccess_reconciliation(self): """ - Process the reconciliation by transferring document accesses from the inactive user + Prepare the reconciliation by transferring document accesses from the inactive user to the active user. """ updated_accesses = [] @@ -408,13 +443,6 @@ def process_documentaccess_reconciliation(self): entry.user = self.active_user updated_accesses.append(entry) - self.logs += f"""Requested update for {len(updated_accesses)} DocumentAccess items - and deletion for {len(removed_accesses)} DocumentAccess items.\n""" - self.status = "done" - self.send_reconciliation_done_email() - - self.save() - return updated_accesses, removed_accesses def send_reconciliation_confirm_email( From 61c81decdbc570a702e3520feb8fdfe6849aafd7 Mon Sep 17 00:00:00 2001 From: Sylvain Boissel Date: Tue, 3 Feb 2026 15:55:58 +0100 Subject: [PATCH 08/11] =?UTF-8?q?=E2=9C=A8(backend)=20reconciliation=20req?= =?UTF-8?q?uests=20update=20link=20traces=20and=20doc=20favorites?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds management of link traces and documents favorites in the reconciliation requests. --- src/backend/core/models.py | 70 ++++++++- .../tests/test_models_user_reconciliation.py | 148 +++++++++++++++++- 2 files changed, 212 insertions(+), 6 deletions(-) diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 1a465a521b..c29e445595 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -383,8 +383,9 @@ def process_reconciliation_request(self): """ Process the reconciliation request as a transaction. - Its action is threefold: - Transfer document accesses from inactive to active user, updating roles as needed. + - Transfer document favorites from inactive to active user. + - Transfer link traces from inactive to active user. - Activate the active user and deactivate the inactive user. - Update the reconciliation entry itself. """ @@ -393,6 +394,11 @@ def process_reconciliation_request(self): updated_accesses, removed_accesses = ( self.prepare_documentaccess_reconciliation() ) + updated_linktraces, removed_linktraces = self.prepare_linktrace_reconciliation() + update_favorites, removed_favorites = ( + self.prepare_document_favorite_reconciliation() + ) + self.active_user.is_active = True self.inactive_user.is_active = False @@ -400,9 +406,19 @@ def process_reconciliation_request(self): DocumentAccess.objects.bulk_update(updated_accesses, ["user", "role"]) if removed_accesses: - ids_to_delete = [rd.id for rd in removed_accesses] + ids_to_delete = [entry.id for entry in removed_accesses] DocumentAccess.objects.filter(id__in=ids_to_delete).delete() + DocumentFavorite.objects.bulk_update(update_favorites, ["user"]) + if removed_favorites: + ids_to_delete = [entry.id for entry in removed_favorites] + DocumentFavorite.objects.filter(id__in=ids_to_delete).delete() + + LinkTrace.objects.bulk_update(updated_linktraces, ["user"]) + if removed_linktraces: + ids_to_delete = [entry.id for entry in removed_linktraces] + LinkTrace.objects.filter(id__in=ids_to_delete).delete() + User.objects.bulk_update([self.active_user, self.inactive_user], ["is_active"]) # Wrap up the reconciliation entry @@ -423,9 +439,11 @@ def prepare_documentaccess_reconciliation(self): inactive_accesses = DocumentAccess.objects.filter(user=self.inactive_user) # Check documents where the active user already has access - documents_with_both_users = inactive_accesses.values_list("document", flat=True) + inactive_accesses_documents = inactive_accesses.values_list( + "document", flat=True + ) existing_accesses = DocumentAccess.objects.filter(user=self.active_user).filter( - document__in=documents_with_both_users + document__in=inactive_accesses_documents ) existing_roles_per_doc = dict(existing_accesses.values_list("document", "role")) @@ -445,6 +463,50 @@ def prepare_documentaccess_reconciliation(self): return updated_accesses, removed_accesses + def prepare_document_favorite_reconciliation(self): + """ + Prepare the reconciliation by transferring document favorites from the inactive user + to the active user. + """ + updated_favorites = [] + removed_favorites = [] + + existing_favorites = DocumentFavorite.objects.filter(user=self.active_user) + existing_favorite_doc_ids = set( + existing_favorites.values_list("document_id", flat=True) + ) + + inactive_favorites = DocumentFavorite.objects.filter(user=self.inactive_user) + + for entry in inactive_favorites: + if entry.document_id in existing_favorite_doc_ids: + removed_favorites.append(entry) + else: + entry.user = self.active_user + updated_favorites.append(entry) + + return updated_favorites, removed_favorites + + def prepare_linktrace_reconciliation(self): + """ + Prepare the reconciliation by transferring link traces from the inactive user + to the active user. + """ + updated_linktraces = [] + removed_linktraces = [] + + existing_linktraces = LinkTrace.objects.filter(user=self.active_user) + inactive_linktraces = LinkTrace.objects.filter(user=self.inactive_user) + + for entry in inactive_linktraces: + if existing_linktraces.filter(document=entry.document).exists(): + removed_linktraces.append(entry) + else: + entry.user = self.active_user + updated_linktraces.append(entry) + + return updated_linktraces, removed_linktraces + def send_reconciliation_confirm_email( self, user, user_type, confirmation_id, language=None ): diff --git a/src/backend/core/tests/test_models_user_reconciliation.py b/src/backend/core/tests/test_models_user_reconciliation.py index dba2231036..070f491d33 100644 --- a/src/backend/core/tests/test_models_user_reconciliation.py +++ b/src/backend/core/tests/test_models_user_reconciliation.py @@ -389,10 +389,10 @@ def test_user_reconciliation_only_starts_if_checks_are_made( assert (user_1.is_active, user_2.is_active) == users_active_before -def test_process_documentaccess_reconciliation( +def test_process_reconciliation_updates_accesses( user_reconciliation_users_and_docs, ): - """Use the fixture to verify accesses are consolidated on the active user.""" + """Test that accesses are consolidated on the active user.""" user_1, user_2, userdocs_u1, userdocs_u2 = user_reconciliation_users_and_docs u1_2 = userdocs_u1[2] @@ -456,3 +456,147 @@ def test_process_documentaccess_reconciliation( email_content = " ".join(email.body.split()) assert "Your accounts have been merged" in email_content + + +def test_process_reconciliation_updates_linktraces( + user_reconciliation_users_and_docs, +): + """Test that linktraces are consolidated on the active user.""" + user_1, user_2, userdocs_u1, userdocs_u2 = user_reconciliation_users_and_docs + + u1_2 = userdocs_u1[2] + u1_5 = userdocs_u1[5] + u2doc1 = userdocs_u2[1].document + u2doc5 = userdocs_u2[5].document + + doc_both = u1_2.document + models.LinkTrace.objects.create(document=doc_both, user=user_1) + models.LinkTrace.objects.create(document=doc_both, user=user_2) + + doc_inactive_only = userdocs_u2[4].document + models.LinkTrace.objects.create( + document=doc_inactive_only, user=user_2, is_masked=True + ) + + doc_active_only = userdocs_u1[4].document + models.LinkTrace.objects.create(document=doc_active_only, user=user_1) + + rec = models.UserReconciliation.objects.create( + active_email=user_1.email, + inactive_email=user_2.email, + active_user=user_1, + inactive_user=user_2, + active_email_checked=True, + inactive_email_checked=True, + status="ready", + ) + + qs = models.UserReconciliation.objects.filter(id=rec.id) + process_reconciliation(None, None, qs) + + rec.refresh_from_db() + user_1.refresh_from_db() + user_2.refresh_from_db() + u1_2.refresh_from_db( + from_queryset=models.DocumentAccess.objects.select_for_update() + ) + u1_5.refresh_from_db( + from_queryset=models.DocumentAccess.objects.select_for_update() + ) + + # Inactive user should have no linktraces + assert models.LinkTrace.objects.filter(user=user_2).count() == 0 + + # doc_both should have a single LinkTrace owned by the active user + assert ( + models.LinkTrace.objects.filter(user=user_1, document=doc_both).exists() is True + ) + assert models.LinkTrace.objects.filter(user=user_1, document=doc_both).count() == 1 + assert ( + models.LinkTrace.objects.filter(user=user_2, document=doc_both).exists() + is False + ) + + # doc_inactive_only should now be linked to active user and preserve is_masked + lt = models.LinkTrace.objects.filter( + user=user_1, document=doc_inactive_only + ).first() + assert lt is not None + assert lt.is_masked is True + + # doc_active_only should still belong to active user + assert models.LinkTrace.objects.filter( + user=user_1, document=doc_active_only + ).exists() + + +def test_process_reconciliation_updates_favorites( + user_reconciliation_users_and_docs, +): + """Test that favorites are consolidated on the active user.""" + user_1, user_2, userdocs_u1, userdocs_u2 = user_reconciliation_users_and_docs + + u1_2 = userdocs_u1[2] + u1_5 = userdocs_u1[5] + u2doc1 = userdocs_u2[1].document + u2doc5 = userdocs_u2[5].document + + doc_both = u1_2.document + models.DocumentFavorite.objects.create(document=doc_both, user=user_1) + models.DocumentFavorite.objects.create(document=doc_both, user=user_2) + + doc_inactive_only = userdocs_u2[4].document + models.DocumentFavorite.objects.create(document=doc_inactive_only, user=user_2) + + doc_active_only = userdocs_u1[4].document + models.DocumentFavorite.objects.create(document=doc_active_only, user=user_1) + + rec = models.UserReconciliation.objects.create( + active_email=user_1.email, + inactive_email=user_2.email, + active_user=user_1, + inactive_user=user_2, + active_email_checked=True, + inactive_email_checked=True, + status="ready", + ) + + qs = models.UserReconciliation.objects.filter(id=rec.id) + process_reconciliation(None, None, qs) + + rec.refresh_from_db() + user_1.refresh_from_db() + user_2.refresh_from_db() + u1_2.refresh_from_db( + from_queryset=models.DocumentAccess.objects.select_for_update() + ) + u1_5.refresh_from_db( + from_queryset=models.DocumentAccess.objects.select_for_update() + ) + + # Inactive user should have no document favorites + assert models.DocumentFavorite.objects.filter(user=user_2).count() == 0 + + # doc_both should have a single DocumentFavorite owned by the active user + assert ( + models.DocumentFavorite.objects.filter(user=user_1, document=doc_both).exists() + is True + ) + assert ( + models.DocumentFavorite.objects.filter(user=user_1, document=doc_both).count() + == 1 + ) + assert ( + models.DocumentFavorite.objects.filter(user=user_2, document=doc_both).exists() + is False + ) + + # doc_inactive_only should now be linked to active user + df = models.DocumentFavorite.objects.filter( + user=user_1, document=doc_inactive_only + ).first() + + # doc_active_only should still belong to active user + assert models.DocumentFavorite.objects.filter( + user=user_1, document=doc_active_only + ).exists() From b3e69eaa88b3bf18b14b22229eb1b1ccf85b9abe Mon Sep 17 00:00:00 2001 From: Sylvain Boissel Date: Tue, 3 Feb 2026 19:22:09 +0100 Subject: [PATCH 09/11] =?UTF-8?q?=E2=9C=A8(backend)=20reconciliation=20req?= =?UTF-8?q?uests=20update=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds management of threads, comments and reactions in the reconciliation requests. --- src/backend/core/admin.py | 2 - src/backend/core/models.py | 69 ++++++++++++++++ .../tests/test_models_user_reconciliation.py | 79 +++++++++++++++++-- 3 files changed, 142 insertions(+), 8 deletions(-) diff --git a/src/backend/core/admin.py b/src/backend/core/admin.py index b9964de22d..3d2b0ef786 100644 --- a/src/backend/core/admin.py +++ b/src/backend/core/admin.py @@ -2,7 +2,6 @@ from django.contrib import admin, messages from django.contrib.auth import admin as auth_admin -from django.db import transaction from django.shortcuts import redirect from django.utils.translation import gettext_lazy as _ @@ -119,7 +118,6 @@ def process_reconciliation(_modeladmin, _request, queryset): """ Admin action to process selected user reconciliations. The action will process only entries that are ready and have both emails checked. - """ processable_entries = queryset.filter( status="ready", active_email_checked=True, inactive_email_checked=True diff --git a/src/backend/core/models.py b/src/backend/core/models.py index c29e445595..78bbf3e7c4 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -386,6 +386,8 @@ def process_reconciliation_request(self): - Transfer document accesses from inactive to active user, updating roles as needed. - Transfer document favorites from inactive to active user. - Transfer link traces from inactive to active user. + - Transfer comment-related content from inactive to active user + (threads, comments and reactions) - Activate the active user and deactivate the inactive user. - Update the reconciliation entry itself. """ @@ -398,6 +400,9 @@ def process_reconciliation_request(self): update_favorites, removed_favorites = ( self.prepare_document_favorite_reconciliation() ) + updated_threads = self.prepare_thread_reconciliation() + updated_comments = self.prepare_comment_reconciliation() + updated_reactions, removed_reactions = self.prepare_reaction_reconciliation() self.active_user.is_active = True self.inactive_user.is_active = False @@ -419,6 +424,28 @@ def process_reconciliation_request(self): ids_to_delete = [entry.id for entry in removed_linktraces] LinkTrace.objects.filter(id__in=ids_to_delete).delete() + Thread.objects.bulk_update(updated_threads, ["creator"]) + Comment.objects.bulk_update(updated_comments, ["user"]) + + # pylint: disable=C0103 + ReactionThroughModel = Reaction.users.through + reactions_to_create = [] + for updated_reaction in updated_reactions: + reactions_to_create.append( + ReactionThroughModel( + user_id=self.active_user.pk, reaction_id=updated_reaction.pk + ) + ) + + if reactions_to_create: + ReactionThroughModel.objects.bulk_create(reactions_to_create) + + if removed_reactions: + ids_to_delete = [entry.id for entry in removed_reactions] + ReactionThroughModel.objects.filter( + reaction_id__in=ids_to_delete, user_id=self.inactive_user.pk + ).delete() + User.objects.bulk_update([self.active_user, self.inactive_user], ["is_active"]) # Wrap up the reconciliation entry @@ -507,6 +534,48 @@ def prepare_linktrace_reconciliation(self): return updated_linktraces, removed_linktraces + def prepare_thread_reconciliation(self): + """ + Prepare the reconciliation by transferring threads from the inactive user + to the active user. + """ + updated_threads = [] + + inactive_threads = Thread.objects.filter(creator=self.inactive_user) + + for entry in inactive_threads: + entry.creator = self.active_user + updated_threads.append(entry) + + return updated_threads + + def prepare_comment_reconciliation(self): + """ + Prepare the reconciliation by transferring comments from the inactive user + to the active user. + """ + updated_comments = [] + + inactive_comments = Comment.objects.filter(user=self.inactive_user) + + for entry in inactive_comments: + entry.user = self.active_user + updated_comments.append(entry) + + return updated_comments + + def prepare_reaction_reconciliation(self): + """ + Prepare the reconciliation by creating missing reactions for the active user + (ie, the ones that exist for the inactive user but not the active user) + and then deleting all reactions of the inactive user. + """ + + inactive_reactions = Reaction.objects.filter(users=self.inactive_user) + updated_reactions = inactive_reactions.exclude(users=self.active_user) + + return updated_reactions, inactive_reactions + def send_reconciliation_confirm_email( self, user, user_type, confirmation_id, language=None ): diff --git a/src/backend/core/tests/test_models_user_reconciliation.py b/src/backend/core/tests/test_models_user_reconciliation.py index 070f491d33..d7a063d1a4 100644 --- a/src/backend/core/tests/test_models_user_reconciliation.py +++ b/src/backend/core/tests/test_models_user_reconciliation.py @@ -466,8 +466,6 @@ def test_process_reconciliation_updates_linktraces( u1_2 = userdocs_u1[2] u1_5 = userdocs_u1[5] - u2doc1 = userdocs_u2[1].document - u2doc5 = userdocs_u2[5].document doc_both = u1_2.document models.LinkTrace.objects.create(document=doc_both, user=user_1) @@ -530,6 +528,71 @@ def test_process_reconciliation_updates_linktraces( ).exists() +def test_process_reconciliation_updates_threads_comments_reactions( + user_reconciliation_users_and_docs, +): + """Test that threads, comments and reactions are transferred/deduplicated + on reconciliation.""" + user_1, user_2, _userdocs_u1, userdocs_u2 = user_reconciliation_users_and_docs + + # Use a document from the inactive user's set + document = userdocs_u2[0].document + + # Thread and comment created by inactive user -> should be moved to active + thread = factories.ThreadFactory(document=document, creator=user_2) + comment = factories.CommentFactory(thread=thread, user=user_2) + + # Reaction where only inactive user reacted -> should be moved to active user + reaction_inactive_only = factories.ReactionFactory(comment=comment, users=[user_2]) + + # Reaction where both users reacted -> inactive user's participation should be removed + thread2 = factories.ThreadFactory(document=document, creator=user_1) + comment2 = factories.CommentFactory(thread=thread2, user=user_1) + reaction_both = factories.ReactionFactory(comment=comment2, users=[user_1, user_2]) + + # Reaction where only active user reacted -> unchanged + thread3 = factories.ThreadFactory(document=document, creator=user_1) + comment3 = factories.CommentFactory(thread=thread3, user=user_1) + reaction_active_only = factories.ReactionFactory(comment=comment3, users=[user_1]) + + rec = models.UserReconciliation.objects.create( + active_email=user_1.email, + inactive_email=user_2.email, + active_user=user_1, + inactive_user=user_2, + active_email_checked=True, + inactive_email_checked=True, + status="ready", + ) + + qs = models.UserReconciliation.objects.filter(id=rec.id) + process_reconciliation(None, None, qs) + + # Refresh objects + thread.refresh_from_db() + comment.refresh_from_db() + reaction_inactive_only.refresh_from_db() + reaction_both.refresh_from_db() + reaction_active_only.refresh_from_db() + + # Thread and comment creator should now be the active user + assert thread.creator == user_1 + assert comment.user == user_1 + + # reaction_inactive_only: inactive user's participation should be removed and + # active user's participation added + reaction_inactive_only.refresh_from_db() + assert not reaction_inactive_only.users.filter(pk=user_2.pk).exists() + assert reaction_inactive_only.users.filter(pk=user_1.pk).exists() + + # reaction_both: should end up with only active user's participation + assert reaction_both.users.filter(pk=user_2.pk).exists() is False + assert reaction_both.users.filter(pk=user_1.pk).exists() is True + + # reaction_active_only should still have active user's participation + assert reaction_active_only.users.filter(pk=user_1.pk).exists() + + def test_process_reconciliation_updates_favorites( user_reconciliation_users_and_docs, ): @@ -538,8 +601,6 @@ def test_process_reconciliation_updates_favorites( u1_2 = userdocs_u1[2] u1_5 = userdocs_u1[5] - u2doc1 = userdocs_u2[1].document - u2doc5 = userdocs_u2[5].document doc_both = u1_2.document models.DocumentFavorite.objects.create(document=doc_both, user=user_1) @@ -592,9 +653,15 @@ def test_process_reconciliation_updates_favorites( ) # doc_inactive_only should now be linked to active user - df = models.DocumentFavorite.objects.filter( + assert ( + models.DocumentFavorite.objects.filter( + user=user_2, document=doc_inactive_only + ).count() + == 0 + ) + assert models.DocumentFavorite.objects.filter( user=user_1, document=doc_inactive_only - ).first() + ).exists() # doc_active_only should still belong to active user assert models.DocumentFavorite.objects.filter( From 2a8e51ad766f6a44d00810425a07167389e3656e Mon Sep 17 00:00:00 2001 From: Sylvain Boissel Date: Wed, 4 Feb 2026 10:25:28 +0100 Subject: [PATCH 10/11] =?UTF-8?q?=E2=9C=A8(backend)=20add=20unit=20tests?= =?UTF-8?q?=20for=20the=20api=20view=20for=20reconciliation=20requests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds unit tests for the new API view that is used when a confirmation link is clicked in an email. --- .../tests/test_api_user_reconciliation.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/backend/core/tests/test_api_user_reconciliation.py diff --git a/src/backend/core/tests/test_api_user_reconciliation.py b/src/backend/core/tests/test_api_user_reconciliation.py new file mode 100644 index 0000000000..8f2bd1b8b4 --- /dev/null +++ b/src/backend/core/tests/test_api_user_reconciliation.py @@ -0,0 +1,85 @@ +""" +Unit tests for the ReconciliationConfirmView API view. +""" + +import uuid + +from django.conf import settings + +import pytest +from rest_framework.test import APIClient + +from core import factories, models + +pytestmark = pytest.mark.django_db + + +def test_reconciliation_confirm_view_sets_active_checked(): + """GETting the active confirmation endpoint should set active_email_checked.""" + user = factories.UserFactory(email="user.confirm1@example.com") + other = factories.UserFactory(email="user.confirm2@example.com") + rec = models.UserReconciliation.objects.create( + active_email=user.email, + inactive_email=other.email, + active_user=user, + inactive_user=other, + active_email_checked=False, + inactive_email_checked=False, + status="ready", + ) + + client = APIClient() + conf_id = rec.active_email_confirmation_id + url = f"/api/{settings.API_VERSION}/user-reconciliations/active/{conf_id}/" + resp = client.get(url) + assert resp.status_code == 200 + assert resp.json() == {"detail": "Confirmation received"} + + rec.refresh_from_db() + assert rec.active_email_checked is True + + +def test_reconciliation_confirm_view_sets_inactive_checked(): + """GETting the inactive confirmation endpoint should set inactive_email_checked.""" + user = factories.UserFactory(email="user.confirm3@example.com") + other = factories.UserFactory(email="user.confirm4@example.com") + rec = models.UserReconciliation.objects.create( + active_email=user.email, + inactive_email=other.email, + active_user=user, + inactive_user=other, + active_email_checked=False, + inactive_email_checked=False, + status="ready", + ) + + client = APIClient() + conf_id = rec.inactive_email_confirmation_id + url = f"/api/{settings.API_VERSION}/user-reconciliations/inactive/{conf_id}/" + resp = client.get(url) + assert resp.status_code == 200 + assert resp.json() == {"detail": "Confirmation received"} + + rec.refresh_from_db() + assert rec.inactive_email_checked is True + + +def test_reconciliation_confirm_view_invalid_user_type_returns_400(): + """GETting with an invalid user_type should return 400.""" + client = APIClient() + # Use a valid uuid format but invalid user_type + + url = f"/api/{settings.API_VERSION}/user-reconciliations/other/{uuid.uuid4()}/" + resp = client.get(url) + assert resp.status_code == 400 + assert resp.json() == {"detail": "Invalid user_type"} + + +def test_reconciliation_confirm_view_not_found_returns_404(): + """GETting with a non-existing confirmation_id should return 404.""" + client = APIClient() + + url = f"/api/{settings.API_VERSION}/user-reconciliations/active/{uuid.uuid4()}/" + resp = client.get(url) + assert resp.status_code == 404 + assert resp.json() == {"detail": "Reconciliation entry not found"} From 5a23ecff54730528bd532c70396d60bcbaa4ee9d Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Fri, 6 Feb 2026 11:24:36 +0100 Subject: [PATCH 11/11] =?UTF-8?q?fixup!=20=E2=9C=A8(frontend)=20add=20emai?= =?UTF-8?q?l=20validation=20pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../active/[id]/index.tsx | 0 .../inactive/[id]/index.tsx | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/frontend/apps/impress/src/pages/{user_reconciliations => user-reconciliations}/active/[id]/index.tsx (100%) rename src/frontend/apps/impress/src/pages/{user_reconciliations => user-reconciliations}/inactive/[id]/index.tsx (100%) diff --git a/src/frontend/apps/impress/src/pages/user_reconciliations/active/[id]/index.tsx b/src/frontend/apps/impress/src/pages/user-reconciliations/active/[id]/index.tsx similarity index 100% rename from src/frontend/apps/impress/src/pages/user_reconciliations/active/[id]/index.tsx rename to src/frontend/apps/impress/src/pages/user-reconciliations/active/[id]/index.tsx diff --git a/src/frontend/apps/impress/src/pages/user_reconciliations/inactive/[id]/index.tsx b/src/frontend/apps/impress/src/pages/user-reconciliations/inactive/[id]/index.tsx similarity index 100% rename from src/frontend/apps/impress/src/pages/user_reconciliations/inactive/[id]/index.tsx rename to src/frontend/apps/impress/src/pages/user-reconciliations/inactive/[id]/index.tsx

o3jkhF!Kd^#TI5ivAyGJh9{GyC0gS;s9 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