diff --git a/tests/fixtures/timeseries/timeseries.json b/tests/fixtures/timeseries/timeseries.json index fa3d1644fda83cecb8734eb7ebf0a113fd88660d..bd7c59b1699ffe7f33baa9d0e139714d5b77412a 100644 --- a/tests/fixtures/timeseries/timeseries.json +++ b/tests/fixtures/timeseries/timeseries.json @@ -11,7 +11,8 @@ "data_origin": 0, "data_origin_type": 0, "sampling_height": 7, - "additional_metadata": {} + "additional_metadata": {}, + "programme_id": 0 }, { "station_id": 3, @@ -32,6 +33,7 @@ "Data level": "2", "Frameworks": "GAW-WDCRG NOAA-ESRL", "Station code": "XXX", - "Station name": "Secret" } } + "Station name": "Secret" } }, + "programme_id": 0 } ] diff --git a/tests/fixtures/toardb_pytest.psql b/tests/fixtures/toardb_pytest.psql index 372960d688a8bb1177149bff228bc55cddf13b05..e7da2950203a3e9dfc8ae2855b213763889808ed 100644 --- a/tests/fixtures/toardb_pytest.psql +++ b/tests/fixtures/toardb_pytest.psql @@ -25,6 +25,16 @@ CREATE SCHEMA IF NOT EXISTS toar_convoc; ALTER SCHEMA toar_convoc OWNER TO postgres; +-- +-- Name: services; Type: SCHEMA; Schema: -; Owner: postgres +-- + +CREATE SCHEMA IF NOT EXISTS services; + + +ALTER SCHEMA services OWNER TO postgres; + + -- -- Name: address_standardizer; Type: EXTENSION; Schema: -; Owner: - -- @@ -102,6 +112,18 @@ CREATE EXTENSION IF NOT EXISTS postgis_topology WITH SCHEMA topology; COMMENT ON EXTENSION postgis_topology IS 'PostGIS topology spatial types and functions'; +-- +-- List of contributors for request of special services +-- here: s1: analysis-service +-- + +CREATE TABLE IF NOT EXISTS services.s1_contributors ( + request_id character varying(36) NOT NULL, + timeseries_ids bigint[], + PRIMARY KEY(request_id) +); + + -- -- Name: toar_controlled_vocabulary; Type: EXTENSION -- faked; Schema: -; Owner: - -- @@ -4064,5 +4086,5 @@ ALTER TABLE ONLY public.timeseries ADD CONSTRAINT timeseries_variable_id_dd9603f5_fk_variables_id FOREIGN KEY (variable_id) REFERENCES public.variables(id) DEFERRABLE INITIALLY DEFERRED; ALTER DATABASE postgres - SET search_path=public,tiger,toar_convoc; + SET search_path=public,tiger,toar_convoc,services; diff --git a/tests/test_timeseries.py b/tests/test_timeseries.py index 72701316e1e2e2dd1909eb750c715ed3134747b1..b27d5fceb2a74c12a695b1512a4f5312d165439d 100644 --- a/tests/test_timeseries.py +++ b/tests/test_timeseries.py @@ -4,7 +4,8 @@ import pytest import json from sqlalchemy import insert -from toardb.timeseries.models import Timeseries, timeseries_timeseries_roles_table +from toardb.timeseries.models import Timeseries, timeseries_timeseries_roles_table, \ + s1_contributors_table from toardb.timeseries.models_programme import TimeseriesProgramme from toardb.timeseries.models_role import TimeseriesRole from toardb.stationmeta.models import StationmetaCore, StationmetaGlobal @@ -165,6 +166,12 @@ class TestApps: for entry in metajson: db.execute(insert(timeseries_timeseries_roles_table).values(timeseries_id=entry["timeseries_id"], role_id=entry["role_id"])) db.execute("COMMIT") + infilename = "tests/fixtures/timeseries/timeseries_contributors.json" + with open(infilename) as f: + metajson=json.load(f) + for entry in metajson: + db.execute(insert(s1_contributors_table).values(request_id=entry["request_id"], timeseries_ids=entry["timeseries_ids"])) + db.execute("COMMIT") def test_get_timeseries(self, client, db): @@ -1011,8 +1018,82 @@ class TestApps: expected_status_code = 400 assert response.status_code == expected_status_code expected_response = 'not a valid format: CMOR' + assert response.json() == expected_response + + + def test_register_contributors_list(self, client, db): + response = client.post("/timeseries/register_timeseries_list_of_contributors/5f0df73a-bd0f-48b9-bb17-d5cd36f89598", + data='''[1,2]''', + headers={"email": "s.schroeder@fz-juelich.de"} ) + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_response = '5f0df73a-bd0f-48b9-bb17-d5cd36f89598 successfully registered.' assert response.json() == expected_response + + def test_register_duplicate_contributors_list(self, client, db): + response = client.post("/timeseries/register_timeseries_list_of_contributors/7f0df73a-bd0f-48b9-bb17-d5cd36f89598", + data='''[1,2]''', + headers={"email": "s.schroeder@fz-juelich.de"} ) + expected_status_code = 443 + assert response.status_code == expected_status_code + expected_response = '7f0df73a-bd0f-48b9-bb17-d5cd36f89598 already registered.' + assert response.json() == expected_response + + + def test_request_registered_contributors_list_json(self, client, db): + response = client.get("/timeseries/request_timeseries_list_of_contributors/7f0df73a-bd0f-48b9-bb17-d5cd36f89598?format=json") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_response = [ + {'contact': {'id': 5, + 'organisation': {'city': 'Jülich', + 'contact_url': 'mailto:toar-data@fz-juelich.de', + 'country': 'Germany', + 'homepage': 'https://www.fz-juelich.de', + 'id': 2, + 'kind': 'research', + 'longname': 'Forschungszentrum Jülich', + 'name': 'FZJ', + 'postcode': '52425', + 'street_address': 'Wilhelm-Johnen-Straße' + }, + 'person': None + }, + 'id': 1, + 'role': 'resource provider', + 'status': 'active' + }, + {'contact': {'id': 4, + 'organisation': {'city': 'Dessau-Roßlau', + 'contact_url': 'mailto:immission@uba.de', + 'country': 'Germany', + 'homepage': 'https://www.umweltbundesamt.de', + 'id': 1, + 'kind': 'government', + 'longname': 'Umweltbundesamt', + 'name': 'UBA', + 'postcode': '06844', + 'street_address': 'Wörlitzer Platz 1' + }, + 'person': None + }, + 'id': 2, + 'role': 'resource provider', + 'status': 'active' + } + ] + assert response.json() == expected_response + + + def test_request_registered_contributors_list_text(self, client, db): + response = client.get("/timeseries/request_timeseries_list_of_contributors/7f0df73a-bd0f-48b9-bb17-d5cd36f89598?format=text") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_response = 'organisations: Forschungszentrum Jülich;Umweltbundesamt' + assert response.json() == expected_response + + # 3. tests updating timeseries metadata def test_patch_timeseries_no_description(self, client, db): diff --git a/toardb/timeseries/crud.py b/toardb/timeseries/crud.py index e8cda0b04866e69a95db1761b87bff95c26f85f8..a2e7332ecf3ac7e97834c173b6b4647fb497c414 100644 --- a/toardb/timeseries/crud.py +++ b/toardb/timeseries/crud.py @@ -9,6 +9,7 @@ Create, Read, Update, Delete functionality from sqlalchemy import insert, select, and_, func, text from sqlalchemy.orm import Session, load_only from sqlalchemy.util import _collections +from sqlalchemy.exc import IntegrityError from geoalchemy2.elements import WKBElement, WKTElement from fastapi import File, UploadFile from fastapi.responses import JSONResponse @@ -16,7 +17,7 @@ import datetime as dt import json from . import models from .models import TimeseriesChangelog, timeseries_timeseries_roles_table, \ - timeseries_timeseries_annotations_table + timeseries_timeseries_annotations_table, s1_contributors_table from toardb.stationmeta.models import StationmetaCore, StationmetaGlobal from toardb.stationmeta.schemas import get_coordinates_from_geom, get_geom_from_coordinates, get_coordinates_from_string from toardb.stationmeta.crud import get_stationmeta_by_id, get_stationmeta_core, station_id_exists, get_stationmeta_changelog @@ -586,9 +587,7 @@ def get_contributors_string(programmes, roles): return result -def get_request_contributors(db: Session, format: str = 'text', input_handle: UploadFile = File(...)): - f = input_handle.file - timeseries_ids = [int(line.strip()) for line in f.readlines()] +def get_contributors_list(db: Session, timeseries_ids, format: str = 'text'): # get time series' programmes # join(models.Timeseries, Timeseries.programme_id == TimeseriesProgramme.id) is implicit given due to the foreign key programmes = db.query(models.TimeseriesProgramme) \ @@ -612,6 +611,36 @@ def get_request_contributors(db: Session, format: str = 'text', input_handle: Up return result +def get_request_contributors(db: Session, format: str = 'text', input_handle: UploadFile = File(...)): + f = input_handle.file + timeseries_ids = [int(line.strip()) for line in f.readlines()] + return get_contributors_list(db, timeseries_ids, format) + + +def get_registered_request_contributors(db: Session, rid, format: str = 'text'): + timeseries_ids = db.execute(select([s1_contributors_table]).\ + where(s1_contributors_table.c.request_id == rid)).mappings().first()['timeseries_ids'] + return get_contributors_list(db, timeseries_ids, format) + + +def register_request_contributors(db: Session, rid, ids): + try: + db.execute(insert(s1_contributors_table).values(request_id=rid, timeseries_ids=ids)) + db.commit() + status_code = 200 + message=f'{rid} successfully registered.' + except IntegrityError as e: + error_code = e.orig.pgcode + if error_code == "23505": + status_code = 443 + message=f'{rid} already registered.' + else: + status_code = 442 + message=f'database error: error_code' + result = JSONResponse(status_code=status_code, content=message) + return result + + # is this internal, or should this also go to public REST api? def get_timeseries_roles(db: Session, timeseries_id: int): return db.execute(select([timeseries_timeseries_roles_table]).where(timeseries_timeseries_roles_table.c.timeseries_id == timeseries_id)) diff --git a/toardb/timeseries/models.py b/toardb/timeseries/models.py index f697fb36f5cdbdf98bf706d56bb027353e985fe7..121bcc7b953392e875d8240e46e32bcf373e5c33 100644 --- a/toardb/timeseries/models.py +++ b/toardb/timeseries/models.py @@ -6,6 +6,7 @@ from .models_role import TimeseriesRole, timeseries_timeseries_roles_table from .models_annotation import TimeseriesAnnotation, timeseries_timeseries_annotations_table from .models_programme import TimeseriesProgramme from .models_changelog import TimeseriesChangelog +from .models_contributor import s1_contributors_table from toardb.base import Base from sqlalchemy import Table, Column, Integer, String diff --git a/toardb/timeseries/timeseries.py b/toardb/timeseries/timeseries.py index d5b5e210c48a8a7b84ffc096c7a75e12cd2fb0ba..1a40821059b568e1734c3082cbc36497882e79c8 100644 --- a/toardb/timeseries/timeseries.py +++ b/toardb/timeseries/timeseries.py @@ -24,7 +24,8 @@ from toardb.data.models import Data from toardb.utils.utils import ( get_str_from_value, get_admin_access_rights, - get_timeseries_md_change_access_rights + get_timeseries_md_change_access_rights, + get_register_contributors_access_rights ) @@ -93,17 +94,33 @@ def get_timeseries(station_id: int, variable_id: int, resource_provider: str = N raise HTTPException(status_code=404, detail="Timeseries not found.") return db_timeseries + @router.get('/timeseries_changelog/{timeseries_id}', response_model=List[schemas.TimeseriesChangelog]) def get_timeseries_changelog(timeseries_id: int, db: Session = Depends(get_db)): db_changelog = crud.get_timeseries_changelog(db, timeseries_id=timeseries_id) return db_changelog + @router.get('/timeseries_programme/{name}', response_model=List[schemas.TimeseriesProgramme]) def get_timeseries_programme(name: str, db: Session = Depends(get_db)): db_programme = crud.get_timeseries_programme(db, name=name) return db_programme +@router.get('/timeseries/request_timeseries_list_of_contributors/{rid}', response_model=Union[str,List[schemas.Contributors]]) +def get_registered_request_contributors(rid: str, format: str = 'text', db: Session = Depends(get_db)): + contributors = crud.get_registered_request_contributors(db, rid=rid, format=format) + return contributors + + +@router.post('/timeseries/register_timeseries_list_of_contributors/{rid}') +def register_request_contributors(rid: str, + ids: List[int], + access: dict = Depends(get_register_contributors_access_rights), + db: Session = Depends(get_db)): + return crud.register_request_contributors(db, rid=rid, ids=ids) + + @router.post('/timeseries/request_contributors', response_model=Union[str,List[schemas.Contributors]]) def get_request_contributors(format: str = 'text', file: UploadFile = File(...), db: Session = Depends(get_db)): contributors = crud.get_request_contributors(db, format=format, input_handle=file) diff --git a/toardb/utils/utils.py b/toardb/utils/utils.py index 5bdc4578b7a2ea8dd6af676a2f919477fdee08de..e84a4b2ee89aab6c8a64eb98fa12e980f318ce2f 100644 --- a/toardb/utils/utils.py +++ b/toardb/utils/utils.py @@ -16,6 +16,8 @@ import requests import datetime as dt from toardb.utils.settings import base_geopeas_url, userinfo_endpoint + +# the following statement only if not in testing (pytest) mode! from toardb.utils.database import get_db from toardb.contacts.models import Contact, Organisation, Person from toardb.timeseries.models import Timeseries, TimeseriesRole, timeseries_timeseries_roles_table @@ -38,7 +40,7 @@ def get_access_rights(request: Request, access_right: str = 'admin'): if status_code != 401: userinfo = userinfo.json() if "eduperson_entitlement" in userinfo and \ - f"urn:geant:helmholtz.de:res:toar-data:{access_right}#login-dev.helmholtz.de" \ + f"urn:geant:helmholtz.de:res:toar-data{access_right}#login.helmholtz.de" \ in userinfo["eduperson_entitlement"]: user_name = userinfo["name"] user_email = userinfo["email"] @@ -59,20 +61,24 @@ def get_access_rights(request: Request, access_right: str = 'admin'): def get_admin_access_rights(request: Request): - return get_access_rights(request, 'admin') + return get_access_rights(request, ':admin') def get_station_md_change_access_rights(request: Request): - return get_access_rights(request, 'station-md-change') + return get_access_rights(request, ':station-md-change') def get_timeseries_md_change_access_rights(request: Request): - return get_access_rights(request, 'timeseries-md-change') + return get_access_rights(request, ':timeseries-md-change') def get_data_change_access_rights(request: Request): - return get_access_rights(request, 'data-change') + return get_access_rights(request, ':data-change') + + +def get_register_contributors_access_rights(request: Request): + return get_access_rights(request, ':contributors-register') # function to return code for given value