Skip to content

Commit 4e07df6

Browse files
committed
♻️ Refactor UsersRepo to use instance methods for user creation and linking
1 parent 9302992 commit 4e07df6

File tree

5 files changed

+82
-66
lines changed

5 files changed

+82
-66
lines changed

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

Lines changed: 46 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@ def generate_alternative_username(username: str) -> str:
5555

5656

5757
class UsersRepo:
58+
def __init__(self, engine: AsyncEngine):
59+
self._engine = engine
5860

59-
@staticmethod
6061
async def new_user(
61-
engine: AsyncEngine,
62+
self,
6263
connection: AsyncConnection | None = None,
6364
*,
6465
email: str,
@@ -78,14 +79,14 @@ async def new_user(
7879
user_id = None
7980
while user_id is None:
8081
try:
81-
async with transaction_context(engine, connection) as conn:
82+
async with transaction_context(self._engine, connection) as conn:
8283
user_id = await conn.scalar(
8384
users.insert().values(**data).returning(users.c.id)
8485
)
8586
except IntegrityError:
8687
data["name"] = generate_alternative_username(data["name"])
8788

88-
async with pass_or_acquire_connection(engine, connection) as conn:
89+
async with pass_or_acquire_connection(self._engine, connection) as conn:
8990
result = await conn.execute(
9091
sa.select(
9192
users.c.id,
@@ -97,9 +98,9 @@ async def new_user(
9798
)
9899
return result.one()
99100

100-
@staticmethod
101101
async def link_and_update_user_from_pre_registration(
102-
connection: AsyncConnection,
102+
self,
103+
connection: AsyncConnection | None = None,
103104
*,
104105
new_user_id: int,
105106
new_user_email: str,
@@ -113,48 +114,51 @@ async def link_and_update_user_from_pre_registration(
113114
assert new_user_email # nosec
114115
assert new_user_id > 0 # nosec
115116

116-
# link both tables first
117-
result = await connection.execute(
118-
users_pre_registration_details.update()
119-
.where(users_pre_registration_details.c.pre_email == new_user_email)
120-
.values(user_id=new_user_id)
121-
)
122-
123-
if update_user:
124-
# COPIES some pre-registration details to the users table
125-
pre_columns = (
126-
users_pre_registration_details.c.pre_first_name,
127-
users_pre_registration_details.c.pre_last_name,
128-
# NOTE: pre_phone is not copied since it has to be validated. Otherwise, if
129-
# phone is wrong, currently user won't be able to login!
117+
async with transaction_context(self._engine, connection) as conn:
118+
# link both tables first
119+
result = await conn.execute(
120+
users_pre_registration_details.update()
121+
.where(users_pre_registration_details.c.pre_email == new_user_email)
122+
.values(user_id=new_user_id)
130123
)
131124

132-
assert {c.name for c in pre_columns} == { # nosec
133-
c.name
134-
for c in users_pre_registration_details.columns
135-
if c
136-
not in (
137-
users_pre_registration_details.c.pre_email,
138-
users_pre_registration_details.c.pre_phone,
125+
if update_user:
126+
# COPIES some pre-registration details to the users table
127+
pre_columns = (
128+
users_pre_registration_details.c.pre_first_name,
129+
users_pre_registration_details.c.pre_last_name,
130+
# NOTE: pre_phone is not copied since it has to be validated. Otherwise, if
131+
# phone is wrong, currently user won't be able to login!
139132
)
140-
and c.name.startswith("pre_")
141-
}, "Different pre-cols detected. This code might need an update update"
142133

143-
result = await connection.execute(
144-
sa.select(*pre_columns).where(
145-
users_pre_registration_details.c.pre_email == new_user_email
146-
)
147-
)
148-
if pre_registration_details_data := result.first():
149-
# NOTE: could have many products! which to use?
150-
await connection.execute(
151-
users.update()
152-
.where(users.c.id == new_user_id)
153-
.values(
154-
first_name=pre_registration_details_data.pre_first_name, # type: ignore[union-attr]
155-
last_name=pre_registration_details_data.pre_last_name, # type: ignore[union-attr]
134+
assert {c.name for c in pre_columns} == { # nosec
135+
c.name
136+
for c in users_pre_registration_details.columns
137+
if c
138+
not in (
139+
users_pre_registration_details.c.pre_email,
140+
users_pre_registration_details.c.pre_phone,
141+
)
142+
and c.name.startswith("pre_")
143+
}, "Different pre-cols detected. This code might need an update update"
144+
145+
result = await conn.execute(
146+
sa.select(*pre_columns).where(
147+
users_pre_registration_details.c.pre_email
148+
== new_user_email
149+
# FIXME: product_name!
156150
)
157151
)
152+
if pre_registration_details_data := result.first():
153+
# NOTE: could have many products! which to use?
154+
await conn.execute(
155+
users.update()
156+
.where(users.c.id == new_user_id)
157+
.values(
158+
first_name=pre_registration_details_data.pre_first_name, # type: ignore[union-attr]
159+
last_name=pre_registration_details_data.pre_last_name, # type: ignore[union-attr]
160+
)
161+
)
158162

159163
@staticmethod
160164
def get_billing_details_query(user_id: int):

packages/postgres-database/tests/test_users.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ async def test_new_user(
143143
"status": UserStatus.ACTIVE,
144144
"expires_at": datetime.utcnow(),
145145
}
146-
new_user = await UsersRepo.new_user(asyncpg_engine, **data)
146+
repo = UsersRepo(asyncpg_engine)
147+
new_user = await repo.new_user(**data)
147148

148149
assert new_user.email == data["email"]
149150
assert new_user.status == data["status"]
@@ -153,7 +154,7 @@ async def test_new_user(
153154
assert _generate_username_from_email(other_email) == new_user.name
154155
other_data = {**data, "email": other_email}
155156

156-
other_user = await UsersRepo.new_user(asyncpg_engine, **other_data)
157+
other_user = await repo.new_user(**other_data)
157158
assert other_user.email != new_user.email
158159
assert other_user.name != new_user.name
159160

packages/postgres-database/tests/test_users_details.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -257,16 +257,18 @@ async def test_create_and_link_user_from_pre_registration(
257257
# Invitation link is clicked and the user is created and linked to the pre-registration
258258
async with transaction_context(asyncpg_engine) as connection:
259259
# user gets created
260-
new_user = await UsersRepo.new_user(
261-
asyncpg_engine,
260+
repo = UsersRepo(asyncpg_engine)
261+
new_user = await repo.new_user(
262262
connection,
263263
email=pre_email,
264264
password_hash="123456", # noqa: S106
265265
status=UserStatus.ACTIVE,
266266
expires_at=None,
267267
)
268-
await UsersRepo.link_and_update_user_from_pre_registration(
269-
connection, new_user_id=new_user.id, new_user_email=new_user.email
268+
await repo.link_and_update_user_from_pre_registration(
269+
connection,
270+
new_user_id=new_user.id,
271+
new_user_email=new_user.email,
270272
)
271273

272274
# Verify the user was created and linked
@@ -292,16 +294,18 @@ async def test_get_billing_details_from_pre_registration(
292294

293295
# Create the user
294296
async with transaction_context(asyncpg_engine) as connection:
295-
new_user = await UsersRepo.new_user(
296-
asyncpg_engine,
297+
repo = UsersRepo(asyncpg_engine)
298+
new_user = await repo.new_user(
297299
connection,
298300
email=pre_email,
299301
password_hash="123456", # noqa: S106
300302
status=UserStatus.ACTIVE,
301303
expires_at=None,
302304
)
303-
await UsersRepo.link_and_update_user_from_pre_registration(
304-
connection, new_user_id=new_user.id, new_user_email=new_user.email
305+
await repo.link_and_update_user_from_pre_registration(
306+
connection,
307+
new_user_id=new_user.id,
308+
new_user_email=new_user.email,
305309
)
306310

307311
# Get billing details
@@ -333,16 +337,18 @@ async def test_update_user_from_pre_registration(
333337

334338
# Create the user and link to pre-registration
335339
async with transaction_context(asyncpg_engine) as connection:
336-
new_user = await UsersRepo.new_user(
337-
asyncpg_engine,
340+
repo = UsersRepo(asyncpg_engine)
341+
new_user = await repo.new_user(
338342
connection,
339343
email=pre_email,
340344
password_hash="123456", # noqa: S106
341345
status=UserStatus.ACTIVE,
342346
expires_at=None,
343347
)
344-
await UsersRepo.link_and_update_user_from_pre_registration(
345-
connection, new_user_id=new_user.id, new_user_email=new_user.email
348+
await repo.link_and_update_user_from_pre_registration(
349+
connection,
350+
new_user_id=new_user.id,
351+
new_user_email=new_user.email,
346352
)
347353

348354
# Update the user manually
@@ -361,8 +367,11 @@ async def test_update_user_from_pre_registration(
361367

362368
# Re-link the user to pre-registration, which should override manual updates
363369
async with transaction_context(asyncpg_engine) as connection:
364-
await UsersRepo.link_and_update_user_from_pre_registration(
365-
connection, new_user_id=new_user.id, new_user_email=new_user.email
370+
repo = UsersRepo(asyncpg_engine)
371+
await repo.link_and_update_user_from_pre_registration(
372+
connection,
373+
new_user_id=new_user.id,
374+
new_user_email=new_user.email,
366375
)
367376

368377
result = await connection.execute(
@@ -492,16 +501,18 @@ async def test_user_preregisters_for_multiple_products_with_different_outcomes(
492501

493502
# 3.Now create a user account with the approved pre-registration
494503
async with transaction_context(asyncpg_engine) as connection:
495-
new_user = await UsersRepo.new_user(
496-
asyncpg_engine,
504+
repo = UsersRepo(asyncpg_engine)
505+
new_user = await repo.new_user(
497506
connection,
498507
email=user_email,
499508
password_hash="123456", # noqa: S106
500509
status=UserStatus.ACTIVE,
501510
expires_at=None,
502511
)
503-
await UsersRepo.link_and_update_user_from_pre_registration(
504-
connection, new_user_id=new_user.id, new_user_email=new_user.email
512+
await repo.link_and_update_user_from_pre_registration(
513+
connection,
514+
new_user_id=new_user.id,
515+
new_user_email=new_user.email,
505516
)
506517

507518
# Verify both pre-registrations are linked to the new user

packages/postgres-database/tests/test_utils_users.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ async def user(
4141

4242

4343
async def test_users_repo_get(asyncpg_engine: AsyncEngine, user: dict[str, Any]):
44-
repo = UsersRepo()
44+
repo = UsersRepo(asyncpg_engine)
4545

4646
async with pass_or_acquire_connection(asyncpg_engine) as connection:
4747
assert await repo.get_email(connection, user_id=user["id"]) == user["email"]

services/web/server/src/simcore_service_webserver/login/_auth_service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@ async def create_user(
3232
) -> dict[str, Any]:
3333

3434
asyncpg_engine = get_asyncpg_engine(app)
35+
repo = UsersRepo(asyncpg_engine)
3536
async with transaction_context(asyncpg_engine) as conn:
36-
user = await UsersRepo.new_user(
37-
asyncpg_engine,
37+
user = await repo.new_user(
3838
conn,
3939
email=email,
4040
password_hash=security_service.encrypt_password(password),
4141
status=status_upon_creation,
4242
expires_at=expires_at,
4343
)
44-
await UsersRepo.link_and_update_user_from_pre_registration(
44+
await repo.link_and_update_user_from_pre_registration(
4545
conn, new_user_id=user.id, new_user_email=user.email
4646
)
4747
return dict(user._mapping) # pylint: disable=protected-access # noqa: SLF001

0 commit comments

Comments
 (0)