Skip to content

Commit 5d33fd1

Browse files
pcrespovmrnicegyu11
authored andcommitted
🎨 web-api: Add privacy Field to Profile Endpoints and Retire Legacy Entrypoint (ITISFoundation#7408)
1 parent 39fc8cb commit 5d33fd1

File tree

10 files changed

+64
-79
lines changed

10 files changed

+64
-79
lines changed

‎api/specs/web-server/_users.py‎

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -39,26 +39,14 @@
3939
"/me",
4040
response_model=Envelope[MyProfileGet],
4141
)
42-
async def get_my_profile():
43-
...
42+
async def get_my_profile(): ...
4443

4544

4645
@router.patch(
4746
"/me",
4847
status_code=status.HTTP_204_NO_CONTENT,
4948
)
50-
async def update_my_profile(_body: MyProfilePatch):
51-
...
52-
53-
54-
@router.put(
55-
"/me",
56-
status_code=status.HTTP_204_NO_CONTENT,
57-
deprecated=True,
58-
description="Use PATCH instead",
59-
)
60-
async def replace_my_profile(_body: MyProfilePatch):
61-
...
49+
async def update_my_profile(_body: MyProfilePatch): ...
6250

6351

6452
@router.patch(
@@ -68,25 +56,22 @@ async def replace_my_profile(_body: MyProfilePatch):
6856
async def set_frontend_preference(
6957
preference_id: PreferenceIdentifier,
7058
_body: PatchRequestBody,
71-
):
72-
...
59+
): ...
7360

7461

7562
@router.get(
7663
"/me/tokens",
7764
response_model=Envelope[list[MyTokenGet]],
7865
)
79-
async def list_tokens():
80-
...
66+
async def list_tokens(): ...
8167

8268

8369
@router.post(
8470
"/me/tokens",
8571
response_model=Envelope[MyTokenGet],
8672
status_code=status.HTTP_201_CREATED,
8773
)
88-
async def create_token(_body: MyTokenCreate):
89-
...
74+
async def create_token(_body: MyTokenCreate): ...
9075

9176

9277
@router.get(
@@ -95,24 +80,21 @@ async def create_token(_body: MyTokenCreate):
9580
)
9681
async def get_token(
9782
_path: Annotated[_TokenPathParams, Depends()],
98-
):
99-
...
83+
): ...
10084

10185

10286
@router.delete(
10387
"/me/tokens/{service}",
10488
status_code=status.HTTP_204_NO_CONTENT,
10589
)
106-
async def delete_token(_path: Annotated[_TokenPathParams, Depends()]):
107-
...
90+
async def delete_token(_path: Annotated[_TokenPathParams, Depends()]): ...
10891

10992

11093
@router.get(
11194
"/me/notifications",
11295
response_model=Envelope[list[UserNotification]],
11396
)
114-
async def list_user_notifications():
115-
...
97+
async def list_user_notifications(): ...
11698

11799

118100
@router.post(
@@ -121,8 +103,7 @@ async def list_user_notifications():
121103
)
122104
async def create_user_notification(
123105
_body: UserNotificationCreate,
124-
):
125-
...
106+
): ...
126107

127108

128109
@router.patch(
@@ -132,16 +113,14 @@ async def create_user_notification(
132113
async def mark_notification_as_read(
133114
_path: Annotated[_NotificationPathParams, Depends()],
134115
_body: UserNotificationPatch,
135-
):
136-
...
116+
): ...
137117

138118

139119
@router.get(
140120
"/me/permissions",
141121
response_model=Envelope[list[MyPermissionGet]],
142122
)
143-
async def list_user_permissions():
144-
...
123+
async def list_user_permissions(): ...
145124

146125

147126
#
@@ -154,8 +133,7 @@ async def list_user_permissions():
154133
response_model=Envelope[list[UserGet]],
155134
description="Search among users who are publicly visible to the caller (i.e., me) based on their privacy settings.",
156135
)
157-
async def search_users(_body: UsersSearch):
158-
...
136+
async def search_users(_body: UsersSearch): ...
159137

160138

161139
#
@@ -171,7 +149,7 @@ async def search_users(_body: UsersSearch):
171149
tags=_extra_tags,
172150
)
173151
async def search_users_for_admin(
174-
_query: Annotated[UsersForAdminSearchQueryParams, Depends()]
152+
_query: Annotated[UsersForAdminSearchQueryParams, Depends()],
175153
):
176154
# NOTE: see `Search` in `Common Custom Methods` in https://cloud.google.com/apis/design/custom_methods
177155
...
@@ -182,5 +160,4 @@ async def search_users_for_admin(
182160
response_model=Envelope[UserForAdminGet],
183161
tags=_extra_tags,
184162
)
185-
async def pre_register_user_for_admin(_body: PreRegisteredUserGet):
186-
...
163+
async def pre_register_user_for_admin(_body: PreRegisteredUserGet): ...

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

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
ValidationInfo,
1717
field_validator,
1818
)
19+
from pydantic.config import JsonDict
1920

2021
from ..basic_types import IDStr
2122
from ..emails import LowerCaseEmailStr
@@ -46,11 +47,13 @@
4647

4748

4849
class MyProfilePrivacyGet(OutputSchema):
50+
hide_username: bool
4951
hide_fullname: bool
5052
hide_email: bool
5153

5254

5355
class MyProfilePrivacyPatch(InputSchema):
56+
hide_username: bool | None = None
5457
hide_fullname: bool | None = None
5558
hide_email: bool | None = None
5659

@@ -79,23 +82,33 @@ class MyProfileGet(OutputSchemaWithoutCamelCase):
7982
privacy: MyProfilePrivacyGet
8083
preferences: AggregatedPreferences
8184

85+
@staticmethod
86+
def _update_json_schema_extra(schema: JsonDict) -> None:
87+
schema.update(
88+
{
89+
"examples": [
90+
{
91+
"id": 42,
92+
"login": "[email protected]",
93+
"userName": "bla42",
94+
"role": "admin", # pre
95+
"expirationDate": "2022-09-14", # optional
96+
"preferences": {},
97+
"privacy": {
98+
"hide_username": 0,
99+
"hide_fullname": 0,
100+
"hide_email": 1,
101+
},
102+
},
103+
]
104+
}
105+
)
106+
82107
model_config = ConfigDict(
83108
# NOTE: old models have an hybrid between snake and camel cases!
84109
# Should be unified at some point
85110
populate_by_name=True,
86-
json_schema_extra={
87-
"examples": [
88-
{
89-
"id": 42,
90-
"login": "[email protected]",
91-
"userName": "bla42",
92-
"role": "admin", # pre
93-
"expirationDate": "2022-09-14", # optional
94-
"preferences": {},
95-
"privacy": {"hide_fullname": 0, "hide_email": 1},
96-
},
97-
]
98-
},
111+
json_schema_extra=_update_json_schema_extra,
99112
)
100113

101114
@field_validator("role", mode="before")

‎packages/postgres-database/src/simcore_postgres_database/utils_users.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ class UserNotFoundInRepoError(BaseUserRepoError):
2727

2828

2929
# NOTE: see MyProfilePatch.user_name
30-
_MIN_USERNAME_LEN: Final[int] = 4
30+
MIN_USERNAME_LEN: Final[int] = 4
3131

3232

33-
def _generate_random_chars(length: int = _MIN_USERNAME_LEN) -> str:
33+
def _generate_random_chars(length: int = MIN_USERNAME_LEN) -> str:
3434
"""returns `length` random digit character"""
3535
return "".join(secrets.choice(string.digits) for _ in range(length))
3636

@@ -42,8 +42,8 @@ def _generate_username_from_email(email: str) -> str:
4242
username = re.sub(r"[^a-zA-Z0-9]", "", username).lower()
4343

4444
# Ensure the username is at least 4 characters long
45-
if len(username) < _MIN_USERNAME_LEN:
46-
username += _generate_random_chars(length=_MIN_USERNAME_LEN - len(username))
45+
if len(username) < MIN_USERNAME_LEN:
46+
username += _generate_random_chars(length=MIN_USERNAME_LEN - len(username))
4747

4848
return username
4949

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.61.3
1+
0.61.4

‎services/web/server/setup.cfg‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.61.3
2+
current_version = 0.61.4
33
commit = True
44
message = services/webserver api version: {current_version} → {new_version}
55
tag = False

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

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ openapi: 3.1.0
22
info:
33
title: simcore-service-webserver
44
description: Main service with an interface (http-API & websockets) to the web front-end
5-
version: 0.61.3
5+
version: 0.61.4
66
servers:
77
- url: ''
88
description: webserver
@@ -1180,22 +1180,6 @@ paths:
11801180
application/json:
11811181
schema:
11821182
$ref: '#/components/schemas/Envelope_MyProfileGet_'
1183-
put:
1184-
tags:
1185-
- users
1186-
summary: Replace My Profile
1187-
description: Use PATCH instead
1188-
operationId: replace_my_profile
1189-
requestBody:
1190-
content:
1191-
application/json:
1192-
schema:
1193-
$ref: '#/components/schemas/MyProfilePatch'
1194-
required: true
1195-
responses:
1196-
'204':
1197-
description: Successful Response
1198-
deprecated: true
11991183
patch:
12001184
tags:
12011185
- users
@@ -11734,6 +11718,9 @@ components:
1173411718
last_name: Crespo
1173511719
MyProfilePrivacyGet:
1173611720
properties:
11721+
hideUsername:
11722+
type: boolean
11723+
title: Hideusername
1173711724
hideFullname:
1173811725
type: boolean
1173911726
title: Hidefullname
@@ -11742,11 +11729,17 @@ components:
1174211729
title: Hideemail
1174311730
type: object
1174411731
required:
11732+
- hideUsername
1174511733
- hideFullname
1174611734
- hideEmail
1174711735
title: MyProfilePrivacyGet
1174811736
MyProfilePrivacyPatch:
1174911737
properties:
11738+
hideUsername:
11739+
anyOf:
11740+
- type: boolean
11741+
- type: 'null'
11742+
title: Hideusername
1175011743
hideFullname:
1175111744
anyOf:
1175211745
- type: boolean

‎services/web/server/src/simcore_service_webserver/users/_common/models.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class ToUserUpdateDB(BaseModel):
5555
first_name: str | None = None
5656
last_name: str | None = None
5757

58+
privacy_hide_username: bool | None = None
5859
privacy_hide_fullname: bool | None = None
5960
privacy_hide_email: bool | None = None
6061

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,6 @@ async def get_my_profile(request: web.Request) -> web.Response:
113113

114114

115115
@routes.patch(f"/{API_VTAG}/me", name="update_my_profile")
116-
@routes.put(
117-
f"/{API_VTAG}/me", name="replace_my_profile" # deprecated. Use patch instead
118-
)
119116
@login_required
120117
@permission_required("user.profile.update")
121118
@_handle_users_exceptions

‎services/web/server/tests/unit/isolated/test_users_models.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ def fake_profile_get(faker: Faker) -> MyProfileGet:
3333
user_name=fake_profile["username"],
3434
login=fake_profile["mail"],
3535
role="USER",
36-
privacy=MyProfilePrivacyGet(hide_fullname=True, hide_email=True),
36+
privacy=MyProfilePrivacyGet(
37+
hide_fullname=True, hide_email=True, hide_username=False
38+
),
3739
preferences={},
3840
)
3941

@@ -78,7 +80,7 @@ def test_parsing_output_of_get_user_profile():
7880
"last_name": "",
7981
"role": "Guest",
8082
"gravatar_id": "9d5e02c75fcd4bce1c8861f219f7f8a5",
81-
"privacy": {"hide_email": True, "hide_fullname": False},
83+
"privacy": {"hide_email": True, "hide_fullname": False, "hide_username": False},
8284
"groups": {
8385
"me": {
8486
"gid": 2,
@@ -125,7 +127,7 @@ def test_mapping_update_models_from_rest_to_db():
125127
{
126128
"first_name": "foo",
127129
"userName": "foo1234",
128-
"privacy": {"hideFullname": False},
130+
"privacy": {"hideFullname": False, "hideUsername": True},
129131
}
130132
)
131133

@@ -137,6 +139,7 @@ def test_mapping_update_models_from_rest_to_db():
137139
"first_name": "foo",
138140
"name": "foo1234",
139141
"privacy_hide_fullname": False,
142+
"privacy_hide_username": True,
140143
}
141144

142145

‎services/web/server/tests/unit/with_dbs/03/test_users.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@ async def test_profile_workflow(
503503
assert updated_profile.user_name == "odei123"
504504

505505
assert updated_profile.privacy != my_profile.privacy
506+
assert updated_profile.privacy.hide_username == my_profile.privacy.hide_username
506507
assert updated_profile.privacy.hide_email == my_profile.privacy.hide_email
507508
assert updated_profile.privacy.hide_fullname != my_profile.privacy.hide_fullname
508509

0 commit comments

Comments
 (0)