Skip to content

Commit 9faefc3

Browse files
committed
drafter interface
1 parent 567ffca commit 9faefc3

File tree

7 files changed

+87
-60
lines changed

7 files changed

+87
-60
lines changed

api/specs/web-server/_users.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@
1414
MyProfilePatch,
1515
MyTokenCreate,
1616
MyTokenGet,
17-
MyUsersGetParams,
18-
MyUsersSearchQueryParams,
19-
UserAsAdminGet,
20-
UsersSearchQueryParams,
17+
UserForAdminGet,
18+
UsersForAdminSearchQueryParams,
19+
UsersGetParams,
2120
)
2221
from models_library.api_schemas_webserver.users_preferences import PatchRequestBody
2322
from models_library.generics import Envelope
@@ -151,18 +150,18 @@ async def list_user_permissions():
151150

152151
@router.get(
153152
"/users/{user_id}",
154-
response_model=Envelope[UserAsAdminGet],
153+
response_model=Envelope[UserForAdminGet],
155154
)
156-
async def get_user(_path: Annotated[MyUsersGetParams, Depends()]):
155+
async def get_user(_path: Annotated[UsersGetParams, Depends()]):
157156
...
158157

159158

160-
@router.get(
159+
@router.post(
161160
"/users:search",
162-
response_model=Envelope[list[UserAsAdminGet]],
161+
response_model=Envelope[list[UserForAdminGet]],
163162
description="Search among users who are publicly visible to the caller (i.e., me) based on their privacy settings.",
164163
)
165-
async def search_users(_query: Annotated[MyUsersSearchQueryParams, Depends()]):
164+
async def search_users(_body: Annotated[UsersForAdminSearchQueryParams, Depends()]):
166165
...
167166

168167

@@ -175,17 +174,19 @@ async def search_users(_query: Annotated[MyUsersSearchQueryParams, Depends()]):
175174

176175
@router.get(
177176
"/admin/users:search",
178-
response_model=Envelope[list[UserAsAdminGet]],
177+
response_model=Envelope[list[UserForAdminGet]],
179178
tags=_extra_tags,
180179
)
181-
async def search_users_for_admin(_query: Annotated[UsersSearchQueryParams, Depends()]):
180+
async def search_users_for_admin(
181+
_query: Annotated[UsersForAdminSearchQueryParams, Depends()]
182+
):
182183
# NOTE: see `Search` in `Common Custom Methods` in https://cloud.google.com/apis/design/custom_methods
183184
...
184185

185186

186187
@router.post(
187188
"/admin/users:pre-register",
188-
response_model=Envelope[UserAsAdminGet],
189+
response_model=Envelope[UserForAdminGet],
189190
tags=_extra_tags,
190191
)
191192
async def pre_register_user_for_admin(_body: PreRegisteredUserGet):

packages/models-library/src/models_library/api_schemas_webserver/users.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from common_library.users_enums import UserStatus
1111
from models_library.groups import AccessRightsDict
1212
from pydantic import (
13-
BaseModel,
1413
ConfigDict,
1514
EmailStr,
1615
Field,
@@ -23,6 +22,7 @@
2322
from ..emails import LowerCaseEmailStr
2423
from ..groups import AccessRightsDict, Group, GroupID, GroupsByTypeTuple
2524
from ..products import ProductName
25+
from ..rest_base import RequestParameters
2626
from ..users import (
2727
FirstNameStr,
2828
LastNameStr,
@@ -196,11 +196,11 @@ def _validate_user_name(cls, value: str):
196196
#
197197

198198

199-
class MyUsersGetParams(BaseModel):
199+
class UsersGetParams(RequestParameters):
200200
user_id: UserID
201201

202202

203-
class MyUsersSearchQueryParams(BaseModel):
203+
class UsersSearch(InputSchema):
204204
match_: Annotated[
205205
str,
206206
StringConstraints(strip_whitespace=True, min_length=1, max_length=50),
@@ -212,7 +212,7 @@ class MyUsersSearchQueryParams(BaseModel):
212212
limit: Annotated[int, annotated_types.Interval(ge=1, le=50)] = 10
213213

214214

215-
class UserAsAdminGet(OutputSchema):
215+
class UserGet(OutputSchema):
216216
# Public profile of a user subject to its privacy settings
217217
user_id: UserID
218218
group_id: GroupID
@@ -221,8 +221,12 @@ class UserAsAdminGet(OutputSchema):
221221
last_name: str | None = None
222222
email: EmailStr | None = None
223223

224+
@classmethod
225+
def from_model(cls, args):
226+
...
227+
224228

225-
class UsersSearchQueryParams(BaseModel):
229+
class UsersForAdminSearchQueryParams(RequestParameters):
226230
email: Annotated[
227231
str,
228232
Field(
@@ -233,7 +237,7 @@ class UsersSearchQueryParams(BaseModel):
233237
]
234238

235239

236-
class UserAsAdminGet(OutputSchema):
240+
class UserForAdminGet(OutputSchema):
237241
# ONLY for admins
238242
first_name: str | None
239243
last_name: str | None

services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,40 +1344,31 @@ paths:
13441344
content:
13451345
application/json:
13461346
schema:
1347-
$ref: '#/components/schemas/Envelope_UserAsAdminGet_'
1347+
$ref: '#/components/schemas/Envelope_UserForAdminGet_'
13481348
/v0/users:search:
1349-
get:
1349+
post:
13501350
tags:
13511351
- user
13521352
summary: Search Users
13531353
description: Search among users who are publicly visible to the caller (i.e.,
13541354
me) based on their privacy settings.
13551355
operationId: search_users
13561356
parameters:
1357-
- name: match
1357+
- name: email
13581358
in: query
13591359
required: true
13601360
schema:
13611361
type: string
1362-
minLength: 1
1363-
maxLength: 50
1364-
title: Match
1365-
- name: limit
1366-
in: query
1367-
required: false
1368-
schema:
1369-
type: integer
1370-
maximum: 50
1371-
minimum: 1
1372-
default: 10
1373-
title: Limit
1362+
minLength: 3
1363+
maxLength: 200
1364+
title: Email
13741365
responses:
13751366
'200':
13761367
description: Successful Response
13771368
content:
13781369
application/json:
13791370
schema:
1380-
$ref: '#/components/schemas/Envelope_list_UserAsAdminGet__'
1371+
$ref: '#/components/schemas/Envelope_list_UserForAdminGet__'
13811372
/v0/admin/users:search:
13821373
get:
13831374
tags:
@@ -1400,7 +1391,7 @@ paths:
14001391
content:
14011392
application/json:
14021393
schema:
1403-
$ref: '#/components/schemas/Envelope_list_UserAsAdminGet__'
1394+
$ref: '#/components/schemas/Envelope_list_UserForAdminGet__'
14041395
/v0/admin/users:pre-register:
14051396
post:
14061397
tags:
@@ -1420,7 +1411,7 @@ paths:
14201411
content:
14211412
application/json:
14221413
schema:
1423-
$ref: '#/components/schemas/Envelope_UserAsAdminGet_'
1414+
$ref: '#/components/schemas/Envelope_UserForAdminGet_'
14241415
/v0/wallets:
14251416
get:
14261417
tags:
@@ -8611,19 +8602,19 @@ components:
86118602
title: Error
86128603
type: object
86138604
title: Envelope[Union[WalletGet, NoneType]]
8614-
Envelope_UserAsAdminGet_:
8605+
Envelope_UserForAdminGet_:
86158606
properties:
86168607
data:
86178608
anyOf:
8618-
- $ref: '#/components/schemas/UserAsAdminGet'
8609+
- $ref: '#/components/schemas/UserForAdminGet'
86198610
- type: 'null'
86208611
error:
86218612
anyOf:
86228613
- {}
86238614
- type: 'null'
86248615
title: Error
86258616
type: object
8626-
title: Envelope[UserAsAdminGet]
8617+
title: Envelope[UserForAdminGet]
86278618
Envelope_WalletGetWithAvailableCredits_:
86288619
properties:
86298620
data:
@@ -9257,12 +9248,12 @@ components:
92579248
title: Error
92589249
type: object
92599250
title: Envelope[list[TaskGet]]
9260-
Envelope_list_UserAsAdminGet__:
9251+
Envelope_list_UserForAdminGet__:
92619252
properties:
92629253
data:
92639254
anyOf:
92649255
- items:
9265-
$ref: '#/components/schemas/UserAsAdminGet'
9256+
$ref: '#/components/schemas/UserForAdminGet'
92669257
type: array
92679258
- type: 'null'
92689259
title: Data
@@ -9272,7 +9263,7 @@ components:
92729263
- type: 'null'
92739264
title: Error
92749265
type: object
9275-
title: Envelope[list[UserAsAdminGet]]
9266+
title: Envelope[list[UserForAdminGet]]
92769267
Envelope_list_UserNotification__:
92779268
properties:
92789269
data:
@@ -14395,7 +14386,7 @@ components:
1439514386
- number
1439614387
- e_tag
1439714388
title: UploadedPart
14398-
UserAsAdminGet:
14389+
UserForAdminGet:
1439914390
properties:
1440014391
firstName:
1440114392
anyOf:
@@ -14486,7 +14477,7 @@ components:
1448614477
- country
1448714478
- registered
1448814479
- status
14489-
title: UserAsAdminGet
14480+
title: UserForAdminGet
1449014481
UserNotification:
1449114482
properties:
1449214483
user_id:

services/web/server/src/simcore_service_webserver/users/_common/schemas.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import pycountry
1414
from models_library.api_schemas_webserver._base import InputSchema
15-
from models_library.api_schemas_webserver.users import UserAsAdminGet
15+
from models_library.api_schemas_webserver.users import UserForAdminGet
1616
from models_library.emails import LowerCaseEmailStr
1717
from models_library.users import UserID
1818
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
@@ -110,5 +110,5 @@ def _pre_check_and_normalize_country(cls, v):
110110

111111
# asserts field names are in sync
112112
assert set(PreRegisteredUserGet.model_fields).issubset(
113-
UserAsAdminGet.model_fields
113+
UserForAdminGet.model_fields
114114
) # nosec

services/web/server/src/simcore_service_webserver/users/_users_rest.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
from models_library.api_schemas_webserver.users import (
66
MyProfileGet,
77
MyProfilePatch,
8-
UsersSearchQueryParams,
8+
UserGet,
9+
UsersForAdminSearchQueryParams,
10+
UsersGetParams,
11+
UsersSearch,
912
)
1013
from servicelib.aiohttp import status
1114
from servicelib.aiohttp.requests_validation import (
1215
parse_request_body_as,
16+
parse_request_path_parameters_as,
1317
parse_request_query_parameters_as,
1418
)
1519
from servicelib.rest_constants import RESPONSE_MODEL_POLICY
@@ -133,15 +137,20 @@ async def update_my_profile(request: web.Request) -> web.Response:
133137
#
134138

135139

136-
@routes.get(f"/{API_VTAG}/users:search", name="get_user")
140+
@routes.get(f"/{API_VTAG}/users/{{user_id}}", name="get_user")
137141
@login_required
138142
@permission_required("user.read")
139143
@_handle_users_exceptions
140144
async def get_user(request: web.Request) -> web.Response:
141145
req_ctx = UsersRequestContext.model_validate(request)
142146
assert req_ctx.product_name # nosec
147+
path_params = parse_request_path_parameters_as(UsersGetParams, request)
143148

144-
raise NotImplementedError
149+
user = await _users_service.get_public_user(
150+
request.app, caller_id=req_ctx.user_id, user_id=path_params.user_id
151+
)
152+
153+
return envelope_json_response(UserGet.from_model(user))
145154

146155

147156
@routes.get(f"/{API_VTAG}/users:search", name="search_users")
@@ -152,7 +161,16 @@ async def search_users(request: web.Request) -> web.Response:
152161
req_ctx = UsersRequestContext.model_validate(request)
153162
assert req_ctx.product_name # nosec
154163

155-
raise NotImplementedError
164+
search_params = await parse_request_body_as(UsersSearch, request)
165+
166+
found = await _users_service.search_public_users(
167+
request.app,
168+
caller_id=req_ctx.user_id,
169+
match_=search_params.match_,
170+
limit=search_params.limit,
171+
)
172+
173+
return envelope_json_response([UserGet.from_model(user) for user in found])
156174

157175

158176
#
@@ -171,8 +189,8 @@ async def search_users_for_admin(request: web.Request) -> web.Response:
171189
req_ctx = UsersRequestContext.model_validate(request)
172190
assert req_ctx.product_name # nosec
173191

174-
query_params: UsersSearchQueryParams = parse_request_query_parameters_as(
175-
UsersSearchQueryParams, request
192+
query_params: UsersForAdminSearchQueryParams = parse_request_query_parameters_as(
193+
UsersForAdminSearchQueryParams, request
176194
)
177195

178196
found = await _users_service.search_users(

services/web/server/src/simcore_service_webserver/users/_users_service.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import pycountry
55
from aiohttp import web
6-
from models_library.api_schemas_webserver.users import MyProfilePatch, UserAsAdminGet
6+
from models_library.api_schemas_webserver.users import MyProfilePatch, UserForAdminGet
77
from models_library.basic_types import IDStr
88
from models_library.emails import LowerCaseEmailStr
99
from models_library.groups import GroupID
@@ -43,7 +43,7 @@ async def pre_register_user(
4343
app: web.Application,
4444
profile: PreRegisteredUserGet,
4545
creator_user_id: UserID,
46-
) -> UserAsAdminGet:
46+
) -> UserForAdminGet:
4747

4848
found = await search_users(app, email_glob=profile.email, include_products=False)
4949
if found:
@@ -87,6 +87,19 @@ async def pre_register_user(
8787
#
8888

8989

90+
async def get_public_user(
91+
app: web.Application, *, caller_id: UserID, user_id: UserID
92+
) -> dict[str, Any]:
93+
...
94+
95+
96+
async def search_public_users(
97+
app: web.Application, *, caller_id: UserID, match_: str, limit: int
98+
) -> list[dict[str, Any]]:
99+
100+
...
101+
102+
90103
async def get_user(app: web.Application, user_id: UserID) -> dict[str, Any]:
91104
"""
92105
:raises UserNotFoundError: if missing but NOT if marked for deletion!
@@ -108,7 +121,7 @@ async def get_user_id_from_gid(app: web.Application, primary_gid: GroupID) -> Us
108121

109122
async def search_users(
110123
app: web.Application, email_glob: str, *, include_products: bool = False
111-
) -> list[UserAsAdminGet]:
124+
) -> list[UserForAdminGet]:
112125
# NOTE: this search is deploy-wide i.e. independent of the product!
113126

114127
def _glob_to_sql_like(glob_pattern: str) -> str:
@@ -130,7 +143,7 @@ async def _list_products_or_none(user_id):
130143
return None
131144

132145
return [
133-
UserAsAdminGet(
146+
UserForAdminGet(
134147
first_name=r.first_name or r.pre_first_name,
135148
last_name=r.last_name or r.pre_last_name,
136149
email=r.email or r.pre_email,

0 commit comments

Comments
 (0)