Skip to content

Commit 89f3caf

Browse files
authored
✨ web-api: admin views and actions for user accounts for the PO center (#7560)
1 parent 532f8eb commit 89f3caf

File tree

24 files changed

+2406
-503
lines changed

24 files changed

+2406
-503
lines changed

api/specs/web-server/_auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,8 @@ async def email_confirmation(code: str):
260260

261261
@router.get(
262262
"/auth/captcha",
263-
operation_id="request_captcha",
263+
operation_id="create_captcha",
264264
status_code=status.HTTP_200_OK,
265265
responses={status.HTTP_200_OK: {"content": {"image/png": {}}}},
266266
)
267-
async def request_captcha(): ...
267+
async def create_captcha(): ...

api/specs/web-server/_users.py

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,25 @@
77
from enum import Enum
88
from typing import Annotated
99

10+
from _common import as_query
1011
from fastapi import APIRouter, Depends, status
1112
from models_library.api_schemas_webserver.users import (
1213
MyPermissionGet,
1314
MyProfileGet,
1415
MyProfilePatch,
1516
MyTokenCreate,
1617
MyTokenGet,
17-
UserForAdminGet,
18+
UserAccountApprove,
19+
UserAccountGet,
20+
UserAccountReject,
21+
UserAccountSearchQueryParams,
1822
UserGet,
19-
UsersForAdminSearchQueryParams,
23+
UsersAccountListQueryParams,
2024
UsersSearch,
2125
)
2226
from models_library.api_schemas_webserver.users_preferences import PatchRequestBody
2327
from models_library.generics import Envelope
28+
from models_library.rest_pagination import Page
2429
from models_library.user_preferences import PreferenceIdentifier
2530
from simcore_service_webserver._meta import API_VTAG
2631
from simcore_service_webserver.users._common.schemas import PreRegisteredUserGet
@@ -144,20 +149,46 @@ async def search_users(_body: UsersSearch): ...
144149

145150

146151
@router.get(
147-
"/admin/users:search",
148-
response_model=Envelope[list[UserForAdminGet]],
152+
"/admin/user-accounts",
153+
response_model=Page[UserAccountGet],
149154
tags=_extra_tags,
150155
)
151-
async def search_users_for_admin(
152-
_query: Annotated[UsersForAdminSearchQueryParams, Depends()],
156+
async def list_users_accounts(
157+
_query: Annotated[as_query(UsersAccountListQueryParams), Depends()],
158+
): ...
159+
160+
161+
@router.post(
162+
"/admin/user-accounts:approve",
163+
status_code=status.HTTP_204_NO_CONTENT,
164+
tags=_extra_tags,
165+
)
166+
async def approve_user_account(_body: UserAccountApprove): ...
167+
168+
169+
@router.post(
170+
"/admin/user-accounts:reject",
171+
status_code=status.HTTP_204_NO_CONTENT,
172+
tags=_extra_tags,
173+
)
174+
async def reject_user_account(_body: UserAccountReject): ...
175+
176+
177+
@router.get(
178+
"/admin/user-accounts:search",
179+
response_model=Envelope[list[UserAccountGet]],
180+
tags=_extra_tags,
181+
)
182+
async def search_user_accounts(
183+
_query: Annotated[UserAccountSearchQueryParams, Depends()],
153184
):
154185
# NOTE: see `Search` in `Common Custom Methods` in https://cloud.google.com/apis/design/custom_methods
155186
...
156187

157188

158189
@router.post(
159-
"/admin/users:pre-register",
160-
response_model=Envelope[UserForAdminGet],
190+
"/admin/user-accounts:pre-register",
191+
response_model=Envelope[UserAccountGet],
161192
tags=_extra_tags,
162193
)
163-
async def pre_register_user_for_admin(_body: PreRegisteredUserGet): ...
194+
async def pre_register_user_account(_body: PreRegisteredUserGet): ...

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

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import re
2-
from datetime import date
2+
from datetime import date, datetime
33
from enum import Enum
44
from typing import Annotated, Any, Literal, Self
55

66
import annotated_types
77
from common_library.basic_types import DEFAULT_FACTORY
88
from common_library.dict_tools import remap_keys
9-
from common_library.users_enums import UserStatus
9+
from common_library.users_enums import AccountRequestStatus, UserStatus
1010
from models_library.groups import AccessRightsDict
11+
from models_library.rest_filters import Filters
12+
from models_library.rest_pagination import PageQueryParameters
1113
from pydantic import (
1214
ConfigDict,
1315
EmailStr,
@@ -39,6 +41,7 @@
3941
OutputSchemaWithoutCamelCase,
4042
)
4143
from .groups import MyGroupsGet
44+
from .products import InvitationGenerate
4245
from .users_preferences import AggregatedPreferences
4346

4447
#
@@ -238,7 +241,30 @@ def from_domain_model(cls, data):
238241
return cls.model_validate(data, from_attributes=True)
239242

240243

241-
class UsersForAdminSearchQueryParams(RequestParameters):
244+
class UsersForAdminListFilter(Filters):
245+
# 1. account_request_status: PENDING, REJECTED, APPROVED
246+
# 2. If APPROVED AND user uses the invitation link, then when user is registered,
247+
# it can be in any of these statuses:
248+
# CONFIRMATION_PENDING, ACTIVE, EXPIRED, BANNED, DELETED
249+
#
250+
review_status: Literal["PENDING", "REVIEWED"] | None = None
251+
252+
model_config = ConfigDict(extra="forbid")
253+
254+
255+
class UsersAccountListQueryParams(UsersForAdminListFilter, PageQueryParameters): ...
256+
257+
258+
class UserAccountApprove(InputSchema):
259+
email: EmailStr
260+
invitation: InvitationGenerate | None = None
261+
262+
263+
class UserAccountReject(InputSchema):
264+
email: EmailStr
265+
266+
267+
class UserAccountSearchQueryParams(RequestParameters):
242268
email: Annotated[
243269
str,
244270
Field(
@@ -249,7 +275,7 @@ class UsersForAdminSearchQueryParams(RequestParameters):
249275
]
250276

251277

252-
class UserForAdminGet(OutputSchema):
278+
class UserAccountGet(OutputSchema):
253279
# ONLY for admins
254280
first_name: str | None
255281
last_name: str | None
@@ -269,8 +295,12 @@ class UserForAdminGet(OutputSchema):
269295
),
270296
] = DEFAULT_FACTORY
271297

272-
# authorization
298+
# pre-registration
299+
pre_registration_id: int | None
273300
invited_by: str | None = None
301+
account_request_status: AccountRequestStatus | None
302+
account_request_reviewed_by: UserID | None = None
303+
account_request_reviewed_at: datetime | None = None
274304

275305
# user status
276306
registered: bool

scripts/maintenance/pre_registration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ async def _pre_register_user(
123123
extras: dict[str, Any] = {},
124124
) -> dict[str, Any]:
125125
"""Pre-register a user in the system"""
126-
path = "/v0/admin/users:pre-register"
126+
path = "/v0/admin/user-accounts:pre-register"
127127

128128
user_data = PreRegisterUserRequest(
129129
firstName=first_name,

services/static-webserver/client/source/class/osparc/data/Resources.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,27 +1059,27 @@ qx.Class.define("osparc.data.Resources", {
10591059
endpoints: {
10601060
search: {
10611061
method: "GET",
1062-
url: statics.API + "/admin/users:search?email={email}"
1062+
url: statics.API + "/admin/user-accounts:search?email={email}"
10631063
},
10641064
getPendingUsers: {
10651065
method: "GET",
1066-
url: statics.API + "/admin/users?status=PENDING"
1066+
url: statics.API + "/admin/user-accounts?review_status=PENDING"
10671067
},
10681068
approveUser: {
10691069
method: "POST",
1070-
url: statics.API + "/admin/users:approve"
1070+
url: statics.API + "/admin/user-accounts:approve"
10711071
},
10721072
rejectUser: {
10731073
method: "POST",
1074-
url: statics.API + "/admin/users:reject"
1074+
url: statics.API + "/admin/user-accounts:reject"
10751075
},
10761076
resendConfirmationEmail: {
10771077
method: "POST",
1078-
url: statics.API + "/admin/users:resendConfirmationEmail"
1078+
url: statics.API + "/admin/user-accounts:resendConfirmationEmail"
10791079
},
10801080
preRegister: {
10811081
method: "POST",
1082-
url: statics.API + "/admin/users:pre-register"
1082+
url: statics.API + "/admin/user-accounts:pre-register"
10831083
}
10841084
}
10851085
},

services/web/server/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.65.0
1+
0.66.0

services/web/server/setup.cfg

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.65.0
2+
current_version = 0.66.0
33
commit = True
44
message = services/webserver api version: {current_version} → {new_version}
55
tag = False
@@ -13,13 +13,13 @@ commit_args = --no-verify
1313
addopts = --strict-markers
1414
asyncio_mode = auto
1515
asyncio_default_fixture_loop_scope = function
16-
markers =
16+
markers =
1717
slow: marks tests as slow (deselect with '-m "not slow"')
1818
acceptance_test: "marks tests as 'acceptance tests' i.e. does the system do what the user expects? Typically those are workflows."
1919
testit: "marks test to run during development"
2020
heavy_load: "mark tests that require large amount of data"
2121

2222
[mypy]
23-
plugins =
23+
plugins =
2424
pydantic.mypy
2525
sqlalchemy.ext.mypy.plugin

0 commit comments

Comments
 (0)