diff --git a/site/setup.cfg b/site/setup.cfg
index d544ff169..4ef350aac 100644
--- a/site/setup.cfg
+++ b/site/setup.cfg
@@ -52,6 +52,7 @@ invenio_config.module =
zenodo_rdm = zenodo_rdm.config
invenio_requests.types =
legacy_record_upgrade = zenodo_rdm.legacy.requests:LegacyRecordUpgrade
+ community_manage_record = zenodo_rdm.legacy.requests:CommunityManageRecord
invenio_access.actions =
media_files_management_action = zenodo_rdm.generators:media_files_management_action
diff --git a/site/zenodo_rdm/legacy/requests/__init__.py b/site/zenodo_rdm/legacy/requests/__init__.py
index 1d1fec7ea..68c51e152 100644
--- a/site/zenodo_rdm/legacy/requests/__init__.py
+++ b/site/zenodo_rdm/legacy/requests/__init__.py
@@ -8,6 +8,7 @@
"""Request types for ZenodoRDM."""
+from .community_manage_record import CommunityManageRecord
from .record_upgrade import LegacyRecordUpgrade
-__all__ = ("LegacyRecordUpgrade",)
+__all__ = ("LegacyRecordUpgrade", "CommunityManageRecord")
diff --git a/site/zenodo_rdm/legacy/requests/community_manage_record.py b/site/zenodo_rdm/legacy/requests/community_manage_record.py
new file mode 100644
index 000000000..634aa1c4c
--- /dev/null
+++ b/site/zenodo_rdm/legacy/requests/community_manage_record.py
@@ -0,0 +1,211 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2023 CERN.
+#
+# Zenodo is free software; you can redistribute it and/or modify
+# it under the terms of the MIT License; see LICENSE file for more details.
+
+"""Zenodo community manage record request."""
+
+from flask_login import current_user
+from invenio_access.permissions import system_identity
+from invenio_communities.proxies import current_communities
+from invenio_pidstore.models import PersistentIdentifier
+from invenio_rdm_records.proxies import (
+ current_rdm_records_service,
+ current_record_communities_service,
+)
+from invenio_rdm_records.records import RDMParent, RDMRecord
+from invenio_records_resources.services.uow import IndexRefreshOp, RecordCommitOp
+from invenio_requests.customizations import CommentEventType, RequestType, actions
+from invenio_requests.proxies import current_events_service, current_requests_service
+from invenio_search.engine import dsl
+
+
+def _remove_permission_flag(record, uow):
+ """Remove can_community_manage_record permission flag."""
+ try:
+ record.parent.permission_flags.pop("can_community_manage_record")
+ record.parent.commit()
+ uow.register(RecordCommitOp(record.parent))
+ except (KeyError, AttributeError):
+ # if field or flag is absent just continue with an action
+ pass
+
+
+def _remove_record_from_communities(record, communities_ids):
+ """Remove record from communities."""
+ if communities_ids:
+ communities = []
+ for community_id in communities_ids:
+ communities.append({"id": community_id})
+
+ data = dict(communities=communities)
+ current_record_communities_service.remove(system_identity, record["id"], data)
+
+
+def _create_comment(request, content, uow):
+ """Create a comment event."""
+ comment = {"payload": {"content": content}}
+
+ current_events_service.create(
+ system_identity, request.id, comment, CommentEventType, uow=uow
+ )
+
+ # make event immediately available in search
+ uow.register(IndexRefreshOp(indexer=current_events_service.indexer))
+
+
+def _get_legacy_records_by_user():
+ """Find legacy records of a specific user."""
+ return current_rdm_records_service.search(
+ system_identity,
+ extra_filter=dsl.query.Bool(
+ "must",
+ must=[
+ dsl.Q("terms", **{"parent.access.owned_by.user": [current_user.id]}),
+ # indicator that record is a legacy one
+ dsl.Q(
+ "exists",
+ field="parent.permission_flags.can_community_manage_record",
+ ),
+ ],
+ ),
+ )
+
+
+def _resolve_record(legacy_record):
+ """Get record byt its pid."""
+ record_pid = PersistentIdentifier.query.filter(
+ PersistentIdentifier.pid_value == legacy_record["id"]
+ ).one()
+ record_id = str(record_pid.object_uuid)
+ return RDMRecord.get_record(record_id)
+
+
+#
+# Actions
+#
+class SubmitAction(actions.SubmitAction):
+ """Submit action."""
+
+ def execute(self, identity, uow):
+ """Execute the submit action."""
+ self.request["title"] = "Communities manage legacy records"
+
+ # example: "May 11, 2024"
+ expires_at = self.request.expires_at.strftime("%B %d, %Y")
+ self.request["description"] = (
+ "
Some of your records, that are going through migration process are part "
+ "of the communities that don't belong to you.Accept this request to keep the old "
+ "behaviour and allow community curators to manage (edit, create new version, add to "
+ "another community, etc.) corresponding record. In case of declining this "
+ "request all your legacy records will be removed from all communities "
+ "that you are not an owner of. If you do not perform any action by "
+ f"{expires_at}, the permission for community curators to manage the record "
+ "will automatically be fully granted.
"
+ )
+
+ super().execute(identity, uow)
+
+
+class AcceptAction(actions.AcceptAction):
+ """Accept action."""
+
+ def execute(self, identity, uow):
+ """Grant permission to manage all legacy records of a user to all the communities."""
+ legacy_records = _get_legacy_records_by_user()
+
+ for hit in legacy_records.hits:
+ # remove flag from record parent, permissions logic will do the rest
+ record = _resolve_record(hit)
+ _remove_permission_flag(record, uow)
+
+ comment = (
+ "You accepted the request. The community curators "
+ "can now manage all of your legacy records."
+ )
+ _create_comment(self.request, comment, uow)
+
+ super().execute(identity, uow)
+
+
+class DeclineAction(actions.DeclineAction):
+ """Decline action."""
+
+ def execute(self, identity, uow):
+ """Deny access to manage legacy records for community curators."""
+ legacy_records = _get_legacy_records_by_user()
+
+ for legacy_record in legacy_records.hits:
+ record = _resolve_record(legacy_record)
+ communities = (
+ legacy_record["parent"].get("communities", None).get("ids", None)
+ )
+ if communities is not None:
+ not_my_comm = []
+ for community_id in communities:
+ com_members = current_communities.service.members.search(
+ system_identity,
+ community_id,
+ extra_filter=dsl.query.Bool(
+ "must",
+ must=[
+ dsl.Q("term", **{"role": "owner"}),
+ ~dsl.Q("term", **{"user_id": current_user.id}),
+ ],
+ ),
+ )
+ if com_members.total > 0:
+ not_my_comm.append(community_id)
+ _remove_record_from_communities(record, not_my_comm)
+ _remove_permission_flag(record, uow)
+
+ super().execute(identity, uow)
+
+
+class ExpireAction(actions.ExpireAction):
+ """Expire action."""
+
+ def execute(self, identity, uow):
+ """Grant permission to manage all legacy records of a user to all the communities."""
+ legacy_records = _get_legacy_records_by_user()
+
+ for hit in legacy_records.hits:
+ # remove flag from record parent, permissions logic will do the rest
+ record = _resolve_record(hit)
+ _remove_permission_flag(record, uow)
+
+ comment = (
+ "The request was expired. The community curators "
+ "can now manage all of your legacy records."
+ )
+ _create_comment(self.request, comment, uow)
+
+ super().execute(identity, uow)
+
+
+#
+# Request
+#
+class CommunityManageRecord(RequestType):
+ """Request for granting permissions to manage a record for its community curators."""
+
+ type_id = "community-manage-record"
+ name = "Community manage record"
+
+ available_actions = {
+ "create": actions.CreateAction,
+ "submit": SubmitAction,
+ "delete": actions.DeleteAction,
+ "accept": AcceptAction,
+ "cancel": actions.CancelAction,
+ "decline": DeclineAction,
+ "expire": ExpireAction,
+ }
+
+ creator_can_be_none = False
+ topic_can_be_none = True
+
+ allowed_creator_ref_types = ["user"]
+ allowed_receiver_ref_types = ["user"]
diff --git a/site/zenodo_rdm/legacy/requests/utils.py b/site/zenodo_rdm/legacy/requests/utils.py
index 137358e5c..27d975431 100644
--- a/site/zenodo_rdm/legacy/requests/utils.py
+++ b/site/zenodo_rdm/legacy/requests/utils.py
@@ -15,6 +15,7 @@
from invenio_requests.resolvers.registry import ResolverRegistry
from invenio_search.engine import dsl
+from .community_manage_record import CommunityManageRecord
from .record_upgrade import LegacyRecordUpgrade
@@ -82,3 +83,26 @@ def submit_record_upgrade_request(record, uow, comment=None):
return current_requests_service.execute_action(
system_identity, request_item._request.id, "submit", data=comment, uow=uow
)
+
+
+@unit_of_work()
+def submit_community_manage_record_request(user_id, uow, comment=None):
+ """Create and submit a CommunityManageRecord request."""
+ type_ = current_request_type_registry.lookup(CommunityManageRecord.type_id)
+ receiver = ResolverRegistry.resolve_entity_proxy({"user": user_id}).resolve()
+ expires_at = datetime.utcnow() + timedelta(weeks=20)
+
+ # create a request
+ request_item = current_requests_service.create(
+ system_identity,
+ {},
+ type_,
+ receiver,
+ expires_at=expires_at,
+ uow=uow,
+ )
+
+ # submit the request
+ return current_requests_service.execute_action(
+ system_identity, request_item._request.id, "submit", data=comment, uow=uow
+ )
diff --git a/templates/semantic-ui/invenio_requests/community-manage-record/user_dashboard.html b/templates/semantic-ui/invenio_requests/community-manage-record/user_dashboard.html
new file mode 100644
index 000000000..b0164ac6c
--- /dev/null
+++ b/templates/semantic-ui/invenio_requests/community-manage-record/user_dashboard.html
@@ -0,0 +1,41 @@
+{# -*- coding: utf-8 -*-
+
+ This file is part of Invenio.
+ Copyright (C) 2023 CERN.
+
+ Invenio is free software; you can redistribute it and/or modify it
+ under the terms of the MIT License; see LICENSE file for more details.
+#}
+
+{#
+ Renders the Community manage record request details page.
+#}
+
+{% extends "invenio_requests/details/index.html" %}
+
+{% set active_dashboard_menu_item = 'requests' %}
+
+{%- block request_header %}
+ {% set back_button_url = url_for("invenio_app_rdm_users.requests") %}
+ {% from "invenio_requests/macros/request_header.html" import inclusion_request_header %}
+ {{ inclusion_request_header(
+ request=invenio_request,
+ accepted=request_is_accepted,
+ back_button_url=back_button_url,
+ back_button_text=_("Back to requests")
+ ) }}
+{%- endblock request_header %}
+
+{% block request_timeline %}
+
+{% endblock request_timeline %}