11from __future__ import annotations
22
33import copy
4+ import logging
45from typing import Any
56
67import ckan .plugins .toolkit as tk
7- from ckan import types
8+ from ckan import model , types
89from ckan .logic import validate
910
10- from ckanext .scientometrics import utils
11+ from ckanext .scientometrics import config , utils
1112from ckanext .scientometrics .logic import schema
13+ from ckanext .scientometrics .model import UserMetric
1214
13-
14- @tk .chained_action
15- def user_create (next_ : Any , context : types .Context , data_dict : dict [str , Any ]):
16- """Attach scientometrics extras to a user when it is created."""
17- user = next_ (context , data_dict )
18- _attach_extras (context , data_dict , user ["id" ])
19- return user
15+ log = logging .getLogger (__name__ )
2016
2117
2218@tk .chained_action
@@ -36,10 +32,8 @@ def user_show(next_: Any, context: types.Context, data_dict: dict[str, Any]):
3632 userobj = context ["model" ].User .get (user_data ["id" ])
3733 if not userobj :
3834 raise tk .ObjectNotFound ("user" )
39- if userobj .plugin_extras and "scientometrics" in userobj .plugin_extras :
40- user_data ["scientometrics" ] = copy .deepcopy (
41- userobj .plugin_extras .get ("scientometrics" , {})
42- )
35+ if userobj .plugin_extras and "scim" in userobj .plugin_extras :
36+ user_data ["scim" ] = copy .deepcopy (userobj .plugin_extras .get ("scim" , {}))
4337 return user_data
4438
4539
@@ -52,73 +46,117 @@ def _attach_extras(context: types.Context, data_dict: dict[str, Any], user_id: s
5246 raise tk .ObjectNotFound ("user" )
5347 extras = copy .deepcopy (userobj .plugin_extras or {})
5448
55- if "scientometrics" not in extras :
56- extras ["scientometrics" ] = {}
57- extras ["scientometrics" ].update (sm_details )
49+ scim = extras .setdefault ("scim" , {})
50+ for source in config .enabled_metrics ():
51+ key = f"{ source } _author_id"
52+ if key in data_dict and not data_dict .get (key ):
53+ scim .pop (key , None )
54+ scim .update (sm_details )
5855 userobj .plugin_extras = extras
5956 userobj .save ()
6057
6158
6259@tk .side_effect_free
63- @validate (schema .scientometrics_update_user_metrics )
64- def scientometrics_update_user_metrics (
65- context : types .Context , data_dict : dict [str , Any ]
66- ) -> dict [str , Any ]:
67- """Update a user's scientometrics metrics.
60+ @validate (schema .scim_update_user_metrics )
61+ def scim_get_user_metrics (context : types .Context , data_dict : dict [str , Any ]) -> dict [str , Any ]:
62+ """Retrieve user scientometrics metrics.
63+
64+ Args:
65+ context (Dict[str, Any]): The CKAN action context.
66+ data_dict (Dict[str, Any]): A dictionary containing:
67+ - "user_id": The ID of the user whose metrics we want to retrieve.
68+
69+ Returns:
70+ Dict[str, Any]: The user's scientometrics metrics keyed by source.
71+ """
72+ user_id_or_name = data_dict ["user_id" ]
73+
74+ user_dict = tk .get_action ("user_show" )({"ignore_auth" : True }, {"id" : user_id_or_name })
75+ records = UserMetric .by_user_id (user_dict ["id" ])
76+ return {record .source : record .dictize ({}) for record in records }
77+
78+
79+ @validate (schema .scim_update_user_metrics )
80+ def scim_update_user_metrics (context : types .Context , data_dict : dict [str , Any ]) -> dict [str , Any ]:
81+ """Create/update a user's scientometrics metrics using author ids.
6882
6983 Args:
7084 context (Context): The CKAN action context.
7185 data_dict (dict[str, Any]): A dictionary containing:
7286 - "user_id": The ID of the user to update.
73- - "requested_sources": A list or dict indicating which metrics to update.
87+ - "requested_sources": A list or dict indicating which metrics to update (subset of extras) .
7488
7589 Returns:
7690 Dict[str, Any]: A dictionary containing the updated metrics.
7791 """
92+ tk .check_access ("scim_update_user_metrics" , context , data_dict )
7893 user_id_or_name = data_dict ["user_id" ]
79- user_dict = tk .get_action ("user_show" )(
80- {"ignore_auth" : True }, {"id" : user_id_or_name }
81- )
94+ user_dict = tk .get_action ("user_show" )({"ignore_auth" : True }, {"id" : user_id_or_name })
8295 user_id = user_dict ["id" ]
8396
84- requested_sources = data_dict ["requested_sources" ]
85- scientometrics_ids = user_dict .get ("scientometrics" , {})
86- updated_metrics = {}
97+ requested_sources = set (data_dict ["requested_sources" ] or [])
98+ records = UserMetric .by_user_id (user_id )
99+ existing = {record .source : record for record in records }
100+ authors = _collect_authors (user_id , existing )
101+ sources = requested_sources & set (authors .keys ()) if requested_sources else set (authors .keys ())
102+ updated_metrics : dict [str , dict [str , Any ]] = {}
87103
88- for metric_id_key , author_id in scientometrics_ids . items () :
89- source = metric_id_key . replace ( "_author_id" , "" )
90- if not source or source not in requested_sources :
104+ for source in sources :
105+ author_id = authors . get ( source )
106+ if not author_id :
91107 continue
92108
93- extracted_metrics = utils .fetch_author_metrics (source + "_author" , author_id )
109+ try :
110+ extracted_metrics = utils .fetch_author_metrics (source + "_author" , author_id )
111+ except tk .ValidationError as exc :
112+ log .warning ("Failed to fetch metrics for user %s source %s: %s" , user_id , source , exc , exc_info = True )
113+ extracted_metrics = {"error" : str (exc )}
94114 if not extracted_metrics :
95115 continue
96116
97- utils .save_metrics_in_flake (user_id , source , author_id , extracted_metrics )
117+ payload = dict (extracted_metrics )
118+ payload ["author_id" ] = author_id
119+ existing_record = existing .get (source )
120+ external_id = payload .get ("external_id" ) or (existing_record .external_id if existing_record else str (author_id ))
121+ external_url = (
122+ payload .get ("external_url" )
123+ or payload .get ("url" )
124+ or (existing_record .external_url if existing_record else None )
125+ )
126+ status = existing_record .status if existing_record else "pending"
127+ external = {"id" : external_id , "url" : external_url }
128+
129+ UserMetric .upsert (
130+ user_id = user_id ,
131+ source = source ,
132+ metrics = payload ,
133+ external = external ,
134+ status = status ,
135+ )
98136 updated_metrics [source ] = extracted_metrics
99137
100- return updated_metrics
101-
138+ model .Session .commit ()
102139
103- @tk .side_effect_free
104- @validate (schema .scientometrics_update_user_metrics )
105- def scientometrics_get_user_metrics (
106- context : types .Context , data_dict : dict [str , Any ]
107- ) -> dict [str , Any ]:
108- """Retrieve user scientometrics metrics.
109-
110- Args:
111- context (Dict[str, Any]): The CKAN action context.
112- data_dict (Dict[str, Any]): A dictionary containing:
113- - "user_id": The ID of the user whose metrics we want to retrieve.
114-
115- Returns:
116- Dict[str, Any]: The user's scientometrics metrics from the flake store.
117- """
118- user_id_or_name = data_dict ["user_id" ]
140+ return updated_metrics
119141
120- user_dict = tk .get_action ("user_show" )(
121- {"ignore_auth" : True }, {"id" : user_id_or_name }
122- )
123142
124- return utils .get_metrics_from_flake (user_dict ["id" ])
143+ @validate (schema .scim_delete_user_metrics )
144+ def scim_delete_user_metrics (context : types .Context , data_dict : dict [str , Any ]) -> int :
145+ """Delete all scientometrics metrics for a user."""
146+ tk .check_access ("scim_delete_user_metrics" , context , data_dict )
147+ user_dict = tk .get_action ("user_show" )({"ignore_auth" : True }, {"id" : data_dict ["user_id" ]})
148+ deleted = UserMetric .delete_by_user_id (user_dict ["id" ])
149+ model .Session .commit ()
150+ return deleted
151+
152+
153+ def _collect_authors (user_id : str , existing : dict [str , UserMetric ]) -> dict [str , str ]:
154+ """Collect authors strictly from user extras (scim or legacy scientometrics)."""
155+ authors : dict [str , str ] = {}
156+ user_obj = model .User .get (user_id )
157+ extras = (user_obj .plugin_extras or {}) if user_obj else {}
158+ scim_extras = extras .get ("scim" ) or extras .get ("scientometrics" ) or {}
159+ for key , val in scim_extras .items ():
160+ if key .endswith ("_author_id" ) and val :
161+ authors [key .removesuffix ("_author_id" )] = val
162+ return authors
0 commit comments