Files
bincio-activity/bincio/serve/models.py
T
Davide Scaini c465e518e5 Add activity file downloads with per-activity download_disabled flag
New endpoint: GET /api/activity/{id}/download/{bas|original|gpx}
- bas: streams the BAS detail JSON as an attachment
- original: streams the original FIT or GPX file from originals/
- gpx: generates a GPX from the timeseries (always available when GPS exists)

download_disabled flag stored in sidecar (edits/{id}.md), propagated to
the merged BAS detail JSON. When set, only the owner can download.

Backend: ops.py writes flag to sidecar; merge.py propagates it to detail
JSON; download.py implements the endpoint; server.py registers the router.
Frontend: EditDrawer gets a "No download" toggle button; ActivityDetail
shows a Download section (hidden when disabled and viewer is not the owner).
2026-05-15 18:35:40 +02:00

94 lines
3.5 KiB
Python

"""Pydantic request/response models for bincio.serve."""
from __future__ import annotations
from typing import Optional
from pydantic import BaseModel, Field
class LoginRequest(BaseModel):
handle: str = Field(..., description="User handle (username)")
password: str = Field(..., description="User password")
class LoginResponse(BaseModel):
ok: bool = Field(True, description="Success flag")
handle: str = Field(..., description="User handle")
display_name: str = Field(..., description="User's display name")
class ResetPasswordRequest(BaseModel):
handle: str = Field(..., description="User handle")
code: str = Field(..., description="Reset code (24 hours valid)")
password: str = Field(..., description="New password (min 8 chars)")
class RegisterRequest(BaseModel):
code: str = Field(..., description="Invite code")
handle: str = Field(..., description="Desired username (lowercase, 1-30 chars)")
password: str = Field(..., description="Password (min 8 characters)")
display_name: str = Field(default="", description="Full name (optional, defaults to handle)")
class RegisterResponse(BaseModel):
ok: bool = Field(True, description="Success flag")
handle: str = Field(..., description="New user's handle")
class CurrentUserResponse(BaseModel):
handle: str = Field(..., description="User handle")
display_name: str = Field(..., description="User's display name")
is_admin: bool = Field(..., description="Whether user is an admin")
wiki_access: bool = Field(default=True, description="Whether user has wiki access")
activity_access: bool = Field(default=False, description="Whether user has activity access")
store_originals_default: bool = Field(
default=True,
description="Instance-wide default for storing original files"
)
dem_configured: bool = Field(default=False, description="Whether DEM elevation lookup is configured")
class ActivityEditRequest(BaseModel):
title: str | None = Field(default=None, description="Activity title")
description: str | None = Field(default=None, description="Activity description (markdown)")
sport: str | None = Field(default=None, description="Sport type")
sub_sport: str | None = Field(default=None, description="Sport sub-category")
private: bool | None = Field(default=None, description="Hide from public feed")
highlight: bool | None = Field(default=None, description="Mark as favorite")
gear: str | None = Field(default=None, description="Gear used")
download_disabled: bool | None = Field(default=None, description="Prevent others from downloading files")
class ActivityEditResponse(BaseModel):
ok: bool = Field(True, description="Success flag")
class ResetPasswordCodeResponse(BaseModel):
ok: bool = Field(True, description="Success flag")
code: str = Field(..., description="One-time reset code")
expires_in_hours: int = Field(24, description="Code validity period in hours")
class GenericResponse(BaseModel):
ok: bool = Field(True, description="Success flag")
class CreateSegmentRequest(BaseModel):
name: str = Field(..., description="Segment name")
sport: Optional[str] = Field(default=None, description="Sport filter")
polyline: list[list[float]] = Field(..., description="[[lat, lon], ...] GPS points")
distance_m: float = Field(..., description="Segment length in metres")
class CreateInviteRequest(BaseModel):
grants_activity: bool = Field(default=False)
class IdeaBody(BaseModel):
title: str
body: str = ""
class IdeaCommentBody(BaseModel):
comment: str = ""