Skip to content

API Reference

Gear module for managing user equipment.

This module provides CRUD operations and data models for user gear tracking including bikes, shoes, wetsuits, racquets, skis, snowboards, windsurf, and water sports equipment.

Exports
  • CRUD: get_gear_user_by_id, get_gears_number, get_gear_users_with_pagination, get_gear_user, get_gear_user_contains_nickname, get_gear_user_by_nickname, get_gear_by_type_and_user, get_gear_by_strava_id_from_user_id, get_gear_by_garminconnect_id_from_user_id, create_multiple_gears, create_gear, edit_gear, delete_gear, delete_all_strava_gear_for_user, delete_all_garminconnect_gear_for_user
  • Schemas: GearBase, GearCreate, GearRead, GearUpdate, GearsListResponse
  • Models: Gear (ORM model)
  • Utils: transform_schema_gear_to_model_gear, GEAR_ID_TO_NAME, GEAR_NAME_TO_ID

GearBase

Bases: BaseModel

Base model for gear data.

Attributes:

Name Type Description
brand StrictStr | None

Gear brand name.

model StrictStr | None

Gear model name.

nickname StrictStr

Gear display nickname.

gear_type StrictInt

Gear type identifier.

created_at datetime | None

Gear creation timestamp.

active StrictBool | None

Whether the gear is active.

initial_kms StrictFloat | None

Initial kilometers.

purchase_value StrictFloat | None

Purchase value.

strava_gear_id StrictStr | None

Strava gear ID.

garminconnect_gear_id StrictStr | None

Garmin gear ID.

Source code in backend/app/gears/gear/schema.py
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
class GearBase(BaseModel):
    """
    Base model for gear data.

    Attributes:
        brand: Gear brand name.
        model: Gear model name.
        nickname: Gear display nickname.
        gear_type: Gear type identifier.
        created_at: Gear creation timestamp.
        active: Whether the gear is active.
        initial_kms: Initial kilometers.
        purchase_value: Purchase value.
        strava_gear_id: Strava gear ID.
        garminconnect_gear_id: Garmin gear ID.
    """

    brand: StrictStr | None = Field(
        default=None,
        max_length=250,
        description="Gear brand name",
    )
    model: StrictStr | None = Field(
        default=None,
        max_length=250,
        description="Gear model name",
    )
    nickname: StrictStr = Field(
        ...,
        max_length=250,
        description="Gear display nickname",
    )
    gear_type: StrictInt = Field(
        ...,
        ge=1,
        le=8,
        description="Gear type identifier",
    )
    created_at: datetime_type | None = Field(
        default=None,
        description="Gear creation timestamp",
    )
    active: StrictBool | None = Field(
        default=None,
        description="Whether the gear is active",
    )
    initial_kms: StrictFloat | None = Field(
        default=None,
        ge=0,
        description="Initial kilometers",
    )
    purchase_value: StrictFloat | None = Field(
        default=None,
        ge=0,
        description="Purchase value",
    )
    strava_gear_id: StrictStr | None = Field(
        default=None,
        max_length=45,
        description="Strava gear ID",
    )
    garminconnect_gear_id: StrictStr | None = Field(
        default=None,
        max_length=45,
        description="Garmin Connect gear ID",
    )

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

GearCreate

Bases: GearBase

Schema for creating a gear record.

Attributes:

Name Type Description
user_id StrictInt | None

Foreign key to user.

Source code in backend/app/gears/gear/schema.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
class GearCreate(GearBase):
    """
    Schema for creating a gear record.

    Attributes:
        user_id: Foreign key to user.
    """

    user_id: StrictInt | None = Field(
        default=None,
        description="Foreign key to user",
    )

GearModel

Bases: Base

Gear data model.

Attributes:

Name Type Description
id Mapped[int]

Primary key.

brand Mapped[str | None]

Gear brand name.

model Mapped[str | None]

Gear model name.

nickname Mapped[str]

Gear nickname.

gear_type Mapped[int]

Gear type identifier.

user_id Mapped[int]

Foreign key to users table.

created_at Mapped[datetime]

Gear creation timestamp.

active Mapped[bool]

Whether the gear is active.

initial_kms Mapped[Decimal]

Initial kilometers.

purchase_value Mapped[Decimal | None]

Gear purchase value.

strava_gear_id Mapped[str | None]

Strava gear ID.

garminconnect_gear_id Mapped[str | None]

Garmin gear ID.

users Mapped[Users]

Relationship to Users model.

activities Mapped[list[Activity]]

Relationship to Activity.

gear_components Mapped[list[GearComponents]]

Components relationship.

users_default_run_gear Mapped[list[UsersDefaultGear]]

Default run gear.

users_default_trail_run_gear Mapped[list[UsersDefaultGear]]

Trail run.

users_default_virtual_run_gear Mapped[list[UsersDefaultGear]]

Virtual run.

users_default_ride_gear Mapped[list[UsersDefaultGear]]

Default ride.

users_default_gravel_ride_gear Mapped[list[UsersDefaultGear]]

Gravel ride.

users_default_mtb_ride_gear Mapped[list[UsersDefaultGear]]

MTB ride.

users_default_virtual_ride_gear Mapped[list[UsersDefaultGear]]

Virtual ride.

users_default_ows_gear Mapped[list[UsersDefaultGear]]

Open water swim.

users_default_walk_gear Mapped[list[UsersDefaultGear]]

Default walk gear.

users_default_hike_gear Mapped[list[UsersDefaultGear]]

Default hike gear.

users_default_tennis_gear Mapped[list[UsersDefaultGear]]

Tennis gear.

users_default_alpine_ski_gear Mapped[list[UsersDefaultGear]]

Alpine ski.

users_default_nordic_ski_gear Mapped[list[UsersDefaultGear]]

Nordic ski.

users_default_snowboard_gear Mapped[list[UsersDefaultGear]]

Snowboard.

users_default_windsurf_gear Mapped[list[UsersDefaultGear]]

Windsurf.

Source code in backend/app/gears/gear/models.py
 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
class Gear(Base):
    """
    Gear data model.

    Attributes:
        id: Primary key.
        brand: Gear brand name.
        model: Gear model name.
        nickname: Gear nickname.
        gear_type: Gear type identifier.
        user_id: Foreign key to users table.
        created_at: Gear creation timestamp.
        active: Whether the gear is active.
        initial_kms: Initial kilometers.
        purchase_value: Gear purchase value.
        strava_gear_id: Strava gear ID.
        garminconnect_gear_id: Garmin gear ID.
        users: Relationship to Users model.
        activities: Relationship to Activity.
        gear_components: Components relationship.
        users_default_run_gear: Default run gear.
        users_default_trail_run_gear: Trail run.
        users_default_virtual_run_gear: Virtual run.
        users_default_ride_gear: Default ride.
        users_default_gravel_ride_gear: Gravel ride.
        users_default_mtb_ride_gear: MTB ride.
        users_default_virtual_ride_gear: Virtual ride.
        users_default_ows_gear: Open water swim.
        users_default_walk_gear: Default walk gear.
        users_default_hike_gear: Default hike gear.
        users_default_tennis_gear: Tennis gear.
        users_default_alpine_ski_gear: Alpine ski.
        users_default_nordic_ski_gear: Nordic ski.
        users_default_snowboard_gear: Snowboard.
        users_default_windsurf_gear: Windsurf.
    """

    __tablename__ = "gear"

    id: Mapped[int] = mapped_column(
        primary_key=True,
        autoincrement=True,
    )
    brand: Mapped[str | None] = mapped_column(
        String(250),
        nullable=True,
        comment="Gear brand (May include spaces)",
    )
    model: Mapped[str | None] = mapped_column(
        String(250),
        nullable=True,
        comment="Gear model (May include spaces)",
    )
    nickname: Mapped[str] = mapped_column(
        String(250),
        index=True,
        nullable=False,
        comment=("Gear nickname (May include spaces)"),
    )
    gear_type: Mapped[int] = mapped_column(
        nullable=False,
        comment=(
            "Gear type (1 - bike, 2 - shoes,"
            " 3 - wetsuit, 4 - racquet,"
            " 5 - skis, 6 - snowboard,"
            " 7 - windsurf,"
            " 8 - water sports board)"
        ),
    )
    user_id: Mapped[int] = mapped_column(
        ForeignKey("users.id", ondelete="CASCADE"),
        nullable=False,
        comment=("User ID that the gear belongs to"),
    )
    created_at: Mapped[datetime_type] = mapped_column(
        DateTime(timezone=True),
        nullable=False,
        default=func.now(),
        comment="Gear creation date (DateTime)",
    )
    active: Mapped[bool] = mapped_column(
        nullable=False,
        comment=("Whether the gear is active (true - yes, false - no)"),
    )
    initial_kms: Mapped[Decimal] = mapped_column(
        Numeric(precision=11, scale=2),
        nullable=False,
        default=0,
        comment=("Initial kilometers of the gear"),
    )
    purchase_value: Mapped[Decimal | None] = mapped_column(
        Numeric(precision=11, scale=2),
        nullable=True,
        comment="Gear purchase value",
    )
    strava_gear_id: Mapped[str | None] = mapped_column(
        String(45),
        unique=True,
        nullable=True,
        comment="Strava gear ID",
    )
    garminconnect_gear_id: Mapped[str | None] = mapped_column(
        String(45),
        unique=True,
        nullable=True,
        comment="Garmin Connect gear ID",
    )

    # Define a relationship to the Users model
    users: Mapped["Users"] = relationship(back_populates="gear")
    # Establish a one-to-many relationship with 'activities'
    activities: Mapped[list["Activity"]] = relationship(back_populates="gear")
    # Establish a one-to-many relationship with 'gear_components'
    gear_components: Mapped[list["GearComponents"]] = relationship(
        back_populates="gear",
        cascade="all, delete-orphan",
        foreign_keys="[GearComponents.gear_id]",
    )
    # Establish a one-to-many relationship with 'users_default_gear'
    users_default_run_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="run_gear",
        foreign_keys="[UsersDefaultGear.run_gear_id]",
    )
    users_default_trail_run_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="trail_run_gear",
        foreign_keys="[UsersDefaultGear.trail_run_gear_id]",
    )
    users_default_virtual_run_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="virtual_run_gear",
        foreign_keys="[UsersDefaultGear.virtual_run_gear_id]",
    )
    users_default_ride_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="ride_gear",
        foreign_keys="[UsersDefaultGear.ride_gear_id]",
    )
    users_default_gravel_ride_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="gravel_ride_gear",
        foreign_keys="[UsersDefaultGear.gravel_ride_gear_id]",
    )
    users_default_mtb_ride_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="mtb_ride_gear",
        foreign_keys="[UsersDefaultGear.mtb_ride_gear_id]",
    )
    users_default_virtual_ride_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="virtual_ride_gear",
        foreign_keys="[UsersDefaultGear.virtual_ride_gear_id]",
    )
    users_default_ows_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="ows_gear",
        foreign_keys="[UsersDefaultGear.ows_gear_id]",
    )
    users_default_walk_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="walk_gear",
        foreign_keys="[UsersDefaultGear.walk_gear_id]",
    )
    users_default_hike_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="hike_gear",
        foreign_keys="[UsersDefaultGear.hike_gear_id]",
    )
    users_default_tennis_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="tennis_gear",
        foreign_keys="[UsersDefaultGear.tennis_gear_id]",
    )
    users_default_alpine_ski_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="alpine_ski_gear",
        foreign_keys="[UsersDefaultGear.alpine_ski_gear_id]",
    )
    users_default_nordic_ski_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="nordic_ski_gear",
        foreign_keys="[UsersDefaultGear.nordic_ski_gear_id]",
    )
    users_default_snowboard_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="snowboard_gear",
        foreign_keys="[UsersDefaultGear.snowboard_gear_id]",
    )
    users_default_windsurf_gear: Mapped[list["UsersDefaultGear"]] = relationship(
        back_populates="windsurf_gear",
        foreign_keys="[UsersDefaultGear.windsurf_gear_id]",
    )

GearRead

Bases: GearBase

Schema for reading a gear record.

Attributes:

Name Type Description
id StrictInt

Unique identifier.

user_id StrictInt

Foreign key to user.

Source code in backend/app/gears/gear/schema.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
class GearRead(GearBase):
    """
    Schema for reading a gear record.

    Attributes:
        id: Unique identifier.
        user_id: Foreign key to user.
    """

    id: StrictInt = Field(
        ...,
        description="Unique identifier",
    )
    user_id: StrictInt = Field(
        ...,
        description="Foreign key to user",
    )

GearUpdate

Bases: GearBase

Schema for updating a gear record.

Attributes:

Name Type Description
id StrictInt

Unique identifier.

Source code in backend/app/gears/gear/schema.py
150
151
152
153
154
155
156
157
158
159
160
161
class GearUpdate(GearBase):
    """
    Schema for updating a gear record.

    Attributes:
        id: Unique identifier.
    """

    id: StrictInt = Field(
        ...,
        description="Unique identifier",
    )

GearsListResponse

Bases: BaseModel

Response model for paginated gear listing.

Attributes:

Name Type Description
total StrictInt

Total number of gear records.

num_records StrictInt | None

Number of records returned.

page_number StrictInt | None

Current page number.

records list[GearRead]

List of gear records.

Source code in backend/app/gears/gear/schema.py
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
class GearsListResponse(BaseModel):
    """
    Response model for paginated gear listing.

    Attributes:
        total: Total number of gear records.
        num_records: Number of records returned.
        page_number: Current page number.
        records: List of gear records.
    """

    total: StrictInt = Field(
        ...,
        ge=0,
        description="Total number of gear records",
    )
    num_records: StrictInt | None = Field(
        default=None,
        ge=0,
        description="Number of records returned",
    )
    page_number: StrictInt | None = Field(
        default=None,
        ge=1,
        description="Current page number",
    )
    records: list[GearRead] = Field(
        ...,
        description="List of gear records",
    )

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

create_gear

create_gear(gear, user_id, db)

Create a single gear for a user.

Parameters:

Name Type Description Default
gear GearCreate

Gear schema with data to create.

required
user_id int

Owner user ID.

required
db Session

Database session.

required

Returns:

Type Description
Gear

Created Gear model.

Raises:

Type Description
HTTPException

If gear already exists or a database error occurs.

Source code in backend/app/gears/gear/crud.py
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
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 create_gear(
    gear: gears_schema.GearCreate,
    user_id: int,
    db: Session,
) -> gears_models.Gear:
    """
    Create a single gear for a user.

    Args:
        gear: Gear schema with data to create.
        user_id: Owner user ID.
        db: Database session.

    Returns:
        Created Gear model.

    Raises:
        HTTPException: If gear already exists or
            a database error occurs.
    """
    gear_check = get_gear_user_by_nickname(
        user_id,
        gear.nickname,
        db,
    )

    if gear_check is not None:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=(f"Gear with nickname {gear.nickname} already exists for user {user_id}"),
        )

    new_gear = gears_utils.transform_schema_gear_to_model_gear(
        gear,
        user_id,
    )

    try:
        db.add(new_gear)
        db.commit()
        db.refresh(new_gear)
    except IntegrityError as integrity_err:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=("Duplicate entry error. Check if strava_gear_id or garminconnect_gear_id are unique"),
        ) from integrity_err

    return new_gear

create_multiple_gears

create_multiple_gears(gears, user_id, db)

Create multiple gears for a user.

Filters invalid entries, deduplicates by nickname, skips existing gears, and persists the rest.

Parameters:

Name Type Description Default
gears list[GearCreate]

List of gear schemas to create.

required
user_id int

Owner user ID.

required
db Session

Database session.

required

Returns:

Type Description
None

None.

Raises:

Type Description
HTTPException

If a database or integrity error occurs.

Source code in backend/app/gears/gear/crud.py
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
365
366
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
@core_decorators.handle_db_errors
def create_multiple_gears(
    gears: list[gears_schema.GearCreate],
    user_id: int,
    db: Session,
) -> None:
    """
    Create multiple gears for a user.

    Filters invalid entries, deduplicates by
    nickname, skips existing gears, and persists
    the rest.

    Args:
        gears: List of gear schemas to create.
        user_id: Owner user ID.
        db: Database session.

    Returns:
        None.

    Raises:
        HTTPException: If a database or
            integrity error occurs.
    """
    # Filter out None and gears without a nickname
    valid_gears = [
        gear
        for gear in (gears or [])
        if gear is not None and getattr(gear, "nickname", None) and str(gear.nickname).replace("+", " ").strip()
    ]

    # De-dupe by nickname (case-insensitive)
    seen: set[str] = set()
    deduped: list[gears_schema.GearCreate] = []
    for gear in valid_gears:
        nickname_normalized = str(gear.nickname).replace("+", " ").lower().strip()
        if nickname_normalized not in seen:
            seen.add(nickname_normalized)
            deduped.append(gear)
        else:
            core_logger.print_to_log_and_console(
                f"Duplicate nickname '{gear.nickname}' in request for user {user_id}, skipping",
                "warning",
            )

    # Skip any that already exist for this user
    gears_to_create: list[gears_schema.GearCreate] = []
    for gear in deduped:
        gear_check = get_gear_user_by_nickname(
            user_id,
            gear.nickname,
            db,
        )
        if gear_check is not None:
            core_logger.print_to_log_and_console(
                f"Gear with nickname '{gear.nickname}' already exists for user {user_id}, skipping",
                "warning",
            )
        else:
            gears_to_create.append(gear)

    # Persist any remaining
    if gears_to_create:
        new_gears = [
            gears_utils.transform_schema_gear_to_model_gear(
                gear,
                user_id,
            )
            for gear in gears_to_create
        ]
        try:
            db.add_all(new_gears)
            db.commit()
            for new_gear in new_gears:
                db.refresh(new_gear)
        except IntegrityError as integrity_err:
            db.rollback()
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=("Duplicate entry error. Check if strava_gear_id or garminconnect_gear_id are unique"),
            ) from integrity_err

delete_all_garminconnect_gear_for_user

delete_all_garminconnect_gear_for_user(user_id, db)

Delete all Garmin Connect-linked gears for a user.

Parameters:

Name Type Description Default
user_id int

Owner user ID.

required
db Session

Database session.

required

Returns:

Type Description
None

None.

Raises:

Type Description
HTTPException

If a database error occurs.

Source code in backend/app/gears/gear/crud.py
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
@core_decorators.handle_db_errors
def delete_all_garminconnect_gear_for_user(user_id: int, db: Session) -> None:
    """
    Delete all Garmin Connect-linked gears for a
    user.

    Args:
        user_id: Owner user ID.
        db: Database session.

    Returns:
        None.

    Raises:
        HTTPException: If a database error occurs.
    """
    stmt = delete(gears_models.Gear).where(
        gears_models.Gear.user_id == user_id,
        gears_models.Gear.garminconnect_gear_id.isnot(None),
    )
    result: CursorResult[Any] = db.execute(stmt)

    if result.rowcount != 0:
        db.commit()

delete_all_strava_gear_for_user

delete_all_strava_gear_for_user(user_id, db)

Delete all Strava-linked gears for a user.

Parameters:

Name Type Description Default
user_id int

Owner user ID.

required
db Session

Database session.

required

Returns:

Type Description
None

None.

Raises:

Type Description
HTTPException

If a database error occurs.

Source code in backend/app/gears/gear/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
@core_decorators.handle_db_errors
def delete_all_strava_gear_for_user(user_id: int, db: Session) -> None:
    """
    Delete all Strava-linked gears for a user.

    Args:
        user_id: Owner user ID.
        db: Database session.

    Returns:
        None.

    Raises:
        HTTPException: If a database error occurs.
    """
    stmt = delete(gears_models.Gear).where(
        gears_models.Gear.user_id == user_id,
        gears_models.Gear.strava_gear_id.isnot(
            None,
        ),
    )
    result: CursorResult[Any] = db.execute(stmt)

    if result.rowcount != 0:
        db.commit()

delete_gear

delete_gear(gear_id, db)

Delete a gear by ID.

Parameters:

Name Type Description Default
gear_id int

Gear ID to delete.

required
db Session

Database session.

required

Returns:

Type Description
None

None.

Raises:

Type Description
HTTPException

If gear not found or a database error occurs.

Source code in backend/app/gears/gear/crud.py
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 delete_gear(gear_id: int, db: Session) -> None:
    """
    Delete a gear by ID.

    Args:
        gear_id: Gear ID to delete.
        db: Database session.

    Returns:
        None.

    Raises:
        HTTPException: If gear not found or
            a database error occurs.
    """
    stmt = delete(gears_models.Gear).where(
        gears_models.Gear.id == gear_id,
    )
    result: CursorResult[Any] = db.execute(stmt)

    if result.rowcount == 0:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=(f"Gear with id {gear_id} not found"),
        )

    db.commit()

edit_gear

edit_gear(gear, db)

Edit an existing gear by ID.

Parameters:

Name Type Description Default
gear GearUpdate

Gear schema with updated fields.

required
db Session

Database session.

required

Returns:

Type Description
Gear

Updated Gear model.

Raises:

Type Description
HTTPException

If a database error occurs.

Source code in backend/app/gears/gear/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
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
@core_decorators.handle_db_errors
def edit_gear(
    gear: gears_schema.GearUpdate,
    db: Session,
) -> gears_models.Gear:
    """
    Edit an existing gear by ID.

    Args:
        gear: Gear schema with updated fields.
        db: Database session.

    Returns:
        Updated Gear model.

    Raises:
        HTTPException: If a database error occurs.
    """
    stmt = select(gears_models.Gear).where(
        gears_models.Gear.id == gear.id,
    )
    db_gear = db.execute(stmt).scalar_one_or_none()

    if db_gear is None:
        raise HTTPException(
            status_code=(status.HTTP_404_NOT_FOUND),
            detail="Gear not found",
        )

    if gear.brand is not None:
        db_gear.brand = unquote(gear.brand).replace("+", " ")
    if gear.model is not None:
        db_gear.model = unquote(gear.model).replace("+", " ")
    if gear.nickname is not None:
        db_gear.nickname = unquote(gear.nickname).replace("+", " ")
    if gear.gear_type is not None:
        db_gear.gear_type = gear.gear_type
    if gear.created_at is not None:
        db_gear.created_at = gear.created_at
    if gear.active is not None:
        db_gear.active = gear.active
    if gear.initial_kms is not None:
        db_gear.initial_kms = gear.initial_kms
    if gear.purchase_value is not None:
        db_gear.purchase_value = gear.purchase_value
    if gear.strava_gear_id is not None:
        db_gear.strava_gear_id = gear.strava_gear_id
    if gear.garminconnect_gear_id is not None:
        db_gear.garminconnect_gear_id = gear.garminconnect_gear_id

    db.commit()
    db.refresh(db_gear)

    return db_gear

get_gear_by_garminconnect_id_from_user_id

get_gear_by_garminconnect_id_from_user_id(gear_garminconnect_id, user_id, db)

Retrieve a gear by Garmin Connect ID for a user.

Parameters:

Name Type Description Default
gear_garminconnect_id str

Garmin Connect gear ID.

required
user_id int

Owner user ID.

required
db Session

Database session.

required

Returns:

Type Description
Gear | None

Gear model if found, None otherwise.

Raises:

Type Description
HTTPException

If a database error occurs.

Source code in backend/app/gears/gear/crud.py
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
@core_decorators.handle_db_errors
def get_gear_by_garminconnect_id_from_user_id(
    gear_garminconnect_id: str,
    user_id: int,
    db: Session,
) -> gears_models.Gear | None:
    """
    Retrieve a gear by Garmin Connect ID for a user.

    Args:
        gear_garminconnect_id: Garmin Connect gear ID.
        user_id: Owner user ID.
        db: Database session.

    Returns:
        Gear model if found, None otherwise.

    Raises:
        HTTPException: If a database error occurs.
    """
    stmt = select(gears_models.Gear).where(
        gears_models.Gear.user_id == user_id,
        gears_models.Gear.garminconnect_gear_id == gear_garminconnect_id,
    )
    return db.execute(stmt).scalar_one_or_none()

get_gear_by_strava_id_from_user_id

get_gear_by_strava_id_from_user_id(gear_strava_id, user_id, db)

Retrieve a gear by Strava ID for a user.

Parameters:

Name Type Description Default
gear_strava_id str

Strava gear identifier.

required
user_id int

Owner user ID.

required
db Session

Database session.

required

Returns:

Type Description
Gear | None

Gear model if found, None otherwise.

Raises:

Type Description
HTTPException

If a database error occurs.

Source code in backend/app/gears/gear/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
@core_decorators.handle_db_errors
def get_gear_by_strava_id_from_user_id(
    gear_strava_id: str,
    user_id: int,
    db: Session,
) -> gears_models.Gear | None:
    """
    Retrieve a gear by Strava ID for a user.

    Args:
        gear_strava_id: Strava gear identifier.
        user_id: Owner user ID.
        db: Database session.

    Returns:
        Gear model if found, None otherwise.

    Raises:
        HTTPException: If a database error occurs.
    """
    stmt = select(gears_models.Gear).where(
        gears_models.Gear.user_id == user_id,
        gears_models.Gear.strava_gear_id == gear_strava_id,
    )
    return db.execute(stmt).scalar_one_or_none()

get_gear_by_type_and_user

get_gear_by_type_and_user(gear_type, user_id, db)

Retrieve gears by type for a specific user.

Parameters:

Name Type Description Default
gear_type int

Gear type identifier.

required
user_id int

Owner user ID.

required
db Session

Database session.

required

Returns:

Type Description
list[Gear]

List of Gear models ordered by nickname.

Raises:

Type Description
HTTPException

If a database error occurs.

Source code in backend/app/gears/gear/crud.py
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 get_gear_by_type_and_user(gear_type: int, user_id: int, db: Session) -> list[gears_models.Gear]:
    """
    Retrieve gears by type for a specific user.

    Args:
        gear_type: Gear type identifier.
        user_id: Owner user ID.
        db: Database session.

    Returns:
        List of Gear models ordered by nickname.

    Raises:
        HTTPException: If a database error occurs.
    """
    stmt = (
        select(gears_models.Gear)
        .where(
            gears_models.Gear.gear_type == gear_type,
            gears_models.Gear.user_id == user_id,
        )
        .order_by(gears_models.Gear.nickname)
    )
    return list(db.execute(stmt).scalars().all())

get_gear_user

get_gear_user(user_id, db)

Retrieve all gears for a specific user.

Parameters:

Name Type Description Default
user_id int

Owner user ID.

required
db Session

Database session.

required

Returns:

Type Description
list[Gear]

List of Gear models for the user.

Raises:

Type Description
HTTPException

If a database error occurs.

Source code in backend/app/gears/gear/crud.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
@core_decorators.handle_db_errors
def get_gear_user(user_id: int, db: Session) -> list[gears_models.Gear]:
    """
    Retrieve all gears for a specific user.

    Args:
        user_id: Owner user ID.
        db: Database session.

    Returns:
        List of Gear models for the user.

    Raises:
        HTTPException: If a database error occurs.
    """
    stmt = select(gears_models.Gear).where(
        gears_models.Gear.user_id == user_id,
    )
    return list(db.execute(stmt).scalars().all())

get_gear_user_by_id

get_gear_user_by_id(user_id, gear_id, db)

Retrieve a gear by ID for a specific user.

Parameters:

Name Type Description Default
user_id int

Owner user ID.

required
gear_id int

Gear ID to fetch.

required
db Session

Database session.

required

Returns:

Type Description
Gear | None

Gear model if found, None otherwise.

Raises:

Type Description
HTTPException

If a database error occurs.

Source code in backend/app/gears/gear/crud.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@core_decorators.handle_db_errors
def get_gear_user_by_id(user_id: int, gear_id: int, db: Session) -> gears_models.Gear | None:
    """
    Retrieve a gear by ID for a specific user.

    Args:
        user_id: Owner user ID.
        gear_id: Gear ID to fetch.
        db: Database session.

    Returns:
        Gear model if found, None otherwise.

    Raises:
        HTTPException: If a database error occurs.
    """
    stmt = select(gears_models.Gear).where(
        gears_models.Gear.user_id == user_id,
        gears_models.Gear.id == gear_id,
    )
    return db.execute(stmt).scalar_one_or_none()

get_gear_user_by_nickname

get_gear_user_by_nickname(user_id, nickname, db)

Retrieve a gear by exact nickname for a user.

Parameters:

Name Type Description Default
user_id int

Owner user ID.

required
nickname str

Gear nickname to match.

required
db Session

Database session.

required

Returns:

Type Description
Gear | None

Gear model if found, None otherwise.

Raises:

Type Description
HTTPException

If a database error occurs.

Source code in backend/app/gears/gear/crud.py
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
@core_decorators.handle_db_errors
def get_gear_user_by_nickname(user_id: int, nickname: str, db: Session) -> gears_models.Gear | None:
    """
    Retrieve a gear by exact nickname for a user.

    Args:
        user_id: Owner user ID.
        nickname: Gear nickname to match.
        db: Database session.

    Returns:
        Gear model if found, None otherwise.

    Raises:
        HTTPException: If a database error occurs.
    """
    parsed_nickname = unquote(nickname).replace("+", " ").lower().strip()

    stmt = select(gears_models.Gear).where(
        func.lower(
            gears_models.Gear.nickname,
        )
        == parsed_nickname,
        gears_models.Gear.user_id == user_id,
    )
    return db.execute(stmt).scalar_one_or_none()

get_gear_user_contains_nickname

get_gear_user_contains_nickname(user_id, nickname, db)

Retrieve gears matching a nickname substring.

Parameters:

Name Type Description Default
user_id int

Owner user ID.

required
nickname str

Substring to search for.

required
db Session

Database session.

required

Returns:

Type Description
list[Gear]

List of Gear models matching criteria.

Raises:

Type Description
HTTPException

If a database error occurs.

Source code in backend/app/gears/gear/crud.py
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
@core_decorators.handle_db_errors
def get_gear_user_contains_nickname(user_id: int, nickname: str, db: Session) -> list[gears_models.Gear]:
    """
    Retrieve gears matching a nickname substring.

    Args:
        user_id: Owner user ID.
        nickname: Substring to search for.
        db: Database session.

    Returns:
        List of Gear models matching criteria.

    Raises:
        HTTPException: If a database error occurs.
    """
    parsed_nickname = unquote(nickname).replace("+", " ").lower().strip()

    stmt = select(gears_models.Gear).where(
        func.lower(
            gears_models.Gear.nickname,
        ).like(f"%{parsed_nickname}%"),
        gears_models.Gear.user_id == user_id,
    )
    return list(db.execute(stmt).scalars().all())

get_gear_users_with_pagination

get_gear_users_with_pagination(user_id, db, page_number=None, num_records=None, show_inactive=True)

Retrieve paginated gears for a user.

Parameters:

Name Type Description Default
user_id int

Owner user ID.

required
db Session

Database session.

required
page_number int | None

Page number (1-indexed). Defaults to None.

None
num_records int | None

Records per page. Defaults to None.

None
show_inactive bool | None

Include inactive gears. Defaults to True.

True

Returns:

Type Description
list[Gear]

List of Gear models ordered by nickname.

Raises:

Type Description
HTTPException

If a database error occurs.

Source code in backend/app/gears/gear/crud.py
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
@core_decorators.handle_db_errors
def get_gear_users_with_pagination(
    user_id: int,
    db: Session,
    page_number: int | None = None,
    num_records: int | None = None,
    show_inactive: bool | None = True,
) -> list[gears_models.Gear]:
    """
    Retrieve paginated gears for a user.

    Args:
        user_id: Owner user ID.
        db: Database session.
        page_number: Page number (1-indexed). Defaults to None.
        num_records: Records per page. Defaults to None.
        show_inactive: Include inactive gears. Defaults to True.

    Returns:
        List of Gear models ordered by nickname.

    Raises:
        HTTPException: If a database error occurs.
    """
    stmt = select(gears_models.Gear).where(
        gears_models.Gear.user_id == user_id,
    )

    if show_inactive is False:
        stmt = stmt.where(
            gears_models.Gear.active.is_(True),
        )

    stmt = stmt.order_by(gears_models.Gear.nickname)

    if page_number is not None and num_records is not None:
        stmt = stmt.offset(
            (page_number - 1) * num_records,
        ).limit(num_records)

    return list(db.execute(stmt).scalars().all())

get_gears_number

get_gears_number(db)

Get total count of gears in the database.

Parameters:

Name Type Description Default
db Session

Database session.

required

Returns:

Type Description
int

Total number of gears.

Raises:

Type Description
HTTPException

If a database error occurs.

Source code in backend/app/gears/gear/crud.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
@core_decorators.handle_db_errors
def get_gears_number(db: Session) -> int:
    """
    Get total count of gears in the database.

    Args:
        db: Database session.

    Returns:
        Total number of gears.

    Raises:
        HTTPException: If a database error occurs.
    """
    stmt = select(func.count(gears_models.Gear.id))
    return db.execute(stmt).scalar_one()

transform_schema_gear_to_model_gear

transform_schema_gear_to_model_gear(gear, user_id)

Convert a gear schema to a gear ORM model.

Parameters:

Name Type Description Default
gear GearCreate

Gear creation schema instance.

required
user_id int

ID of the owning user.

required

Returns:

Type Description
Gear

New gear ORM model instance.

Source code in backend/app/gears/gear/utils.py
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
def transform_schema_gear_to_model_gear(gear: gears_schema.GearCreate, user_id: int) -> gears_models.Gear:
    """
    Convert a gear schema to a gear ORM model.

    Args:
        gear: Gear creation schema instance.
        user_id: ID of the owning user.

    Returns:
        New gear ORM model instance.
    """
    # Set the created date to now
    created_date = func.now()

    # If created_at is provided, use it instead
    if gear.created_at is not None:
        created_date = gear.created_at

    # Create a new gear object
    new_gear = gears_models.Gear(
        brand=(unquote(gear.brand).replace("+", " ").strip() if gear.brand is not None else None),
        model=(unquote(gear.model).replace("+", " ").strip() if gear.model is not None else None),
        nickname=(unquote(gear.nickname).replace("+", " ").strip()),
        gear_type=gear.gear_type,
        user_id=user_id,
        created_at=created_date,
        active=gear.active,
        initial_kms=gear.initial_kms,
        purchase_value=gear.purchase_value,
        strava_gear_id=gear.strava_gear_id,
        garminconnect_gear_id=(gear.garminconnect_gear_id),
    )

    return new_gear