Skip to content

API Reference

User module for user account management and authentication.

This module provides comprehensive user management including account creation, profile updates, authentication, MFA support, email verification, and admin approval workflows.

Exports
  • CRUD: get_all_users, get_users_number, get_users_with_pagination, get_user_by_username, get_user_by_email, get_user_by_id, get_users_admin, create_user, create_signup_user, edit_user, approve_user, verify_user_email, edit_user_password, update_user_photo, update_user_mfa, delete_user
  • Schemas: UsersBase, Users, UsersRead, UsersMe, UsersSignup, UsersCreate, UsersEditPassword, UsersListResponse
  • Models: Users (ORM model)
  • Enums: Gender, Language, WeekDay, UserAccessType
  • Utils: get_user_by_id_or_404, get_admin_users_or_404, check_password_and_hash, check_user_is_active, create_user_default_data, save_user_image_file, delete_user_photo_filesystem

Gender

Bases: Enum

User gender enumeration.

Attributes:

Name Type Description
MALE

Male gender.

FEMALE

Female gender.

UNSPECIFIED

Unspecified or undisclosed gender.

Source code in backend/app/users/users/schema.py
18
19
20
21
22
23
24
25
26
27
28
29
30
class Gender(Enum):
    """
    User gender enumeration.

    Attributes:
        MALE: Male gender.
        FEMALE: Female gender.
        UNSPECIFIED: Unspecified or undisclosed gender.
    """

    MALE = "male"
    FEMALE = "female"
    UNSPECIFIED = "unspecified"

Language

Bases: Enum

Supported application languages.

Attributes:

Name Type Description
CATALAN

Catalan (ca).

CHINESE_SIMPLIFIED

Simplified Chinese (cn).

CHINESE_TRADITIONAL

Traditional Chinese (tw).

GERMAN

German (de).

FRENCH

French (fr).

GALICIAN

Galician (gl).

ITALIAN

Italian (it).

DUTCH

Dutch (nl).

PORTUGUESE

Portuguese (pt).

SLOVENIAN

Slovenian (sl).

SWEDISH

Swedish (sv).

SPANISH

Spanish (es).

ENGLISH_USA

US English (us).

Source code in backend/app/users/users/schema.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class Language(Enum):
    """
    Supported application languages.

    Attributes:
        CATALAN: Catalan (ca).
        CHINESE_SIMPLIFIED: Simplified Chinese (cn).
        CHINESE_TRADITIONAL: Traditional Chinese (tw).
        GERMAN: German (de).
        FRENCH: French (fr).
        GALICIAN: Galician (gl).
        ITALIAN: Italian (it).
        DUTCH: Dutch (nl).
        PORTUGUESE: Portuguese (pt).
        SLOVENIAN: Slovenian (sl).
        SWEDISH: Swedish (sv).
        SPANISH: Spanish (es).
        ENGLISH_USA: US English (us).
    """

    CATALAN = "ca"
    CHINESE_SIMPLIFIED = "cn"
    CHINESE_TRADITIONAL = "tw"
    GERMAN = "de"
    FRENCH = "fr"
    GALICIAN = "gl"
    ITALIAN = "it"
    DUTCH = "nl"
    PORTUGUESE = "pt"
    SLOVENIAN = "sl"
    SWEDISH = "sv"
    SPANISH = "es"
    ENGLISH_USA = "us"

UserAccessType

Bases: Enum

User access level enumeration.

Attributes:

Name Type Description
REGULAR

Standard user access.

ADMIN

Administrative access.

Source code in backend/app/users/users/schema.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
class UserAccessType(Enum):
    """
    User access level enumeration.

    Attributes:
        REGULAR: Standard user access.
        ADMIN: Administrative access.
    """

    REGULAR = "regular"
    ADMIN = "admin"

Users

Bases: UsersBase

Complete users schema with all fields.

Attributes:

Name Type Description
access_type UserAccessType

User access level.

photo_path StrictStr | None

Path to user's photo.

active StrictBool

Whether the user is active.

mfa_enabled StrictBool

Whether MFA is enabled.

mfa_secret StrictStr | None

MFA secret (encrypted at rest).

email_verified StrictBool

Whether email is verified.

pending_admin_approval StrictBool

Whether pending admin approval.

Source code in backend/app/users/users/schema.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
class Users(UsersBase):
    """
    Complete users schema with all fields.

    Attributes:
        access_type: User access level.
        photo_path: Path to user's photo.
        active: Whether the user is active.
        mfa_enabled: Whether MFA is enabled.
        mfa_secret: MFA secret (encrypted at rest).
        email_verified: Whether email is verified.
        pending_admin_approval: Whether pending admin approval.
    """

    access_type: UserAccessType = Field(
        ...,
        description="User access level",
    )
    photo_path: StrictStr | None = Field(
        default=None,
        max_length=250,
        description="Path to user's photo",
    )
    active: StrictBool = Field(
        ...,
        description="Whether the user is active",
    )
    mfa_enabled: StrictBool = Field(
        default=False,
        description="Whether MFA is enabled",
    )
    mfa_secret: StrictStr | None = Field(
        default=None,
        max_length=512,
        description="MFA secret (encrypted at rest)",
    )
    email_verified: StrictBool = Field(
        default=False,
        description="Whether email is verified",
    )
    pending_admin_approval: StrictBool = Field(
        default=False,
        description="Whether pending admin approval",
    )

    model_config = ConfigDict(
        from_attributes=True,
        extra="forbid",
        validate_assignment=True,
        use_enum_values=True,
    )

UsersBase

Bases: BaseModel

Base users schema with common fields.

Attributes:

Name Type Description
name StrictStr

User's full name (1-250 chars).

username StrictStr

Unique username (1-250 chars, alphanumeric and dots).

email EmailStr

User's email address (max 250 chars).

city StrictStr | None

User's city (max 250 chars).

birthdate date | None

User's birthdate.

preferred_language Language

Preferred language.

gender Gender

User's gender.

units Units

User units (metric, imperial).

height StrictInt | None

User's height in centimeters (1-300).

max_heart_rate StrictInt | None

Maximum heart rate in bpm (30-250).

first_day_of_week WeekDay

First day of the week.

currency Currency

User currency (euro, dollar, pound).

Source code in backend/app/users/users/schema.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
class UsersBase(BaseModel):
    """
    Base users schema with common fields.

    Attributes:
        name: User's full name (1-250 chars).
        username: Unique username (1-250 chars, alphanumeric
            and dots).
        email: User's email address (max 250 chars).
        city: User's city (max 250 chars).
        birthdate: User's birthdate.
        preferred_language: Preferred language.
        gender: User's gender.
        units: User units (metric, imperial).
        height: User's height in centimeters (1-300).
        max_heart_rate: Maximum heart rate in bpm (30-250).
        first_day_of_week: First day of the week.
        currency: User currency (euro, dollar, pound).
    """

    name: StrictStr = Field(
        ...,
        min_length=1,
        max_length=250,
        description="User's full name",
    )
    username: StrictStr = Field(
        ...,
        min_length=1,
        max_length=250,
        pattern=r"^[a-zA-Z0-9.]+$",
        description="Unique username (alphanumeric and dots)",
    )
    email: EmailStr = Field(
        ...,
        max_length=250,
        description="User's email address",
    )
    city: StrictStr | None = Field(
        default=None,
        max_length=250,
        description="User's city",
    )
    birthdate: datetime_date | None = Field(
        default=None,
        description="User's birthdate",
    )
    preferred_language: Language = Field(
        default=Language.ENGLISH_USA,
        description="Preferred language",
    )
    gender: Gender = Field(
        default=Gender.UNSPECIFIED,
        description="User's gender",
    )
    units: server_settings_schema.Units = Field(
        default=server_settings_schema.Units.METRIC,
        description="User units (metric, imperial)",
    )
    height: StrictInt | None = Field(
        default=None,
        ge=1,
        le=300,
        description="Height in centimeters",
    )
    max_heart_rate: StrictInt | None = Field(
        default=None,
        ge=30,
        le=250,
        description="Maximum heart rate in bpm",
    )
    first_day_of_week: WeekDay = Field(
        default=WeekDay.MONDAY,
        description="First day of the week",
    )
    currency: server_settings_schema.Currency = Field(
        default=server_settings_schema.Currency.EURO,
        description="User currency (euro, dollar, pound)",
    )

    model_config = ConfigDict(use_enum_values=True)

    @field_validator("birthdate", mode="before")
    @classmethod
    def validate_birthdate(cls, value: datetime_date | str | None) -> str | None:
        """
        Convert birthdate to ISO format string.

        Args:
            value: Birthdate as date object, string, or None.

        Returns:
            ISO format date string or None.
        """
        if value is None:
            return None
        if isinstance(value, datetime_date):
            return value.isoformat()
        return value

validate_birthdate classmethod

validate_birthdate(value)

Convert birthdate to ISO format string.

Parameters:

Name Type Description Default
value date | str | None

Birthdate as date object, string, or None.

required

Returns:

Type Description
str | None

ISO format date string or None.

Source code in backend/app/users/users/schema.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
@field_validator("birthdate", mode="before")
@classmethod
def validate_birthdate(cls, value: datetime_date | str | None) -> str | None:
    """
    Convert birthdate to ISO format string.

    Args:
        value: Birthdate as date object, string, or None.

    Returns:
        ISO format date string or None.
    """
    if value is None:
        return None
    if isinstance(value, datetime_date):
        return value.isoformat()
    return value

UsersCreate

Bases: Users

Users schema for admin user creation.

Attributes:

Name Type Description
password StrictStr

User's password (min 8 chars).

Source code in backend/app/users/users/schema.py
367
368
369
370
371
372
373
374
375
376
377
378
379
380
class UsersCreate(Users):
    """
    Users schema for admin user creation.

    Attributes:
        password: User's password (min 8 chars).
    """

    password: StrictStr = Field(
        ...,
        min_length=8,
        max_length=250,
        description="User's password",
    )

UsersEditPassword

Bases: BaseModel

Schema for password update operations.

Attributes:

Name Type Description
password StrictStr

New password (min 8 chars).

Source code in backend/app/users/users/schema.py
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
class UsersEditPassword(BaseModel):
    """
    Schema for password update operations.

    Attributes:
        password: New password (min 8 chars).
    """

    password: StrictStr = Field(
        ...,
        min_length=8,
        max_length=250,
        description="New password",
    )

    model_config = ConfigDict(
        extra="forbid",
        validate_assignment=True,
    )

UsersListResponse

Bases: BaseModel

Response model for paginated user listing.

Attributes:

Name Type Description
total StrictInt

Total number of user records.

num_records StrictInt | None

Number of records in this response.

page_number StrictInt | None

Current page number.

records list[UsersRead]

List of user records.

Source code in backend/app/users/users/schema.py
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
class UsersListResponse(BaseModel):
    """
    Response model for paginated user listing.

    Attributes:
        total: Total number of user records.
        num_records: Number of records in this response.
        page_number: Current page number.
        records: List of user records.
    """

    total: StrictInt = Field(
        ...,
        ge=0,
        description="Total number of user records",
    )
    num_records: StrictInt | None = Field(
        default=None,
        ge=0,
        description="Number of records in this response",
    )
    page_number: StrictInt | None = Field(
        default=None,
        ge=1,
        description="Current page number",
    )
    records: list[UsersRead] = Field(
        ...,
        description="List of user records",
    )

    model_config = ConfigDict(
        from_attributes=True,
        extra="forbid",
        validate_assignment=True,
    )

UsersMe

Bases: UsersRead

Extended users schema for current user profile.

Includes privacy settings and integration status.

Attributes:

Name Type Description
is_strava_linked StrictInt | None

Strava integration status.

is_garminconnect_linked StrictInt | None

Garmin Connect status.

default_activity_visibility StrictStr | None

Default visibility level.

hide_activity_start_time StrictBool | None

Hide start time setting.

hide_activity_location StrictBool | None

Hide location setting.

hide_activity_map StrictBool | None

Hide map setting.

hide_activity_hr StrictBool | None

Hide heart rate setting.

hide_activity_power StrictBool | None

Hide power setting.

hide_activity_cadence StrictBool | None

Hide cadence setting.

hide_activity_elevation StrictBool | None

Hide elevation setting.

hide_activity_speed StrictBool | None

Hide speed setting.

hide_activity_pace StrictBool | None

Hide pace setting.

hide_activity_laps StrictBool | None

Hide laps setting.

hide_activity_workout_sets_steps StrictBool | None

Hide workout sets/steps.

hide_activity_gear StrictBool | None

Hide gear setting.

Source code in backend/app/users/users/schema.py
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
class UsersMe(UsersRead):
    """
    Extended users schema for current user profile.

    Includes privacy settings and integration status.

    Attributes:
        is_strava_linked: Strava integration status.
        is_garminconnect_linked: Garmin Connect status.
        default_activity_visibility: Default visibility level.
        hide_activity_start_time: Hide start time setting.
        hide_activity_location: Hide location setting.
        hide_activity_map: Hide map setting.
        hide_activity_hr: Hide heart rate setting.
        hide_activity_power: Hide power setting.
        hide_activity_cadence: Hide cadence setting.
        hide_activity_elevation: Hide elevation setting.
        hide_activity_speed: Hide speed setting.
        hide_activity_pace: Hide pace setting.
        hide_activity_laps: Hide laps setting.
        hide_activity_workout_sets_steps: Hide workout
            sets/steps.
        hide_activity_gear: Hide gear setting.
    """

    is_strava_linked: StrictInt | None = Field(
        default=None, description="Whether Strava is linked"
    )
    is_garminconnect_linked: StrictInt | None = Field(
        default=None, description="Whether Garmin Connect is linked"
    )
    default_activity_visibility: StrictStr | None = Field(
        default=None, description="Default activity visibility"
    )
    hide_activity_start_time: StrictBool | None = Field(
        default=None, description="Hide activity start time"
    )
    hide_activity_location: StrictBool | None = Field(
        default=None, description="Hide activity location"
    )
    hide_activity_map: StrictBool | None = Field(
        default=None, description="Hide activity map"
    )
    hide_activity_hr: StrictBool | None = Field(
        default=None, description="Hide activity heart rate"
    )
    hide_activity_power: StrictBool | None = Field(
        default=None, description="Hide activity power"
    )
    hide_activity_cadence: StrictBool | None = Field(
        default=None, description="Hide activity cadence"
    )
    hide_activity_elevation: StrictBool | None = Field(
        default=None, description="Hide activity elevation"
    )
    hide_activity_speed: StrictBool | None = Field(
        default=None, description="Hide activity speed"
    )
    hide_activity_pace: StrictBool | None = Field(
        default=None, description="Hide activity pace"
    )
    hide_activity_laps: StrictBool | None = Field(
        default=None, description="Hide activity laps"
    )
    hide_activity_workout_sets_steps: StrictBool | None = Field(
        default=None, description="Hide activity workout sets and steps"
    )
    hide_activity_gear: StrictBool | None = Field(
        default=None, description="Hide activity gear"
    )

UsersModel

Bases: Base

User account and profile information.

Attributes:

Name Type Description
id Mapped[int]

Primary key.

name Mapped[str]

User's real name (may include spaces).

username Mapped[str]

Unique username (letters, numbers, dots).

email Mapped[str]

Unique email address (max 250 characters).

password Mapped[str]

User's password hash.

city Mapped[str | None]

User's city.

birthdate Mapped[date | None]

User's birthdate.

preferred_language Mapped[str]

Preferred language code.

gender Mapped[str]

User's gender (male, female, unspecified).

units Mapped[str]

Measurement units (metric, imperial).

height Mapped[int | None]

User's height in centimeters.

max_heart_rate Mapped[int | None]

User maximum heart rate (bpm).

access_type Mapped[str]

User type (regular, admin).

photo_path Mapped[str | None]

Path to user's photo.

active Mapped[bool]

Whether the user is active.

first_day_of_week Mapped[str]

First day of the week (sunday, monday, etc.).

currency Mapped[str]

Currency preference (euro, dollar, pound).

mfa_enabled Mapped[bool]

Whether multi-factor authentication is enabled.

mfa_secret Mapped[str | None]

MFA secret for TOTP generation (encrypted at rest).

email_verified Mapped[bool]

Whether the user's email address has been verified.

pending_admin_approval Mapped[bool]

Whether the user is pending admin approval for activation.

users_sessions

List of session objects.

password_reset_tokens

List of password reset tokens.

sign_up_tokens

List of sign-up tokens.

users_integrations

List of integrations.

users_default_gear

List of default gear.

users_privacy_settings

List of privacy settings.

gear

List of gear owned by the user.

gear_components

List of gear components.

activities

List of activities performed.

followers

List of Follower objects representing users who follow this user.

following

List of Follower objects representing users this user is following.

health_sleep

List of health sleep records.

health_weight

List of health weight records.

health_steps

List of health steps records.

health_targets

List of health targets.

notifications

List of notifications.

goals

List of user goals.

user_identity_providers

List of identity providers linked to the user.

oauth_states

List of OAuth states for the user.

mfa_backup_codes

List of MFA backup codes.

Source code in backend/app/users/users/models.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
class Users(Base):
    """
    User account and profile information.

    Attributes:
        id: Primary key.
        name: User's real name (may include spaces).
        username: Unique username (letters, numbers, dots).
        email: Unique email address (max 250 characters).
        password: User's password hash.
        city: User's city.
        birthdate: User's birthdate.
        preferred_language: Preferred language code.
        gender: User's gender (male, female, unspecified).
        units: Measurement units (metric, imperial).
        height: User's height in centimeters.
        max_heart_rate: User maximum heart rate (bpm).
        access_type: User type (regular, admin).
        photo_path: Path to user's photo.
        active: Whether the user is active.
        first_day_of_week: First day of the week
            (sunday, monday, etc.).
        currency: Currency preference (euro, dollar, pound).
        mfa_enabled: Whether multi-factor authentication is
            enabled.
        mfa_secret: MFA secret for TOTP generation
            (encrypted at rest).
        email_verified: Whether the user's email address has
            been verified.
        pending_admin_approval: Whether the user is pending
            admin approval for activation.
        users_sessions: List of session objects.
        password_reset_tokens: List of password reset tokens.
        sign_up_tokens: List of sign-up tokens.
        users_integrations: List of integrations.
        users_default_gear: List of default gear.
        users_privacy_settings: List of privacy settings.
        gear: List of gear owned by the user.
        gear_components: List of gear components.
        activities: List of activities performed.
        followers: List of Follower objects representing users
            who follow this user.
        following: List of Follower objects representing users
            this user is following.
        health_sleep: List of health sleep records.
        health_weight: List of health weight records.
        health_steps: List of health steps records.
        health_targets: List of health targets.
        notifications: List of notifications.
        goals: List of user goals.
        user_identity_providers: List of identity providers
            linked to the user.
        oauth_states: List of OAuth states for the user.
        mfa_backup_codes: List of MFA backup codes.
    """

    __tablename__ = "users"

    id: Mapped[int] = mapped_column(
        primary_key=True,
        autoincrement=True,
    )
    name: Mapped[str] = mapped_column(
        String(250),
        nullable=False,
        comment="User real name (May include spaces)",
    )
    username: Mapped[str] = mapped_column(
        String(250),
        nullable=False,
        unique=True,
        index=True,
        comment="User username (letters, numbers, and dots allowed)",
    )
    email: Mapped[str] = mapped_column(
        String(250),
        nullable=False,
        unique=True,
        index=True,
        comment="User email (max 250 characters)",
    )
    password: Mapped[str] = mapped_column(
        String(250),
        nullable=False,
        comment="User password (hash)",
    )
    city: Mapped[str | None] = mapped_column(
        String(250),
        nullable=True,
        comment="User city",
    )
    birthdate: Mapped[date_type | None] = mapped_column(
        nullable=True,
        comment="User birthdate (date)",
    )
    preferred_language: Mapped[str] = mapped_column(
        String(5),
        nullable=False,
        comment="User preferred language (en, pt, others)",
    )
    gender: Mapped[str] = mapped_column(
        String(20),
        default="male",
        nullable=False,
        comment="User gender (male, female, unspecified)",
    )
    units: Mapped[str] = mapped_column(
        String(20),
        default="metric",
        nullable=False,
        comment="User units (metric, imperial)",
    )
    height: Mapped[int | None] = mapped_column(
        nullable=True,
        comment="User height in centimeters",
    )
    max_heart_rate: Mapped[int | None] = mapped_column(
        nullable=True,
        comment="User maximum heart rate (bpm)",
    )
    access_type: Mapped[str] = mapped_column(
        String(20),
        nullable=False,
        comment="User type (regular, admin)",
    )
    photo_path: Mapped[str | None] = mapped_column(
        String(250),
        nullable=True,
        comment="User photo path",
    )
    active: Mapped[bool] = mapped_column(
        default=True,
        nullable=False,
        comment="Whether the user is active (true - yes, false - no)",
    )
    first_day_of_week: Mapped[str] = mapped_column(
        String(20),
        default="monday",
        nullable=False,
        comment="User first day of week (sunday, monday, etc.)",
    )
    currency: Mapped[str] = mapped_column(
        String(20),
        default="euro",
        nullable=False,
        comment="User currency (euro, dollar, pound)",
    )
    mfa_enabled: Mapped[bool] = mapped_column(
        default=False,
        nullable=False,
        comment="Whether MFA is enabled for this user",
    )
    mfa_secret: Mapped[str | None] = mapped_column(
        String(512),
        nullable=True,
        comment=("User MFA secret for TOTP generation " "(encrypted at rest)"),
    )
    email_verified: Mapped[bool] = mapped_column(
        default=False,
        nullable=False,
        comment=(
            "Whether the user's email address has been verified "
            "(true - yes, false - no)"
        ),
    )
    pending_admin_approval: Mapped[bool] = mapped_column(
        default=False,
        nullable=False,
        comment=(
            "Whether the user is pending admin approval for "
            "activation (true - yes, false - no)"
        ),
    )

    # Relationships
    # TODO: Change to Mapped["ModelName"] when all modules use mapped
    users_sessions = relationship(
        "UsersSessions",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    password_reset_tokens = relationship(
        "PasswordResetToken",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    sign_up_tokens = relationship(
        "SignUpToken",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    users_integrations = relationship(
        "UsersIntegrations",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    users_default_gear = relationship(
        "UsersDefaultGear",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    users_privacy_settings = relationship(
        "UsersPrivacySettings",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    gear = relationship(
        "Gear",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    gear_components = relationship(
        "GearComponents",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    activities = relationship(
        "Activity",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    followers = relationship(
        "Follower",
        back_populates="following",
        cascade="all, delete-orphan",
        foreign_keys=[followers_models.Follower.following_id],
    )
    following = relationship(
        "Follower",
        back_populates="follower",
        cascade="all, delete-orphan",
        foreign_keys=[followers_models.Follower.follower_id],
    )
    health_sleep = relationship(
        "HealthSleep",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    health_weight = relationship(
        "HealthWeight",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    health_steps = relationship(
        "HealthSteps",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    health_targets = relationship(
        "HealthTargets",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    notifications = relationship(
        "Notification",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    goals = relationship(
        "UsersGoal",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    user_identity_providers = relationship(
        "UsersIdentityProvider",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    oauth_states = relationship(
        "OAuthState",
        back_populates="users",
        cascade="all, delete-orphan",
    )
    mfa_backup_codes = relationship(
        "MFABackupCode",
        back_populates="users",
        cascade="all, delete-orphan",
    )

UsersRead

Bases: Users

Users schema for read operations.

Attributes:

Name Type Description
id StrictInt

User ID.

external_auth_count StrictInt

Number of external auth providers.

Source code in backend/app/users/users/schema.py
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
class UsersRead(Users):
    """
    Users schema for read operations.

    Attributes:
        id: User ID.
        external_auth_count: Number of external auth providers.
    """

    id: StrictInt = Field(
        ...,
        ge=1,
        description="User ID",
    )
    external_auth_count: StrictInt = Field(
        default=0,
        ge=0,
        description="Number of external auth providers linked",
    )

UsersSignup

Bases: UsersBase

Users schema for signup operations.

Attributes:

Name Type Description
password StrictStr

User's password (min 8 chars).

Source code in backend/app/users/users/schema.py
351
352
353
354
355
356
357
358
359
360
361
362
363
364
class UsersSignup(UsersBase):
    """
    Users schema for signup operations.

    Attributes:
        password: User's password (min 8 chars).
    """

    password: StrictStr = Field(
        ...,
        min_length=8,
        max_length=250,
        description="User's password",
    )

WeekDay

Bases: Enum

Days of the week enumeration.

Attributes:

Name Type Description
SUNDAY

Sunday.

MONDAY

Monday.

TUESDAY

Tuesday.

WEDNESDAY

Wednesday.

THURSDAY

Thursday.

FRIDAY

Friday.

SATURDAY

Saturday.

Source code in backend/app/users/users/schema.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class WeekDay(Enum):
    """
    Days of the week enumeration.

    Attributes:
        SUNDAY: Sunday.
        MONDAY: Monday.
        TUESDAY: Tuesday.
        WEDNESDAY: Wednesday.
        THURSDAY: Thursday.
        FRIDAY: Friday.
        SATURDAY: Saturday.
    """

    SUNDAY = "sunday"
    MONDAY = "monday"
    TUESDAY = "tuesday"
    WEDNESDAY = "wednesday"
    THURSDAY = "thursday"
    FRIDAY = "friday"
    SATURDAY = "saturday"

approve_user

approve_user(user_id, db)

Approve a user by marking them as active.

Parameters:

Name Type Description Default
user_id int

ID of user to approve.

required
db Session

SQLAlchemy database session.

required

Returns:

Type Description
None

None

Raises:

Type Description
HTTPException

404 if user not found.

HTTPException

400 if user email not verified.

HTTPException

500 if database error occurs.

Source code in backend/app/users/users/crud.py
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
@core_decorators.handle_db_errors
def approve_user(user_id: int, db: Session) -> None:
    """
    Approve a user by marking them as active.

    Args:
        user_id: ID of user to approve.
        db: SQLAlchemy database session.

    Returns:
        None

    Raises:
        HTTPException: 404 if user not found.
        HTTPException: 400 if user email not verified.
        HTTPException: 500 if database error occurs.
    """
    # Get the user from the database
    db_user = users_utils.get_user_by_id_or_404(user_id, db)

    if not db_user.email_verified:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="User email is not verified",
        )

    db_user.pending_admin_approval = False
    db_user.active = True

    # Commit the transaction
    db.commit()
    db.refresh(db_user)

check_password_and_hash

check_password_and_hash(password, password_hasher, server_settings, user_access_type)

Validates password against the configured policy and hashes it.

Parameters:

Name Type Description Default
password str

The password to validate and hash.

required
password_hasher PasswordHasher

The password hasher instance.

required
server_settings ServerSettings | ServerSettingsRead

The server settings containing password policies.

required
user_access_type str

The access type of the user (e.g., "regular" or "admin").

required

Returns:

Name Type Description
str str

The hashed password.

Raises:

Type Description
HTTPException

If password validation fails.

Source code in backend/app/users/users/utils.py
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def check_password_and_hash(
    password: str,
    password_hasher: auth_password_hasher.PasswordHasher,
    server_settings: (
        server_settings_models.ServerSettings
        | server_settings_schema.ServerSettingsRead
    ),
    user_access_type: str,
) -> str:
    """
    Validates password against the configured policy and hashes it.

    Args:
        password (str): The password to validate and hash.
        password_hasher (PasswordHasher): The password hasher instance.
        server_settings (ServerSettings | ServerSettingsRead): The server settings containing password policies.
        user_access_type (str): The access type of the user (e.g., "regular" or "admin").

    Returns:
        str: The hashed password.

    Raises:
        HTTPException: If password validation fails.
    """
    # Determine minimum length based on user access type
    min_length = (
        server_settings.password_length_admin_users
        if user_access_type == users_schema.UserAccessType.ADMIN.value
        else server_settings.password_length_regular_users
    )
    # Check if password meets requirements
    try:
        password_hasher.validate_password(
            password, min_length, str(server_settings.password_type)
        )
    except auth_password_hasher.PasswordPolicyError as err:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(err),
        ) from err

    # Hash the password
    hashed_password = password_hasher.hash_password(password)

    # Return the hashed password
    return hashed_password

check_user_is_active

check_user_is_active(user)

Check if user is active and raise 403 if inactive.

Parameters:

Name Type Description Default
user Users | UsersRead

User object to check (User or UsersRead schema).

required

Returns:

Type Description
None

None

Raises:

Type Description
HTTPException

403 if user is not active.

Source code in backend/app/users/users/utils.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def check_user_is_active(
    user: users_models.Users | users_schema.UsersRead,
) -> None:
    """
    Check if user is active and raise 403 if inactive.

    Args:
        user: User object to check (User or UsersRead schema).

    Returns:
        None

    Raises:
        HTTPException: 403 if user is not active.
    """
    if not user.active:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Inactive user",
            headers={"WWW-Authenticate": "Bearer"},
        )

create_signup_user

create_signup_user(user, server_settings, password_hasher, db)

Create a new user during signup process.

Parameters:

Name Type Description Default
user UsersSignup

User signup data.

required
server_settings ServerSettingsRead

Server config for signup requirements.

required
password_hasher PasswordHasher

Password hasher instance.

required
db Session

SQLAlchemy database session.

required

Returns:

Type Description
Users

Created Users model.

Raises:

Type Description
HTTPException

409 if email/username already exists. Abstract message to reduce information leakage.

HTTPException

500 if database error occurs.

Source code in backend/app/users/users/crud.py
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
@core_decorators.handle_db_errors
def create_signup_user(
    user: users_schema.UsersSignup,
    server_settings: server_settings_schema.ServerSettingsRead,
    password_hasher: auth_password_hasher.PasswordHasher,
    db: Session,
) -> users_models.Users:
    """
    Create a new user during signup process.

    Args:
        user: User signup data.
        server_settings: Server config for signup requirements.
        password_hasher: Password hasher instance.
        db: SQLAlchemy database session.

    Returns:
        Created Users model.

    Raises:
        HTTPException: 409 if email/username already exists. Abstract message
            to reduce information leakage.
        HTTPException: 500 if database error occurs.
    """
    try:
        # Determine user status based on server settings
        active = True
        email_verified = False
        pending_admin_approval = False

        if server_settings.signup_require_email_verification:
            email_verified = False
            active = False  # Inactive until email verified

        if server_settings.signup_require_admin_approval:
            pending_admin_approval = True
            active = False  # Inactive until approved

        # If both email verification and admin approval are disabled, user is immediately active
        if (
            not server_settings.signup_require_email_verification
            and not server_settings.signup_require_admin_approval
        ):
            active = True
            email_verified = True

        # Create a new user
        db_user = users_models.Users(
            **user.model_dump(
                exclude={
                    "username",
                    "email",
                    "access_type",
                    "active",
                    "email_verified",
                    "pending_admin_approval",
                    "password",
                }
            ),
            username=user.username.lower(),
            email=user.email.lower(),
            access_type=users_schema.UserAccessType.REGULAR.value,
            active=active,
            email_verified=email_verified,
            pending_admin_approval=pending_admin_approval,
            password=users_utils.check_password_and_hash(
                user.password,
                password_hasher,
                server_settings,
                users_schema.UserAccessType.REGULAR.value,
            ),
        )

        # Add the user to the database
        db.add(db_user)
        db.commit()
        db.refresh(db_user)

        # Return user
        return db_user
    except IntegrityError as integrity_error:
        # Rollback the transaction
        db.rollback()

        # Raise an HTTPException with a 409 Conflict status code
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=("Unable to create user."),
        ) from integrity_error

create_user

create_user(user, password_hasher, db)

Create a new user with hashed password.

Parameters:

Name Type Description Default
user UsersCreate

User creation data with plain text password.

required
password_hasher PasswordHasher

Password hasher instance.

required
db Session

SQLAlchemy database session.

required

Returns:

Type Description
Users

Created Users model with hashed password.

Raises:

Type Description
HTTPException

409 if email/username already exists.

HTTPException

500 if database error occurs.

Source code in backend/app/users/users/crud.py
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
@core_decorators.handle_db_errors
def create_user(
    user: users_schema.UsersCreate,
    password_hasher: auth_password_hasher.PasswordHasher,
    db: Session,
) -> users_models.Users:
    """
    Create a new user with hashed password.

    Args:
        user: User creation data with plain text password.
        password_hasher: Password hasher instance.
        db: SQLAlchemy database session.

    Returns:
        Created Users model with hashed password.

    Raises:
        HTTPException: 409 if email/username already exists.
        HTTPException: 500 if database error occurs.
    """
    try:
        user.username = user.username.lower()
        user.email = user.email.lower()

        # Get server settings to determine password policy
        server_settings = server_settings_utils.get_server_settings_or_404(db)

        # Normalize access_type to string value
        access_type_value = (
            user.access_type.value
            if isinstance(user.access_type, users_schema.UserAccessType)
            else user.access_type
        )

        # Hash the password with configurable policy and length
        hashed_password = users_utils.check_password_and_hash(
            user.password, password_hasher, server_settings, access_type_value
        )

        # Create a new user
        db_user = users_models.Users(
            **user.model_dump(exclude={"password", "access_type"}),
            password=hashed_password,
            access_type=access_type_value,
        )

        # Add the user to the database
        db.add(db_user)
        db.commit()
        db.refresh(db_user)

        # Return user
        return db_user
    except HTTPException:
        # Rollback the transaction
        db.rollback()
        raise
    except IntegrityError as integrity_error:
        # Rollback the transaction
        db.rollback()

        # Raise an HTTPException with a 409 Conflict status code
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=("Duplicate entry error. Check if email and username are unique"),
        ) from integrity_error

create_user_default_data

create_user_default_data(user_id, db)

Create default data for newly created user.

Parameters:

Name Type Description Default
user_id int

ID of user to create default data for.

required
db Session

SQLAlchemy database session.

required

Returns:

Type Description
None

None

Source code in backend/app/users/users/utils.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def create_user_default_data(user_id: int, db: Session) -> None:
    """
    Create default data for newly created user.

    Args:
        user_id: ID of user to create default data for.
        db: SQLAlchemy database session.

    Returns:
        None
    """
    # Create the user integrations in the database
    user_integrations_crud.create_user_integrations(user_id, db)

    # Create the user privacy settings
    users_privacy_settings_crud.create_user_privacy_settings(user_id, db)

    # Create the user health targets
    health_targets_crud.create_health_targets(user_id, db)

    # Create the user default gear
    user_default_gear_crud.create_user_default_gear(user_id, db)

delete_user async

delete_user(user_id, db)

Delete a user from the database.

Parameters:

Name Type Description Default
user_id int

ID of user to delete.

required
db Session

SQLAlchemy database session.

required

Returns:

Type Description
None

None

Raises:

Type Description
HTTPException

404 if user not found.

HTTPException

500 if database error occurs.

Source code in backend/app/users/users/crud.py
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
@core_decorators.handle_db_errors
async def delete_user(user_id: int, db: Session) -> None:
    """
    Delete a user from the database.

    Args:
        user_id: ID of user to delete.
        db: SQLAlchemy database session.

    Returns:
        None

    Raises:
        HTTPException: 404 if user not found.
        HTTPException: 500 if database error occurs.
    """
    # Get the user from the database
    db_user = users_utils.get_user_by_id_or_404(user_id, db)

    # Delete the user
    db.delete(db_user)

    # Commit the transaction
    db.commit()

    # Delete the user photo in the filesystem
    await users_utils.delete_user_photo_filesystem(user_id)

delete_user_photo_filesystem async

delete_user_photo_filesystem(user_id)

Delete user photo files from filesystem.

Parameters:

Name Type Description Default
user_id int

ID of user whose photo files to delete.

required

Returns:

Type Description
None

None

Source code in backend/app/users/users/utils.py
211
212
213
214
215
216
217
218
219
220
221
222
223
async def delete_user_photo_filesystem(user_id: int) -> None:
    """
    Delete user photo files from filesystem.

    Args:
        user_id: ID of user whose photo files to delete.

    Returns:
        None
    """
    await core_file_uploads.delete_files_by_pattern(
        core_config.USER_IMAGES_DIR, f"{user_id}.*"
    )

edit_user async

edit_user(user_id, user, db)

Update an existing user's information.

Parameters:

Name Type Description Default
user_id int

ID of user to update.

required
user UsersRead

User data to update with.

required
db Session

SQLAlchemy database session.

required

Returns:

Type Description
Users

users_models.Users

Raises:

Type Description
HTTPException

404 if user not found.

HTTPException

409 if email/username conflict.

HTTPException

500 if database error occurs.

Source code in backend/app/users/users/crud.py
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
@core_decorators.handle_db_errors
async def edit_user(
    user_id: int, user: users_schema.UsersRead, db: Session
) -> users_models.Users:
    """
    Update an existing user's information.

    Args:
        user_id: ID of user to update.
        user: User data to update with.
        db: SQLAlchemy database session.

    Returns:
        users_models.Users

    Raises:
        HTTPException: 404 if user not found.
        HTTPException: 409 if email/username conflict.
        HTTPException: 500 if database error occurs.
    """
    try:
        # Get the user from the database
        db_user = users_utils.get_user_by_id_or_404(user_id, db)

        height_before = db_user.height

        # Check if the photo_path is being updated
        if user.photo_path:
            # Delete the user photo in the filesystem
            await users_utils.delete_user_photo_filesystem(db_user.id)

        user.username = user.username.lower()

        # Dictionary of the fields to update if they are not None
        user_data = user.model_dump(exclude_unset=True)
        # Iterate over the fields and update the db_user dynamically
        for key, value in user_data.items():
            setattr(db_user, key, value)

        # Commit the transaction
        db.commit()
        db.refresh(db_user)

        if height_before != db_user.height:
            # Update the user's health data
            health_weight_utils.calculate_bmi_all_user_entries(db_user.id, db)

        if db_user.photo_path is None:
            # Delete the user photo in the filesystem
            await users_utils.delete_user_photo_filesystem(db_user.id)

        return db_user
    except HTTPException:
        raise
    except IntegrityError as integrity_error:
        # Rollback the transaction
        db.rollback()

        # Raise an HTTPException with a 409 Conflict status code
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=("Duplicate entry error. " "Check if email and username are unique"),
        ) from integrity_error

edit_user_password

edit_user_password(user_id, password, password_hasher, db, is_hashed=False)

Update a user's password.

Parameters:

Name Type Description Default
user_id int

ID of user to update password for.

required
password str

New password (plain text or hashed based on is_hashed).

required
password_hasher PasswordHasher

Password hasher instance.

required
db Session

SQLAlchemy database session.

required
is_hashed bool

Whether password is already hashed.

False

Returns:

Type Description
None

None

Raises:

Type Description
HTTPException

404 if user not found.

HTTPException

500 if database error occurs.

Source code in backend/app/users/users/crud.py
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
@core_decorators.handle_db_errors
def edit_user_password(
    user_id: int,
    password: str,
    password_hasher: auth_password_hasher.PasswordHasher,
    db: Session,
    is_hashed: bool = False,
) -> None:
    """
    Update a user's password.

    Args:
        user_id: ID of user to update password for.
        password: New password (plain text or hashed based on is_hashed).
        password_hasher: Password hasher instance.
        db: SQLAlchemy database session.
        is_hashed: Whether password is already hashed.

    Returns:
        None

    Raises:
        HTTPException: 404 if user not found.
        HTTPException: 500 if database error occurs.
    """
    # Get the user from the database
    db_user = users_utils.get_user_by_id_or_404(user_id, db)

    # Update the user
    if is_hashed:
        db_user.password = password
    else:
        # Get server settings to determine password policy
        server_settings = server_settings_utils.get_server_settings_or_404(db)

        # Normalize access_type to string value
        access_type_value = (
            db_user.access_type.value
            if isinstance(db_user.access_type, users_schema.UserAccessType)
            else db_user.access_type
        )

        # Hash the password with configurable policy and length
        db_user.password = users_utils.check_password_and_hash(
            password, password_hasher, server_settings, access_type_value
        )

    # Commit the transaction
    db.commit()
    db.refresh(db_user)

get_admin_users_or_404

get_admin_users_or_404(db)

Retrieve all admin users from database or raise 404 error.

Parameters:

Name Type Description Default
db Session

SQLAlchemy database session.

required

Returns:

Type Description
list[Users]

List of all admin User models.

Raises:

Type Description
HTTPException

404 if no admin users found.

Source code in backend/app/users/users/utils.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def get_admin_users_or_404(db: Session) -> list[users_models.Users]:
    """
    Retrieve all admin users from database or raise 404 error.

    Args:
        db: SQLAlchemy database session.

    Returns:
        List of all admin User models.

    Raises:
        HTTPException: 404 if no admin users found.
    """
    admins = users_crud.get_users_admin(db)

    if not admins:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="No admin users found",
        )

    return admins

get_all_users

get_all_users(db)

Retrieve all users from the database.

Parameters:

Name Type Description Default
db Session

SQLAlchemy database session.

required

Returns:

Type Description
list[Users]

List of all User models.

Raises:

Type Description
HTTPException

500 error if database query fails.

Source code in backend/app/users/users/crud.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@core_decorators.handle_db_errors
def get_all_users(db: Session) -> list[users_models.Users]:
    """
    Retrieve all users from the database.

    Args:
        db: SQLAlchemy database session.

    Returns:
        List of all User models.

    Raises:
        HTTPException: 500 error if database query fails.
    """
    stmt = select(users_models.Users)
    return db.execute(stmt).scalars().all()

get_user_by_email

get_user_by_email(email, db)

Retrieve user by email address.

Parameters:

Name Type Description Default
email str

Email address to search for (case-insensitive).

required
db Session

SQLAlchemy database session.

required

Returns:

Type Description
Users | None

Users model if found, None otherwise.

Raises:

Type Description
HTTPException

500 error if database query fails.

Source code in backend/app/users/users/crud.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
@core_decorators.handle_db_errors
def get_user_by_email(email: str, db: Session) -> users_models.Users | None:
    """
    Retrieve user by email address.

    Args:
        email: Email address to search for (case-insensitive).
        db: SQLAlchemy database session.

    Returns:
        Users model if found, None otherwise.

    Raises:
        HTTPException: 500 error if database query fails.
    """
    stmt = select(users_models.Users).where(users_models.Users.email == email.lower())
    return db.execute(stmt).scalar_one_or_none()

get_user_by_id

get_user_by_id(user_id, db, public_check=False)

Retrieve user by ID.

Parameters:

Name Type Description Default
user_id int

User ID to search for.

required
db Session

SQLAlchemy database session.

required
public_check bool

If True, only returns user when public sharing is enabled in server settings.

False

Returns:

Type Description
Users | None

Users model if found (and public sharing enabled if

Users | None

public_check=True), None otherwise.

Raises:

Type Description
HTTPException

500 error if database query fails.

Source code in backend/app/users/users/crud.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
@core_decorators.handle_db_errors
def get_user_by_id(
    user_id: int, db: Session, public_check: bool = False
) -> users_models.Users | None:
    """
    Retrieve user by ID.

    Args:
        user_id: User ID to search for.
        db: SQLAlchemy database session.
        public_check: If True, only returns user when public sharing
                      is enabled in server settings.

    Returns:
        Users model if found (and public sharing enabled if
        public_check=True), None otherwise.

    Raises:
        HTTPException: 500 error if database query fails.
    """
    if public_check:
        # Check if public sharable links are enabled in server settings
        server_settings = server_settings_utils.get_server_settings_or_404(db)

        # Return None if public sharable links are disabled
        if (
            not server_settings.public_shareable_links
            or not server_settings.public_shareable_links_user_info
        ):
            return None

    stmt = select(users_models.Users).where(users_models.Users.id == user_id)
    return db.execute(stmt).scalar_one_or_none()

get_user_by_id_or_404

get_user_by_id_or_404(user_id, db)

Retrieve user by ID or raise 404 error.

Parameters:

Name Type Description Default
user_id int

User ID to search for.

required
db Session

SQLAlchemy database session.

required

Returns:

Type Description
Users

Users model (guaranteed non-None).

Raises:

Type Description
HTTPException

404 if user not found.

Source code in backend/app/users/users/utils.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def get_user_by_id_or_404(user_id: int, db: Session) -> users_models.Users:
    """
    Retrieve user by ID or raise 404 error.

    Args:
        user_id: User ID to search for.
        db: SQLAlchemy database session.

    Returns:
        Users model (guaranteed non-None).

    Raises:
        HTTPException: 404 if user not found.
    """
    # Get the user from the database
    db_user = users_crud.get_user_by_id(user_id, db)

    if db_user is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User not found",
            headers={"WWW-Authenticate": "Bearer"},
        )

    return db_user

get_user_by_username

get_user_by_username(username, db, contains=False)

Retrieve user by username.

Parameters:

Name Type Description Default
username str

Username to search for.

required
db Session

SQLAlchemy database session.

required
contains bool

If True, performs partial match search and returns list of matching users. If False, performs exact match and returns single user or None.

False

Returns:

Type Description
list[Users] | Users | None

If contains=False: Users model if found, None otherwise.

list[Users] | Users | None

If contains=True: List of User models matching the search.

Raises:

Type Description
HTTPException

500 error if database query fails.

Source code in backend/app/users/users/crud.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
@core_decorators.handle_db_errors
def get_user_by_username(
    username: str, db: Session, contains: bool = False
) -> list[users_models.Users] | users_models.Users | None:
    """
    Retrieve user by username.

    Args:
        username: Username to search for.
        db: SQLAlchemy database session.
        contains: If True, performs partial match search and returns
                  list of matching users. If False, performs exact
                  match and returns single user or None.

    Returns:
        If contains=False: Users model if found, None otherwise.
        If contains=True: List of User models matching the search.

    Raises:
        HTTPException: 500 error if database query fails.
    """
    # Decode and normalize search term (needed for both exact and partial matches)
    normalized_username = unquote(username).replace("+", " ").lower()

    if contains:
        # Escape LIKE special characters to prevent SQL injection
        escaped_username = (
            normalized_username.replace("\\", "\\\\")
            .replace("%", r"\%")
            .replace("_", r"\_")
        )

        # Query users with username containing the search term
        stmt = select(users_models.Users).where(
            func.lower(users_models.Users.username).like(
                f"%{escaped_username}%", escape="\\"
            )
        )
        return db.execute(stmt).scalars().all()
    else:
        # Exact match - no LIKE escaping needed
        stmt = select(users_models.Users).where(
            users_models.Users.username == normalized_username
        )
        return db.execute(stmt).scalar_one_or_none()

get_users_admin

get_users_admin(db)

Retrieve all admin users from the database.

Parameters:

Name Type Description Default
db Session

SQLAlchemy database session.

required

Returns:

Type Description
list[Users]

List of User models with admin access.

Raises:

Type Description
HTTPException

500 error if database query fails.

Source code in backend/app/users/users/crud.py
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
@core_decorators.handle_db_errors
def get_users_admin(db: Session) -> list[users_models.Users]:
    """
    Retrieve all admin users from the database.

    Args:
        db: SQLAlchemy database session.

    Returns:
        List of User models with admin access.

    Raises:
        HTTPException: 500 error if database query fails.
    """
    stmt = select(users_models.Users).where(
        users_models.Users.access_type == users_schema.UserAccessType.ADMIN.value
    )
    return db.execute(stmt).scalars().all()

get_users_number

get_users_number(db)

Get total count of users in the database.

Parameters:

Name Type Description Default
db Session

SQLAlchemy database session.

required

Returns:

Type Description
int

Total number of users.

Raises:

Type Description
HTTPException

500 error if database query fails.

Source code in backend/app/users/users/crud.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@core_decorators.handle_db_errors
def get_users_number(db: Session) -> int:
    """
    Get total count of users in the database.

    Args:
        db: SQLAlchemy database session.

    Returns:
        Total number of users.

    Raises:
        HTTPException: 500 error if database query fails.
    """
    stmt = select(func.count(users_models.Users.id))
    return db.execute(stmt).scalar_one()

get_users_with_pagination

get_users_with_pagination(db, page_number=1, num_records=5)

Retrieve paginated list of users.

Parameters:

Name Type Description Default
db Session

SQLAlchemy database session.

required
page_number int

Page number to retrieve (1-indexed).

1
num_records int

Number of records per page.

5

Returns:

Type Description
list[Users]

List of User models for the requested page.

Raises:

Type Description
HTTPException

500 error if database query fails.

Source code in backend/app/users/users/crud.py
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
@core_decorators.handle_db_errors
def get_users_with_pagination(
    db: Session, page_number: int = 1, num_records: int = 5
) -> list[users_models.Users]:
    """
    Retrieve paginated list of users.

    Args:
        db: SQLAlchemy database session.
        page_number: Page number to retrieve (1-indexed).
        num_records: Number of records per page.

    Returns:
        List of User models for the requested page.

    Raises:
        HTTPException: 500 error if database query fails.
    """
    stmt = (
        select(users_models.Users)
        .order_by(users_models.Users.username)
        .offset((page_number - 1) * num_records)
        .limit(num_records)
    )
    return db.execute(stmt).scalars().all()

save_user_image_file async

save_user_image_file(user_id, file, db)

Save user image file with security validation and update DB.

Uses centralized file upload handler for validation and async I/O, then updates user photo path in database.

Parameters:

Name Type Description Default
user_id int

ID of user whose image is being saved.

required
file UploadFile

Uploaded image file (UploadFile).

required
db Session

SQLAlchemy database session.

required

Returns:

Type Description
str

Path to saved image file.

Raises:

Type Description
HTTPException

400 if filename missing, 500 if upload fails.

Source code in backend/app/users/users/utils.py
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
async def save_user_image_file(user_id: int, file: UploadFile, db: Session) -> str:
    """
    Save user image file with security validation and update DB.

    Uses centralized file upload handler for validation and async
    I/O, then updates user photo path in database.

    Args:
        user_id: ID of user whose image is being saved.
        file: Uploaded image file (UploadFile).
        db: SQLAlchemy database session.

    Returns:
        Path to saved image file.

    Raises:
        HTTPException: 400 if filename missing, 500 if upload
            fails.
    """
    if not file.filename:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Filename is required",
        )

    # Get file extension and build filename
    _, file_extension = os.path.splitext(file.filename)
    filename = f"{user_id}{file_extension}"

    # Save file using centralized file upload handler
    await core_file_uploads.save_image_file_and_validate_it(
        file, core_config.USER_IMAGES_DIR, filename
    )

    # Update user photo path in database
    return str(
        await users_crud.update_user_photo(
            user_id, db, os.path.join(core_config.USER_IMAGES_DIR, filename)
        )
    )

update_user_mfa

update_user_mfa(user_id, db, encrypted_secret=None)

Update a user's MFA settings.

Parameters:

Name Type Description Default
user_id int

ID of user to update MFA for.

required
db Session

SQLAlchemy database session.

required
encrypted_secret str | None

Encrypted MFA secret. If None, disables MFA.

None

Returns:

Type Description
None

None

Raises:

Type Description
HTTPException

404 if user not found.

HTTPException

500 if database error occurs.

Source code in backend/app/users/users/crud.py
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
@core_decorators.handle_db_errors
def update_user_mfa(
    user_id: int, db: Session, encrypted_secret: str | None = None
) -> None:
    """
    Update a user's MFA settings.

    Args:
        user_id: ID of user to update MFA for.
        db: SQLAlchemy database session.
        encrypted_secret: Encrypted MFA secret. If None, disables MFA.

    Returns:
        None

    Raises:
        HTTPException: 404 if user not found.
        HTTPException: 500 if database error occurs.
    """
    # Get the user from the database
    db_user = users_utils.get_user_by_id_or_404(user_id, db)

    if encrypted_secret:
        db_user.mfa_enabled = True
        db_user.mfa_secret = encrypted_secret
    else:
        db_user.mfa_enabled = False
        db_user.mfa_secret = None

    db.commit()
    db.refresh(db_user)

update_user_photo async

update_user_photo(user_id, db, photo_path=None)

Update a user's photo path.

Parameters:

Name Type Description Default
user_id int

ID of user to update photo for.

required
db Session

SQLAlchemy database session.

required
photo_path str | None

New photo path. If None, removes photo.

None

Returns:

Type Description
str | None

The updated photo path, or None if removed.

Raises:

Type Description
HTTPException

404 if user not found.

HTTPException

500 if database error occurs.

Source code in backend/app/users/users/crud.py
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
@core_decorators.handle_db_errors
async def update_user_photo(
    user_id: int, db: Session, photo_path: str | None = None
) -> str | None:
    """
    Update a user's photo path.

    Args:
        user_id: ID of user to update photo for.
        db: SQLAlchemy database session.
        photo_path: New photo path. If None, removes photo.

    Returns:
        The updated photo path, or None if removed.

    Raises:
        HTTPException: 404 if user not found.
        HTTPException: 500 if database error occurs.
    """
    # Get the user from the database
    db_user = users_utils.get_user_by_id_or_404(user_id, db)

    # Update the user
    db_user.photo_path = photo_path

    # Commit the transaction
    db.commit()
    db.refresh(db_user)

    if photo_path:
        # Return the photo path
        return photo_path
    else:
        # Delete the user photo in the filesystem
        await users_utils.delete_user_photo_filesystem(user_id)

        return None

verify_user_email

verify_user_email(user_id, server_settings, db)

Verify user email and conditionally activate account.

Parameters:

Name Type Description Default
user_id int

ID of user to verify.

required
server_settings ServerSettingsRead

Server config determining activation policy.

required
db Session

SQLAlchemy database session.

required

Returns:

Type Description
None

None

Raises:

Type Description
HTTPException

404 if user not found.

HTTPException

500 if database error occurs.

Source code in backend/app/users/users/crud.py
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
@core_decorators.handle_db_errors
def verify_user_email(
    user_id: int,
    server_settings: server_settings_schema.ServerSettingsRead,
    db: Session,
) -> None:
    """
    Verify user email and conditionally activate account.

    Args:
        user_id: ID of user to verify.
        server_settings: Server config determining activation policy.
        db: SQLAlchemy database session.

    Returns:
        None

    Raises:
        HTTPException: 404 if user not found.
        HTTPException: 500 if database error occurs.
    """
    # Get the user from the database
    db_user = users_utils.get_user_by_id_or_404(user_id, db)

    db_user.email_verified = True
    if not server_settings.signup_require_admin_approval:
        db_user.pending_admin_approval = False
        db_user.active = True

    # Commit the transaction
    db.commit()
    db.refresh(db_user)