Skip to content

Commit c39d1ca

Browse files
authored
Move retract endpoint to reviewing router (#477)
2 parents 97c435a + e4ce170 commit c39d1ca

File tree

3 files changed

+54
-71
lines changed

3 files changed

+54
-71
lines changed

src/routers/resource_router.py

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,6 @@ def create(self, url_prefix: str) -> APIRouter:
142142
description=f"Submit a {self.resource_name} for review.",
143143
**default_kwargs,
144144
)
145-
router.add_api_route(
146-
path=f"{url_prefix}/{self.resource_name_plural}/retract/{version}/{{identifier}}",
147-
methods={"POST"},
148-
endpoint=self.get_retract_func(),
149-
name=self.resource_name,
150-
description=f"Retract a {self.resource_name}, setting its status to 'draft'.",
151-
**default_kwargs,
152-
)
153145
router.add_api_route(
154146
path=f"{url_prefix}/{self.resource_name_plural}/{version}",
155147
methods={"POST"},
@@ -567,55 +559,6 @@ def submit_resource(
567559

568560
return submit_resource
569561

570-
def get_retract_func(self):
571-
"""Return a function that can be used to retract a single resource."""
572-
573-
def retract_resource(
574-
identifier: str,
575-
user: KeycloakUser = Depends(get_user_or_raise),
576-
):
577-
with DbSession() as session:
578-
resource = self._retrieve_resource(identifier=identifier, session=session) # type: ignore
579-
580-
if not user_can_administer(user, resource.aiod_entry):
581-
# Could choose to instead give same error as if resource does not exist.
582-
msg = (
583-
f"You do not have permission to retract {self.resource_name} {identifier}."
584-
)
585-
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=msg)
586-
587-
query = (
588-
select(Submission)
589-
.where(
590-
Submission.aiod_entry_identifier == resource.aiod_entry.identifier,
591-
)
592-
.order_by(Submission.request_date.desc()) # type: ignore [attr-defined]
593-
)
594-
current_request = session.scalars(query).first()
595-
if current_request is None or not current_request.is_pending:
596-
raise HTTPException(
597-
status_code=status.HTTP_400_BAD_REQUEST,
598-
detail="Cannot retract this asset, as it is not under review.",
599-
)
600-
601-
retraction = Review(
602-
decision=Decision.RETRACTED,
603-
reviewer_identifier=user._subject_identifier,
604-
submission_identifier=current_request.identifier,
605-
)
606-
resource.aiod_entry.status = EntryStatus.DRAFT
607-
session.add(retraction)
608-
session.commit()
609-
return self._wrap_with_headers(
610-
{
611-
"review_identifier": retraction.identifier,
612-
"submission_identifier": current_request.identifier,
613-
"decision": retraction.decision,
614-
}
615-
)
616-
617-
return retract_resource
618-
619562
def _retrieve_resource(
620563
self,
621564
session: Session,

src/routers/review_router.py

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ def create(url_prefix: str) -> APIRouter:
2424
router = APIRouter()
2525
version = "v1"
2626

27+
router.post(
28+
f"{url_prefix}/submissions/retract/{version}/{{submission_identifier}}",
29+
tags=["Reviewing"],
30+
description="Retract an asset under review, setting its status to 'draft'.",
31+
)(retract_submission)
32+
2733
router.get(
2834
f"{url_prefix}/submissions/{version}/",
2935
tags=["Reviewing"],
@@ -64,27 +70,27 @@ def _get_single_submission(
6470
) -> Submission | None:
6571
with DbSession() as session:
6672
has_review = select(1).where(Submission.identifier == Review.submission_identifier).exists()
67-
query = select(Submission).where(~has_review)
68-
73+
submissions = select(Submission).where(~has_review)
6974
if which == ListMode.NEWEST:
70-
query = query.order_by(Submission.request_date.desc()) # type: ignore[attr-defined]
75+
submissions = submissions.order_by(Submission.request_date.desc()) # type: ignore[attr-defined]
7176
if from_requestee is not None:
72-
query = query.where(Submission.requestee_identifier == from_requestee)
77+
submissions = submissions.where(Submission.requestee_identifier == from_requestee)
7378

74-
return session.scalars(query).first()
79+
return session.scalars(submissions).first()
7580

7681

7782
def _get_submissions_by_state(
7883
*,
79-
which: Literal[ListMode.COMPLETED, ListMode.PENDING],
84+
which: Literal[ListMode.COMPLETED, ListMode.PENDING, ListMode.ALL],
8085
from_requestee: str | None = None,
8186
) -> Sequence[Submission]:
8287
with DbSession() as session:
8388
has_review = select(1).where(Submission.identifier == Review.submission_identifier).exists()
89+
submissions = select(Submission)
8490
if which == ListMode.PENDING:
85-
submissions = select(Submission).where(~has_review)
91+
submissions = submissions.where(~has_review)
8692
if which == ListMode.COMPLETED:
87-
submissions = select(Submission).where(has_review)
93+
submissions = submissions.where(has_review)
8894
if from_requestee is not None:
8995
submissions = submissions.where(Submission.requestee_identifier == from_requestee)
9096
return session.scalars(submissions).all()
@@ -98,7 +104,7 @@ def list_submissions(
98104
if mode in [ListMode.NEWEST, ListMode.OLDEST]:
99105
submission = _get_single_submission(which=mode, from_requestee=user_filter) # type: ignore[arg-type]
100106
return [submission] if submission else []
101-
if mode in [ListMode.PENDING, ListMode.COMPLETED]:
107+
if mode in [ListMode.PENDING, ListMode.COMPLETED, ListMode.ALL]:
102108
return _get_submissions_by_state(which=mode, from_requestee=user_filter) # type: ignore[arg-type]
103109
raise ValueError(f"`mode` should be one of {ListMode!r} but is {mode!r}.")
104110

@@ -108,8 +114,7 @@ def get_submission(
108114
user: KeycloakUser = Depends(get_user_or_raise),
109115
session: Session = Depends(get_session),
110116
) -> Submission:
111-
query = select(Submission).where(Submission.identifier == identifier)
112-
submission = session.scalars(query).first()
117+
submission = session.get(Submission, identifier)
113118
if not submission:
114119
raise HTTPException(
115120
status_code=HTTPStatus.NOT_FOUND,
@@ -169,3 +174,35 @@ def _review_resource(
169174
session.add(review)
170175
session.commit()
171176
return review
177+
178+
179+
def retract_submission(
180+
submission_identifier: str,
181+
user: KeycloakUser = Depends(get_user_or_raise),
182+
):
183+
with DbSession() as session:
184+
submission = session.get(Submission, submission_identifier)
185+
if not user_can_administer(user, submission.asset.aiod_entry):
186+
# Could choose to instead give same error as if resource does not exist.
187+
msg = f"You do not have permission to retract {submission.asset_type} {submission.asset.identifier}."
188+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=msg)
189+
190+
if submission is None or not submission.is_pending:
191+
raise HTTPException(
192+
status_code=status.HTTP_400_BAD_REQUEST,
193+
detail="Cannot retract this asset, as it is not under review.",
194+
)
195+
196+
retraction = Review(
197+
decision=Decision.RETRACTED,
198+
reviewer_identifier=user._subject_identifier,
199+
submission_identifier=submission.identifier,
200+
)
201+
submission.asset.aiod_entry.status = EntryStatus.DRAFT
202+
session.add(retraction)
203+
session.commit()
204+
return {
205+
"review_identifier": retraction.identifier,
206+
"submission_identifier": submission.identifier,
207+
"decision": retraction.decision,
208+
}

src/tests/authorization/test_authorization.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,13 @@ def test_unknown_submission_raises_404(client):
206206
[
207207
(REVIEWER, ListMode.PENDING, [2, 3], "Reviewer can see all pending reviews."),
208208
(REVIEWER, ListMode.COMPLETED, [1], "Reviewer can see all completed reviews."),
209+
(REVIEWER, ListMode.ALL, [1,2,3], "Reviewer can see all reviews."),
209210
(ALICE, ListMode.PENDING, [2], "Alice has one pending submission and can not see Bob's."),
210211
(ALICE, ListMode.COMPLETED, [1], "Alice only has one completed submission."),
212+
(ALICE, ListMode.ALL, [1,2], "Alice can see all her reviews, but not Bob's."),
211213
(BOB, ListMode.PENDING, [3], "Bob has one pending submission and can not see Alice's."),
212214
(BOB, ListMode.COMPLETED, [], "Bob has no completed submission."),
215+
(BOB, ListMode.ALL, [3], "Bob can see all his reviews, but not Alice's."),
213216
]
214217
)
215218
def test_submission_by_state_respects_privacy(user: KeycloakUser, mode: ListMode, assets: list[int], reason: str, client: TestClient, publication):
@@ -271,10 +274,10 @@ def test_retrieving_single_submission_works(user: KeycloakUser, mode: ListMode,
271274

272275

273276
def test_user_can_retract_assets(client, publication):
274-
identifier = register_asset(publication, owner=ALICE, status=EntryStatus.SUBMITTED)
277+
register_asset(publication, owner=ALICE, status=EntryStatus.SUBMITTED)
275278
with logged_in_user(ALICE):
276279
response = client.post(
277-
f"/publications/retract/v1/{identifier}", headers={"Authorization": "Fake token"}
280+
f"/submissions/retract/v1/1", headers={"Authorization": "Fake token"}
278281
)
279282
assert "review_identifier" in response.json()
280283
assert Decision.RETRACTED == response.json()["decision"]
@@ -292,7 +295,7 @@ def test_other_user_can_not_retract_assets(client, publication):
292295

293296
with logged_in_user(BOB):
294297
response = client.post(
295-
f"/publications/retract/v1/{identifier}", headers={"Authorization": "Fake token"}
298+
f"/submissions/retract/v1/{identifier}", headers={"Authorization": "Fake token"}
296299
)
297300
assert response.status_code == HTTPStatus.FORBIDDEN, response.json()
298301

0 commit comments

Comments
 (0)