def _update_users()

in client/securedrop_client/api_jobs/sync.py [0:0]


    def _update_users(session: Session, remote_users: list[SDKUser]) -> None:
        """
        1. Create local user accounts for each remote user that doesn't already exist
        2. Update existing local users
        3. Re-associate any draft replies sent by a user that is about to be deleted
        4. Delete all remaining local user accounts that no longer exist on the server
        """
        deleted_user_id: int | None = None
        local_users = {user.uuid: user for user in session.query(User).all()}
        for remote_user in remote_users:
            local_user = local_users.get(remote_user.uuid)
            if not local_user:  # Create local user account
                new_user = User(
                    uuid=remote_user.uuid,
                    username=remote_user.username,
                    firstname=remote_user.first_name,
                    lastname=remote_user.last_name,
                )
                session.add(new_user)

                # If the new user is the "deleted" user account, store its id in case we need to
                # reassociate draft replies later.
                if new_user.deleted:
                    session.commit()
                    deleted_user_id = new_user.id

                logger.debug(f"Adding account for user with uuid='{new_user.uuid}'")
            else:  # Update existing local users
                # If the local user is the "deleted" user account, store its id in case we need to
                # reassociate draft replies later.
                if local_user.deleted:
                    deleted_user_id = local_user.id

                if local_user.username != remote_user.username:
                    local_user.username = remote_user.username
                if local_user.firstname != remote_user.first_name:
                    local_user.firstname = remote_user.first_name
                if local_user.lastname != remote_user.last_name:
                    local_user.lastname = remote_user.last_name
                del local_users[remote_user.uuid]

        # Delete all remaining local user accounts that no longer exist on the server.
        #
        # In order to support an edge case that can occur on a pre-2.2.0 server that does not create
        # a "deleted" user account, the client will create one locally when there are draft replies
        # that need to be re-associated. Once the "deleted" user account exists on the server, it
        # will replace the local one.
        for uuid, account in local_users.items():
            # Do not delete the local "deleted" user account if there is no "deleted" user account
            # on the server.
            if account.deleted and not deleted_user_id:
                continue

            # Get draft replies sent by the user who's account is about to be deleted.
            draft_replies = session.query(DraftReply).filter_by(journalist_id=account.id).all()

            # Create a local "deleted" user account if there is no "deleted" user account locally or
            # on the server and we are about to delete a user.
            if draft_replies and not account.deleted and not deleted_user_id:
                deleted_user = DeletedUser()
                session.add(deleted_user)
                session.commit()  # commit so that we can retrieve the generated `id`
                deleted_user_id = deleted_user.id
                logger.debug(f"Creating DeletedUser with uuid='{deleted_user.uuid}'")

            # Re-associate draft replies
            for reply in draft_replies:
                reply.journalist_id = deleted_user_id
                logger.debug(f"DraftReply with uuid='{reply.uuid}' re-associated to DeletedUser")

            # Ensure re-associated draft replies are committed to the db before deleting the account
            if draft_replies:
                session.commit()

            session.delete(account)
            logger.debug(f"Deleting account for user with uuid='{uuid}'")

        session.commit()