diff --git a/.gitignore b/.gitignore index d230d7d5..15d7272e 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,7 @@ opac/webapp/media/images/*.* # nodejs/gulp/etc node_modules/ + +# pip editable install sources +src/ + diff --git a/opac/tests/test_restapi.py b/opac/tests/test_restapi.py index db1cd0d9..645f3846 100644 --- a/opac/tests/test_restapi.py +++ b/opac/tests/test_restapi.py @@ -5,6 +5,7 @@ from flask import current_app, url_for from flask_babelex import gettext as _ from opac_schema.v1 import models +from opac_schema.v1.models import CrossmarkPage from .base import BaseTestCase @@ -563,4 +564,190 @@ def test_sync_issue_not_found(self, mock_get_issue_by_iid): data = resp.get_json() self.assertEqual(resp.status_code, 404) self.assertEqual(data.get("failed"), True) - self.assertEqual(data.get("error"), "issue not found") \ No newline at end of file + self.assertEqual(data.get("error"), "issue not found") + +class RestAPICrossmarkPageTestCase(BaseTestCase): + def load_json_fixture(self, filename): + with open(os.path.join(FIXTURES_PATH, filename)) as f: + return json.load(f) + + def setUp(self): + self.journal_dict = self.load_json_fixture("journal_payload.json") + self.crossmark_payload = { + "doi": "10.1234/example.doi", + "is_doi_active": True, + "language": "pt", + "journal_id": "1678-4464", + "url": "https://example.com/crossmark", + "text": "Crossmark policy text", + } + + def _create_journal(self): + with self.client as client: + client.post( + url_for("restapi.journal"), + data=json.dumps(self.journal_dict), + follow_redirects=True, + content_type="application/json", + ) + + def test_add_crossmarkpolicy(self): + with current_app.app_context(): + self._create_journal() + with self.client as client: + response = client.post( + url_for("restapi.crossmarkpolicy"), + data=json.dumps(self.crossmark_payload), + follow_redirects=True, + content_type="application/json", + ) + self.assertEqual(response.status_code, 200) + data = response.get_json() + self.assertFalse(data["failed"]) + self.assertEqual(data["doi"], self.crossmark_payload["doi"]) + crossmark = CrossmarkPage.objects(doi=self.crossmark_payload["doi"]).first() + self.assertIsNotNone(crossmark) + self.assertEqual(crossmark.doi, self.crossmark_payload["doi"]) + self.assertTrue(crossmark.is_doi_active) + self.assertEqual(crossmark.language, "pt") + + def test_add_crossmarkpolicy_missing_doi(self): + with current_app.app_context(): + self._create_journal() + payload = dict(self.crossmark_payload) + del payload["doi"] + with self.client as client: + response = client.post( + url_for("restapi.crossmarkpolicy"), + data=json.dumps(payload), + follow_redirects=True, + content_type="application/json", + ) + self.assertEqual(response.status_code, 400) + data = response.get_json() + self.assertTrue(data["failed"]) + self.assertIn("doi", data["error"]) + + def test_add_crossmarkpolicy_missing_language(self): + with current_app.app_context(): + self._create_journal() + payload = dict(self.crossmark_payload) + del payload["language"] + with self.client as client: + response = client.post( + url_for("restapi.crossmarkpolicy"), + data=json.dumps(payload), + follow_redirects=True, + content_type="application/json", + ) + self.assertEqual(response.status_code, 400) + data = response.get_json() + self.assertTrue(data["failed"]) + self.assertIn("language", data["error"]) + + def test_add_crossmarkpolicy_missing_journal_id(self): + with current_app.app_context(): + payload = dict(self.crossmark_payload) + del payload["journal_id"] + with self.client as client: + response = client.post( + url_for("restapi.crossmarkpolicy"), + data=json.dumps(payload), + follow_redirects=True, + content_type="application/json", + ) + self.assertEqual(response.status_code, 400) + data = response.get_json() + self.assertTrue(data["failed"]) + self.assertIn("journal_id", data["error"]) + + def test_add_crossmarkpolicy_journal_not_found(self): + with current_app.app_context(): + payload = dict(self.crossmark_payload) + payload["journal_id"] = "9999-9999" + with self.client as client: + response = client.post( + url_for("restapi.crossmarkpolicy"), + data=json.dumps(payload), + follow_redirects=True, + content_type="application/json", + ) + self.assertEqual(response.status_code, 404) + data = response.get_json() + self.assertTrue(data["failed"]) + self.assertEqual(data["error"], "journal not found") + + def test_add_crossmarkpolicy_missing_url(self): + with current_app.app_context(): + self._create_journal() + payload = dict(self.crossmark_payload) + del payload["url"] + with self.client as client: + response = client.post( + url_for("restapi.crossmarkpolicy"), + data=json.dumps(payload), + follow_redirects=True, + content_type="application/json", + ) + self.assertEqual(response.status_code, 400) + data = response.get_json() + self.assertTrue(data["failed"]) + self.assertIn("url", data["error"]) + + def test_add_crossmarkpolicy_missing_text(self): + with current_app.app_context(): + self._create_journal() + payload = dict(self.crossmark_payload) + del payload["text"] + with self.client as client: + response = client.post( + url_for("restapi.crossmarkpolicy"), + data=json.dumps(payload), + follow_redirects=True, + content_type="application/json", + ) + self.assertEqual(response.status_code, 400) + data = response.get_json() + self.assertTrue(data["failed"]) + self.assertIn("text", data["error"]) + + def test_update_crossmarkpolicy(self): + with current_app.app_context(): + self._create_journal() + with self.client as client: + client.post( + url_for("restapi.crossmarkpolicy"), + data=json.dumps(self.crossmark_payload), + follow_redirects=True, + content_type="application/json", + ) + updated_payload = dict(self.crossmark_payload) + updated_payload["doi"] = "10.1234/updated.doi" + updated_payload["is_doi_active"] = False + with self.client as client: + response = client.put( + url_for("restapi.crossmarkpolicy"), + data=json.dumps(updated_payload), + follow_redirects=True, + content_type="application/json", + ) + self.assertEqual(response.status_code, 200) + data = response.get_json() + self.assertFalse(data["failed"]) + crossmark = CrossmarkPage.objects(doi=updated_payload["doi"]).first() + self.assertFalse(crossmark.is_doi_active) + self.assertEqual(crossmark.language, "pt") + self.assertEqual(CrossmarkPage.objects.count(), 1) + + def test_add_crossmarkpolicy_missing_payload(self): + with current_app.app_context(): + with self.client as client: + response = client.post( + url_for("restapi.crossmarkpolicy"), + data=None, + follow_redirects=True, + content_type="application/json", + ) + self.assertEqual(response.status_code, 400) + data = response.get_json() + self.assertTrue(data["failed"]) diff --git a/opac/webapp/controllers.py b/opac/webapp/controllers.py index a80d5d8d..4841ad02 100644 --- a/opac/webapp/controllers.py +++ b/opac/webapp/controllers.py @@ -28,6 +28,7 @@ from opac_schema.v1.models import ( Article, Collection, + CrossmarkPage, Issue, Journal, News, @@ -1944,3 +1945,47 @@ def add_article( ) return article.save() + + +# -------- CROSSMARK -------- + + +def add_crossmark_page(doi, is_doi_active, language, journal, url=None, text=None): + """ + Cria ou atualiza um registro de CrossmarkPage. + + O registro é identificado pela combinação de ``journal`` e ``language``. + + - ``doi``: string, DOI do documento + - ``is_doi_active``: bool, indica se o DOI está ativo + - ``language``: string, código do idioma (máx. 5 caracteres) + - ``journal``: instância de Journal + - ``url``: string, URL da política de atualização + - ``text``: string, texto da política de atualização + + Retorna a instância de CrossmarkPage salva. + """ + crossmark = CrossmarkPage.objects(journal=journal, language=language).first() + + if crossmark is None: + crossmark = CrossmarkPage( + doi=doi, + is_doi_active=is_doi_active, + language=language, + journal=journal, + ) + else: + crossmark.doi = doi + crossmark.is_doi_active = is_doi_active + + crossmark.url = url + crossmark.text = text + + return crossmark.save() + + +def get_crossmark_page_by_doi(doi): + """ + Retorna um CrossmarkPage pelo seu DOI, ou None se não existir. + """ + return CrossmarkPage.objects(doi=doi).first() diff --git a/opac/webapp/main/views.py b/opac/webapp/main/views.py index b7ad9972..bb95446d 100644 --- a/opac/webapp/main/views.py +++ b/opac/webapp/main/views.py @@ -2220,6 +2220,70 @@ def journal_last_issues(*args): return list(controllers.journal_last_issues() or []) +@restapi.route("/crossmarkpolicy", methods=["POST", "PUT"]) +@helper.token_required +def crossmarkpolicy(*args): + """ + Endpoint para criar ou atualizar a política de atualização (CrossmarkPage) de um periódico. + + Payload de exemplo: + { + "doi": "10.1234/example.doi", + "is_doi_active": true, + "language": "pt", + "journal_id": "1678-4464", + "url": "https://example.com/crossmark", + "text": "Crossmark policy text" + } + + O campo ``journal_id`` pode ser um eISSN, pISSN ou scielo_issn do periódico. + """ + payload = request.get_json() + + if not payload: + return jsonify({"failed": True, "error": "missing payload"}), 400 + + doi = payload.get("doi") + if not doi: + return jsonify({"failed": True, "error": "missing field: doi"}), 400 + + language = payload.get("language") + if not language: + return jsonify({"failed": True, "error": "missing field: language"}), 400 + + journal_id = payload.get("journal_id") + if not journal_id: + return jsonify({"failed": True, "error": "missing field: journal_id"}), 400 + + journal = controllers.get_journal_by_issn(issn=journal_id) + if not journal: + return jsonify({"failed": True, "error": "journal not found"}), 404 + + is_doi_active = payload.get("is_doi_active", True) + + url = payload.get("url") + if not url: + return jsonify({"failed": True, "error": "missing field: url"}), 400 + + text = payload.get("text") + if not text: + return jsonify({"failed": True, "error": "missing field: text"}), 400 + + try: + crossmark = controllers.add_crossmark_page( + doi=doi, + is_doi_active=is_doi_active, + language=language, + journal=journal, + url=url, + text=text, + ) + except Exception as ex: + return jsonify({"failed": True, "error": str(ex)}), 500 + else: + return jsonify({"failed": False, "doi": crossmark.doi}), 200 + + def remover_tags_html(texto): soup = BeautifulSoup(texto, 'html.parser') return soup.get_text() diff --git a/requirements.txt b/requirements.txt index ab770352..cf45533b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -94,6 +94,6 @@ zope.interface==5.5.2 tox==4.3.5 PyJWT==2.8.0 tenacity==8.2.3 --e git+https://git@github.com/scieloorg/opac_schema@v2.9.0#egg=Opac_Schema +opac-schema==2.10.0 -e git+https://git@github.com/scieloorg/packtools@4.12.4#egg=packtools -e git+https://github.com/scieloorg/scieloh5m5.git@1.9.5#egg=scieloh5m5