Refactor with composition API, fix logout & bump deps

[backend] removed token printing for debug
[backend] bumped dependencies
[frontend] bumped dependencies
[frontend] moved to composition API in some files
[frontend] added logout toast on success
[frontend] fixed logout not redirecting on some cases
[frontend] removed unused translations on summaryView
[frontend] optimized some dateTimeUtils
This commit is contained in:
João Vitória Silva
2025-06-02 16:13:40 +01:00
parent f2c8b49466
commit 05b2250261
20 changed files with 705 additions and 712 deletions

View File

@@ -4,30 +4,46 @@ from datetime import timedelta, date
from typing import List
from activities.activity.models import Activity
from activities.activity.utils import set_activity_name_based_on_activity_type, ACTIVITY_NAME_TO_ID
from activities.activity.utils import (
set_activity_name_based_on_activity_type,
ACTIVITY_NAME_TO_ID,
)
from activities.activity_summaries.schema import (
WeeklySummaryResponse, MonthlySummaryResponse, YearlySummaryResponse,
DaySummary, WeekSummary, MonthSummary, SummaryMetrics, TypeBreakdownItem,
LifetimeSummaryResponse, YearlyPeriodSummary
WeeklySummaryResponse,
MonthlySummaryResponse,
YearlySummaryResponse,
DaySummary,
WeekSummary,
MonthSummary,
SummaryMetrics,
TypeBreakdownItem,
LifetimeSummaryResponse,
YearlyPeriodSummary,
)
def _get_type_breakdown(db: Session, user_id: int, start_date: date, end_date: date, activity_type: str | None = None) -> List[TypeBreakdownItem]:
def _get_type_breakdown(
db: Session,
user_id: int,
start_date: date,
end_date: date,
activity_type: str | None = None,
) -> List[TypeBreakdownItem]:
"""Helper function to get summary breakdown by activity type, optionally filtered by a specific type."""
query = db.query(
Activity.activity_type.label('activity_type'),
func.coalesce(func.sum(Activity.distance), 0).label('total_distance'),
func.coalesce(func.sum(Activity.total_timer_time), 0.0).label('total_duration'),
func.coalesce(func.sum(Activity.elevation_gain), 0).label('total_elevation_gain'),
func.coalesce(func.sum(Activity.calories), 0).label('total_calories'),
func.count(Activity.id).label('activity_count')
).filter(
Activity.user_id == user_id
)
Activity.activity_type.label("activity_type"),
func.coalesce(func.sum(Activity.distance), 0).label("total_distance"),
func.coalesce(func.sum(Activity.total_timer_time), 0.0).label("total_duration"),
func.coalesce(func.sum(Activity.elevation_gain), 0).label(
"total_elevation_gain"
),
func.coalesce(func.sum(Activity.calories), 0).label("total_calories"),
func.count(Activity.id).label("activity_count"),
).filter(Activity.user_id == user_id)
if not (start_date == date.min and end_date == date.max):
query = query.filter(
Activity.start_time >= start_date,
Activity.start_time < end_date
Activity.start_time >= start_date, Activity.start_time < end_date
)
if activity_type:
@@ -37,43 +53,47 @@ def _get_type_breakdown(db: Session, user_id: int, start_date: date, end_date: d
else:
return []
query = query.group_by(
Activity.activity_type
).order_by(
func.count(Activity.id).desc(),
Activity.activity_type.asc()
query = query.group_by(Activity.activity_type).order_by(
func.count(Activity.id).desc(), Activity.activity_type.asc()
)
type_results = query.all()
type_breakdown_list = []
for row in type_results:
activity_type_name = set_activity_name_based_on_activity_type(row.activity_type)
type_breakdown_list.append(TypeBreakdownItem(
activity_type_id=int(row.activity_type),
activity_type=activity_type_name,
total_distance=float(row.total_distance),
total_duration=float(row.total_duration),
total_elevation_gain=float(row.total_elevation_gain),
total_calories=float(row.total_calories),
activity_count=int(row.activity_count)
))
type_breakdown_list.append(
TypeBreakdownItem(
activity_type_id=int(row.activity_type),
activity_type=activity_type_name,
total_distance=float(row.total_distance),
total_duration=float(row.total_duration),
total_elevation_gain=float(row.total_elevation_gain),
total_calories=float(row.total_calories),
activity_count=int(row.activity_count),
)
)
return type_breakdown_list
def get_weekly_summary(db: Session, user_id: int, target_date: date, activity_type: str | None = None) -> WeeklySummaryResponse:
def get_weekly_summary(
db: Session, user_id: int, target_date: date, activity_type: str | None = None
) -> WeeklySummaryResponse:
start_of_week = target_date - timedelta(days=target_date.weekday())
end_of_week = start_of_week + timedelta(days=7)
query = db.query(
extract('isodow', Activity.start_time).label('day_of_week'),
func.coalesce(func.sum(Activity.distance), 0).label('total_distance'),
func.coalesce(func.sum(Activity.total_timer_time), 0.0).label('total_duration'),
func.coalesce(func.sum(Activity.elevation_gain), 0).label('total_elevation_gain'),
func.coalesce(func.sum(Activity.calories), 0).label('total_calories'),
func.count(Activity.id).label('activity_count')
extract("isodow", Activity.start_time).label("day_of_week"),
func.coalesce(func.sum(Activity.distance), 0).label("total_distance"),
func.coalesce(func.sum(Activity.total_timer_time), 0.0).label("total_duration"),
func.coalesce(func.sum(Activity.elevation_gain), 0).label(
"total_elevation_gain"
),
func.coalesce(func.sum(Activity.calories), 0).label("total_calories"),
func.count(Activity.id).label("activity_count"),
).filter(
Activity.user_id == user_id,
Activity.start_time >= start_of_week,
Activity.start_time < end_of_week
Activity.start_time < end_of_week,
)
activity_type_id = None
@@ -82,12 +102,10 @@ def get_weekly_summary(db: Session, user_id: int, target_date: date, activity_ty
if activity_type_id is not None:
query = query.filter(Activity.activity_type == activity_type_id)
else:
query = query.filter(Activity.id == -1) # Force no results
query = query.filter(Activity.id == -1) # Force no results
query = query.group_by(
extract('isodow', Activity.start_time)
).order_by(
extract('isodow', Activity.start_time)
query = query.group_by(extract("isodow", Activity.start_time)).order_by(
extract("isodow", Activity.start_time)
)
daily_results = query.all()
@@ -105,7 +123,7 @@ def get_weekly_summary(db: Session, user_id: int, target_date: date, activity_ty
total_duration=float(day_data.total_duration),
total_elevation_gain=float(day_data.total_elevation_gain),
total_calories=float(day_data.total_calories),
activity_count=int(day_data.activity_count)
activity_count=int(day_data.activity_count),
)
breakdown.append(day_summary)
overall_metrics.total_distance += day_summary.total_distance
@@ -114,7 +132,7 @@ def get_weekly_summary(db: Session, user_id: int, target_date: date, activity_ty
overall_metrics.total_calories += day_summary.total_calories
overall_metrics.activity_count += day_summary.activity_count
else:
breakdown.append(DaySummary(day_of_week=i - 1))
breakdown.append(DaySummary(day_of_week=i - 1))
return WeeklySummaryResponse(
total_distance=overall_metrics.total_distance,
@@ -123,25 +141,32 @@ def get_weekly_summary(db: Session, user_id: int, target_date: date, activity_ty
total_calories=overall_metrics.total_calories,
activity_count=overall_metrics.activity_count,
breakdown=breakdown,
type_breakdown=_get_type_breakdown(db, user_id, start_of_week, end_of_week, activity_type)
type_breakdown=_get_type_breakdown(
db, user_id, start_of_week, end_of_week, activity_type
),
)
def get_monthly_summary(db: Session, user_id: int, target_date: date, activity_type: str | None = None) -> MonthlySummaryResponse:
def get_monthly_summary(
db: Session, user_id: int, target_date: date, activity_type: str | None = None
) -> MonthlySummaryResponse:
start_of_month = target_date.replace(day=1)
next_month = (start_of_month + timedelta(days=32)).replace(day=1)
end_of_month = next_month
query = db.query(
extract('week', Activity.start_time).label('week_number'),
func.coalesce(func.sum(Activity.distance), 0).label('total_distance'),
func.coalesce(func.sum(Activity.total_timer_time), 0.0).label('total_duration'),
func.coalesce(func.sum(Activity.elevation_gain), 0).label('total_elevation_gain'),
func.coalesce(func.sum(Activity.calories), 0).label('total_calories'),
func.count(Activity.id).label('activity_count')
extract("week", Activity.start_time).label("week_number"),
func.coalesce(func.sum(Activity.distance), 0).label("total_distance"),
func.coalesce(func.sum(Activity.total_timer_time), 0.0).label("total_duration"),
func.coalesce(func.sum(Activity.elevation_gain), 0).label(
"total_elevation_gain"
),
func.coalesce(func.sum(Activity.calories), 0).label("total_calories"),
func.count(Activity.id).label("activity_count"),
).filter(
Activity.user_id == user_id,
Activity.start_time >= start_of_month,
Activity.start_time < end_of_month
Activity.start_time < end_of_month,
)
activity_type_id = None
@@ -150,12 +175,10 @@ def get_monthly_summary(db: Session, user_id: int, target_date: date, activity_t
if activity_type_id is not None:
query = query.filter(Activity.activity_type == activity_type_id)
else:
query = query.filter(Activity.id == -1) # Force no results
query = query.filter(Activity.id == -1) # Force no results
query = query.group_by(
extract('week', Activity.start_time)
).order_by(
extract('week', Activity.start_time)
query = query.group_by(extract("week", Activity.start_time)).order_by(
extract("week", Activity.start_time)
)
weekly_results = query.all()
@@ -169,7 +192,7 @@ def get_monthly_summary(db: Session, user_id: int, target_date: date, activity_t
total_duration=float(week_data.total_duration),
total_elevation_gain=float(week_data.total_elevation_gain),
total_calories=float(week_data.total_calories),
activity_count=int(week_data.activity_count)
activity_count=int(week_data.activity_count),
)
breakdown.append(week_summary)
overall_metrics.total_distance += week_summary.total_distance
@@ -185,24 +208,31 @@ def get_monthly_summary(db: Session, user_id: int, target_date: date, activity_t
total_calories=overall_metrics.total_calories,
activity_count=overall_metrics.activity_count,
breakdown=breakdown,
type_breakdown=_get_type_breakdown(db, user_id, start_of_month, end_of_month, activity_type)
type_breakdown=_get_type_breakdown(
db, user_id, start_of_month, end_of_month, activity_type
),
)
def get_yearly_summary(db: Session, user_id: int, year: int, activity_type: str | None = None) -> YearlySummaryResponse:
def get_yearly_summary(
db: Session, user_id: int, year: int, activity_type: str | None = None
) -> YearlySummaryResponse:
start_of_year = date(year, 1, 1)
end_of_year = date(year + 1, 1, 1)
query = db.query(
extract('month', Activity.start_time).label('month_number'),
func.coalesce(func.sum(Activity.distance), 0).label('total_distance'),
func.coalesce(func.sum(Activity.total_timer_time), 0.0).label('total_duration'),
func.coalesce(func.sum(Activity.elevation_gain), 0).label('total_elevation_gain'),
func.coalesce(func.sum(Activity.calories), 0).label('total_calories'),
func.count(Activity.id).label('activity_count')
extract("month", Activity.start_time).label("month_number"),
func.coalesce(func.sum(Activity.distance), 0).label("total_distance"),
func.coalesce(func.sum(Activity.total_timer_time), 0.0).label("total_duration"),
func.coalesce(func.sum(Activity.elevation_gain), 0).label(
"total_elevation_gain"
),
func.coalesce(func.sum(Activity.calories), 0).label("total_calories"),
func.count(Activity.id).label("activity_count"),
).filter(
Activity.user_id == user_id,
Activity.start_time >= start_of_year,
Activity.start_time < end_of_year
Activity.start_time < end_of_year,
)
activity_type_id = None
@@ -211,12 +241,10 @@ def get_yearly_summary(db: Session, user_id: int, year: int, activity_type: str
if activity_type_id is not None:
query = query.filter(Activity.activity_type == activity_type_id)
else:
query = query.filter(Activity.id == -1) # Force no results
query = query.filter(Activity.id == -1) # Force no results
query = query.group_by(
extract('month', Activity.start_time)
).order_by(
extract('month', Activity.start_time)
query = query.group_by(extract("month", Activity.start_time)).order_by(
extract("month", Activity.start_time)
)
monthly_results = query.all()
@@ -234,7 +262,7 @@ def get_yearly_summary(db: Session, user_id: int, year: int, activity_type: str
total_duration=float(month_data.total_duration),
total_elevation_gain=float(month_data.total_elevation_gain),
total_calories=float(month_data.total_calories),
activity_count=int(month_data.activity_count)
activity_count=int(month_data.activity_count),
)
breakdown.append(month_summary)
overall_metrics.total_distance += month_summary.total_distance
@@ -251,17 +279,24 @@ def get_yearly_summary(db: Session, user_id: int, year: int, activity_type: str
total_calories=overall_metrics.total_calories,
activity_count=overall_metrics.activity_count,
breakdown=breakdown,
type_breakdown=_get_type_breakdown(db, user_id, start_of_year, end_of_year, activity_type)
type_breakdown=_get_type_breakdown(
db, user_id, start_of_year, end_of_year, activity_type
),
)
def get_lifetime_summary(db: Session, user_id: int, activity_type: str | None = None) -> LifetimeSummaryResponse:
def get_lifetime_summary(
db: Session, user_id: int, activity_type: str | None = None
) -> LifetimeSummaryResponse:
# Base query for overall metrics and yearly breakdown
base_metrics_query = db.query(
func.coalesce(func.sum(Activity.distance), 0.0).label('total_distance'),
func.coalesce(func.sum(Activity.total_timer_time), 0.0).label('total_duration'),
func.coalesce(func.sum(Activity.elevation_gain), 0.0).label('total_elevation_gain'),
func.coalesce(func.sum(Activity.calories), 0.0).label('total_calories'),
func.count(Activity.id).label('activity_count')
func.coalesce(func.sum(Activity.distance), 0.0).label("total_distance"),
func.coalesce(func.sum(Activity.total_timer_time), 0.0).label("total_duration"),
func.coalesce(func.sum(Activity.elevation_gain), 0.0).label(
"total_elevation_gain"
),
func.coalesce(func.sum(Activity.calories), 0.0).label("total_calories"),
func.count(Activity.id).label("activity_count"),
).filter(Activity.user_id == user_id)
# Apply activity type filter if provided
@@ -269,7 +304,9 @@ def get_lifetime_summary(db: Session, user_id: int, activity_type: str | None =
if activity_type:
activity_type_id_filter = ACTIVITY_NAME_TO_ID.get(activity_type.lower())
if activity_type_id_filter is not None:
base_metrics_query = base_metrics_query.filter(Activity.activity_type == activity_type_id_filter)
base_metrics_query = base_metrics_query.filter(
Activity.activity_type == activity_type_id_filter
)
else:
# Invalid activity type, force no results for metrics
base_metrics_query = base_metrics_query.filter(Activity.id == -1)
@@ -278,38 +315,46 @@ def get_lifetime_summary(db: Session, user_id: int, activity_type: str | None =
# Yearly breakdown query
yearly_breakdown_query = db.query(
extract('year', Activity.start_time).label('year_number'),
func.coalesce(func.sum(Activity.distance), 0.0).label('total_distance'),
func.coalesce(func.sum(Activity.total_timer_time), 0.0).label('total_duration'),
func.coalesce(func.sum(Activity.elevation_gain), 0.0).label('total_elevation_gain'),
func.coalesce(func.sum(Activity.calories), 0.0).label('total_calories'),
func.count(Activity.id).label('activity_count')
extract("year", Activity.start_time).label("year_number"),
func.coalesce(func.sum(Activity.distance), 0.0).label("total_distance"),
func.coalesce(func.sum(Activity.total_timer_time), 0.0).label("total_duration"),
func.coalesce(func.sum(Activity.elevation_gain), 0.0).label(
"total_elevation_gain"
),
func.coalesce(func.sum(Activity.calories), 0.0).label("total_calories"),
func.count(Activity.id).label("activity_count"),
).filter(Activity.user_id == user_id)
if activity_type: # Apply same activity type filter to breakdown
if activity_type: # Apply same activity type filter to breakdown
if activity_type_id_filter is not None:
yearly_breakdown_query = yearly_breakdown_query.filter(Activity.activity_type == activity_type_id_filter)
yearly_breakdown_query = yearly_breakdown_query.filter(
Activity.activity_type == activity_type_id_filter
)
else:
yearly_breakdown_query = yearly_breakdown_query.filter(Activity.id == -1) # Force no results
yearly_breakdown_query = yearly_breakdown_query.filter(
Activity.id == -1
) # Force no results
yearly_breakdown_query = yearly_breakdown_query.group_by(
extract('year', Activity.start_time)
extract("year", Activity.start_time)
).order_by(
extract('year', Activity.start_time).desc() # Show recent years first
extract("year", Activity.start_time).desc() # Show recent years first
)
yearly_results = yearly_breakdown_query.all()
breakdown_list = []
for row in yearly_results:
breakdown_list.append(YearlyPeriodSummary(
year_number=int(row.year_number),
total_distance=float(row.total_distance),
total_duration=float(row.total_duration),
total_elevation_gain=float(row.total_elevation_gain),
total_calories=float(row.total_calories),
activity_count=int(row.activity_count)
))
breakdown_list.append(
YearlyPeriodSummary(
year_number=int(row.year_number),
total_distance=float(row.total_distance),
total_duration=float(row.total_duration),
total_elevation_gain=float(row.total_elevation_gain),
total_calories=float(row.total_calories),
activity_count=int(row.activity_count),
)
)
# Handle case where overall_totals might be None (e.g., no activities at all for the user/filter)
if overall_totals:
response = LifetimeSummaryResponse(
@@ -319,9 +364,12 @@ def get_lifetime_summary(db: Session, user_id: int, activity_type: str | None =
total_calories=float(overall_totals.total_calories),
activity_count=int(overall_totals.activity_count),
breakdown=breakdown_list,
type_breakdown=_get_type_breakdown(db, user_id, date.min, date.max, activity_type) or []
type_breakdown=_get_type_breakdown(
db, user_id, date.min, date.max, activity_type
)
or [],
)
else: # No activities matching criteria
else: # No activities matching criteria
response = LifetimeSummaryResponse(
total_distance=0.0,
total_duration=0.0,
@@ -329,8 +377,7 @@ def get_lifetime_summary(db: Session, user_id: int, activity_type: str | None =
total_calories=0.0,
activity_count=0,
breakdown=[],
type_breakdown=[]
type_breakdown=[],
)
return response

View File

@@ -1,5 +1,6 @@
from fastapi import HTTPException, status
def validate_view_type(view_type: str):
if (
view_type
@@ -14,4 +15,4 @@ def validate_view_type(view_type: str):
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Invalid view type field",
)
)

View File

@@ -21,8 +21,6 @@ router = APIRouter()
activities_summary_schema.YearlySummaryResponse,
activities_summary_schema.LifetimeSummaryResponse,
],
summary="Get Activity Summary by Period",
description="Retrieves aggregated activity summaries (weekly, monthly, yearly, or lifetime) for a specific user.",
)
async def read_activity_summary(
view_type: str,

View File

@@ -1,6 +1,7 @@
from pydantic import BaseModel
from typing import List
class SummaryMetrics(BaseModel):
total_distance: float = 0.0
total_duration: float = 0.0
@@ -8,39 +9,48 @@ class SummaryMetrics(BaseModel):
activity_count: int = 0
total_calories: float = 0.0
class DaySummary(SummaryMetrics):
day_of_week: int # 0=Monday, 6=Sunday
day_of_week: int # 0=Monday, 6=Sunday
class WeekSummary(SummaryMetrics):
week_number: int
class MonthSummary(SummaryMetrics):
month_number: int # 1=January, 12=December
month_number: int # 1=January, 12=December
class YearlyPeriodSummary(SummaryMetrics):
year_number: int
class TypeBreakdownItem(SummaryMetrics):
"""Schema for breakdown by activity type."""
activity_type_id: int
activity_type: str
class WeeklySummaryResponse(SummaryMetrics):
breakdown: List[DaySummary]
type_breakdown: List[TypeBreakdownItem] | None = None
class MonthlySummaryResponse(SummaryMetrics):
breakdown: List[WeekSummary]
type_breakdown: List[TypeBreakdownItem] | None = None
class YearlySummaryResponse(SummaryMetrics):
breakdown: List[MonthSummary]
type_breakdown: List[TypeBreakdownItem] | None = None
class LifetimeSummaryResponse(SummaryMetrics):
breakdown: List[YearlyPeriodSummary]
type_breakdown: List[TypeBreakdownItem] | None = None
class SummaryParams(BaseModel):
user_id: int
start_date: str

View File

@@ -103,7 +103,7 @@ router.include_router(
)
router.include_router(
activity_summaries_router.router,
prefix=core_config.ROOT_PATH + "/summaries",
prefix=core_config.ROOT_PATH + "/activities/summaries",
tags=["summaries"],
dependencies=[Depends(session_security.validate_access_token)],
)

View File

@@ -159,8 +159,6 @@ def login_garminconnect_using_tokens(oauth1_token, oauth2_token):
def serialize_oauth1_token(token):
try:
print("Serializing OAuth1 token")
print(token)
return {
"oauth_token": core_cryptography.encrypt_token_fernet(token.oauth_token),
"oauth_token_secret": core_cryptography.encrypt_token_fernet(
@@ -184,8 +182,6 @@ def serialize_oauth1_token(token):
def serialize_oauth2_token(token):
try:
print("Serializing OAuth2 token")
print(token)
return {
"scope": token.scope,
"jti": token.jti,
@@ -209,8 +205,6 @@ def serialize_oauth2_token(token):
def deserialize_oauth1_token(data):
try:
print("Deserializing OAuth1 token. Tokens should be encrypted.")
print(data)
return garminconnect.garth.auth_tokens.OAuth1Token(
oauth_token=core_cryptography.decrypt_token_fernet(data["oauth_token"]),
oauth_token_secret=core_cryptography.decrypt_token_fernet(
@@ -234,8 +228,6 @@ def deserialize_oauth1_token(data):
def deserialize_oauth2_token(data):
try:
print("Deserializing OAuth2 token. Tokens should be encrypted.")
print(data)
return garminconnect.garth.auth_tokens.OAuth2Token(
scope=data["scope"],
jti=data["jti"],

148
backend/poetry.lock generated
View File

@@ -971,67 +971,67 @@ test = ["objgraph", "psutil"]
[[package]]
name = "grpcio"
version = "1.71.0"
version = "1.72.1"
description = "HTTP/2-based RPC framework"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "grpcio-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:c200cb6f2393468142eb50ab19613229dcc7829b5ccee8b658a36005f6669fdd"},
{file = "grpcio-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b2266862c5ad664a380fbbcdbdb8289d71464c42a8c29053820ee78ba0119e5d"},
{file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:0ab8b2864396663a5b0b0d6d79495657ae85fa37dcb6498a2669d067c65c11ea"},
{file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c30f393f9d5ff00a71bb56de4aa75b8fe91b161aeb61d39528db6b768d7eac69"},
{file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f250ff44843d9a0615e350c77f890082102a0318d66a99540f54769c8766ab73"},
{file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6d8de076528f7c43a2f576bc311799f89d795aa6c9b637377cc2b1616473804"},
{file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9b91879d6da1605811ebc60d21ab6a7e4bae6c35f6b63a061d61eb818c8168f6"},
{file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f71574afdf944e6652203cd1badcda195b2a27d9c83e6d88dc1ce3cfb73b31a5"},
{file = "grpcio-1.71.0-cp310-cp310-win32.whl", hash = "sha256:8997d6785e93308f277884ee6899ba63baafa0dfb4729748200fcc537858a509"},
{file = "grpcio-1.71.0-cp310-cp310-win_amd64.whl", hash = "sha256:7d6ac9481d9d0d129224f6d5934d5832c4b1cddb96b59e7eba8416868909786a"},
{file = "grpcio-1.71.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:d6aa986318c36508dc1d5001a3ff169a15b99b9f96ef5e98e13522c506b37eef"},
{file = "grpcio-1.71.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:d2c170247315f2d7e5798a22358e982ad6eeb68fa20cf7a820bb74c11f0736e7"},
{file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:e6f83a583ed0a5b08c5bc7a3fe860bb3c2eac1f03f1f63e0bc2091325605d2b7"},
{file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be74ddeeb92cc87190e0e376dbc8fc7736dbb6d3d454f2fa1f5be1dee26b9d7"},
{file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd0dfbe4d5eb1fcfec9490ca13f82b089a309dc3678e2edabc144051270a66e"},
{file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a2242d6950dc892afdf9e951ed7ff89473aaf744b7d5727ad56bdaace363722b"},
{file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0fa05ee31a20456b13ae49ad2e5d585265f71dd19fbd9ef983c28f926d45d0a7"},
{file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3d081e859fb1ebe176de33fc3adb26c7d46b8812f906042705346b314bde32c3"},
{file = "grpcio-1.71.0-cp311-cp311-win32.whl", hash = "sha256:d6de81c9c00c8a23047136b11794b3584cdc1460ed7cbc10eada50614baa1444"},
{file = "grpcio-1.71.0-cp311-cp311-win_amd64.whl", hash = "sha256:24e867651fc67717b6f896d5f0cac0ec863a8b5fb7d6441c2ab428f52c651c6b"},
{file = "grpcio-1.71.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:0ff35c8d807c1c7531d3002be03221ff9ae15712b53ab46e2a0b4bb271f38537"},
{file = "grpcio-1.71.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:b78a99cd1ece4be92ab7c07765a0b038194ded2e0a26fd654591ee136088d8d7"},
{file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:dc1a1231ed23caac1de9f943d031f1bc38d0f69d2a3b243ea0d664fc1fbd7fec"},
{file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6beeea5566092c5e3c4896c6d1d307fb46b1d4bdf3e70c8340b190a69198594"},
{file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5170929109450a2c031cfe87d6716f2fae39695ad5335d9106ae88cc32dc84c"},
{file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5b08d03ace7aca7b2fadd4baf291139b4a5f058805a8327bfe9aece7253b6d67"},
{file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f903017db76bf9cc2b2d8bdd37bf04b505bbccad6be8a81e1542206875d0e9db"},
{file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:469f42a0b410883185eab4689060a20488a1a0a00f8bbb3cbc1061197b4c5a79"},
{file = "grpcio-1.71.0-cp312-cp312-win32.whl", hash = "sha256:ad9f30838550695b5eb302add33f21f7301b882937460dd24f24b3cc5a95067a"},
{file = "grpcio-1.71.0-cp312-cp312-win_amd64.whl", hash = "sha256:652350609332de6dac4ece254e5d7e1ff834e203d6afb769601f286886f6f3a8"},
{file = "grpcio-1.71.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:cebc1b34ba40a312ab480ccdb396ff3c529377a2fce72c45a741f7215bfe8379"},
{file = "grpcio-1.71.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:85da336e3649a3d2171e82f696b5cad2c6231fdd5bad52616476235681bee5b3"},
{file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f9a412f55bb6e8f3bb000e020dbc1e709627dcb3a56f6431fa7076b4c1aab0db"},
{file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47be9584729534660416f6d2a3108aaeac1122f6b5bdbf9fd823e11fe6fbaa29"},
{file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9c80ac6091c916db81131d50926a93ab162a7e97e4428ffc186b6e80d6dda4"},
{file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:789d5e2a3a15419374b7b45cd680b1e83bbc1e52b9086e49308e2c0b5bbae6e3"},
{file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1be857615e26a86d7363e8a163fade914595c81fec962b3d514a4b1e8760467b"},
{file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a76d39b5fafd79ed604c4be0a869ec3581a172a707e2a8d7a4858cb05a5a7637"},
{file = "grpcio-1.71.0-cp313-cp313-win32.whl", hash = "sha256:74258dce215cb1995083daa17b379a1a5a87d275387b7ffe137f1d5131e2cfbb"},
{file = "grpcio-1.71.0-cp313-cp313-win_amd64.whl", hash = "sha256:22c3bc8d488c039a199f7a003a38cb7635db6656fa96437a8accde8322ce2366"},
{file = "grpcio-1.71.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c6a0a28450c16809f94e0b5bfe52cabff63e7e4b97b44123ebf77f448534d07d"},
{file = "grpcio-1.71.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:a371e6b6a5379d3692cc4ea1cb92754d2a47bdddeee755d3203d1f84ae08e03e"},
{file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:39983a9245d37394fd59de71e88c4b295eb510a3555e0a847d9965088cdbd033"},
{file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9182e0063112e55e74ee7584769ec5a0b4f18252c35787f48738627e23a62b97"},
{file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693bc706c031aeb848849b9d1c6b63ae6bcc64057984bb91a542332b75aa4c3d"},
{file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20e8f653abd5ec606be69540f57289274c9ca503ed38388481e98fa396ed0b41"},
{file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8700a2a57771cc43ea295296330daaddc0d93c088f0a35cc969292b6db959bf3"},
{file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d35a95f05a8a2cbe8e02be137740138b3b2ea5f80bd004444e4f9a1ffc511e32"},
{file = "grpcio-1.71.0-cp39-cp39-win32.whl", hash = "sha256:f9c30c464cb2ddfbc2ddf9400287701270fdc0f14be5f08a1e3939f1e749b455"},
{file = "grpcio-1.71.0-cp39-cp39-win_amd64.whl", hash = "sha256:63e41b91032f298b3e973b3fa4093cbbc620c875e2da7b93e249d4728b54559a"},
{file = "grpcio-1.71.0.tar.gz", hash = "sha256:2b85f7820475ad3edec209d3d89a7909ada16caab05d3f2e08a7e8ae3200a55c"},
{file = "grpcio-1.72.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:ce2706ff37be7a6de68fbc4c3f8dde247cab48cc70fee5fedfbc9cd923b4ee5a"},
{file = "grpcio-1.72.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7db9e15ee7618fbea748176a67d347f3100fa92d36acccd0e7eeb741bc82f72a"},
{file = "grpcio-1.72.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:8d6e7764181ba4a8b74aa78c98a89c9f3441068ebcee5d6f14c44578214e0be3"},
{file = "grpcio-1.72.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:237bb619ba33594006025e6f114f62e60d9563afd6f8e89633ee384868e26687"},
{file = "grpcio-1.72.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7f1d8a442fd242aa432c8e1b8411c79ebc409dad2c637614d726e226ce9ed0c"},
{file = "grpcio-1.72.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f2359bd4bba85bf94fd9ab8802671b9637a6803bb673d221157a11523a52e6a8"},
{file = "grpcio-1.72.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3269cfca37570a420a57a785f2a5d4234c5b12aced55f8843dafced2d3f8c9a6"},
{file = "grpcio-1.72.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:06c023d86398714d6257194c21f2bc0b58a53ce45cee87dd3c54c7932c590e17"},
{file = "grpcio-1.72.1-cp310-cp310-win32.whl", hash = "sha256:06dbe54eeea5f9dfb3e7ca2ff66c715ff5fc96b07a1feb322122fe14cb42f6aa"},
{file = "grpcio-1.72.1-cp310-cp310-win_amd64.whl", hash = "sha256:ba593aa2cd52f4468ba29668c83f893d88c128198d6b1273ca788ef53e3ae5fe"},
{file = "grpcio-1.72.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:4e112c083f90c330b0eaa78a633fb206d49c20c443926e827f8cac9eb9d2ea32"},
{file = "grpcio-1.72.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:c6f7e3275832adab7384193f78b8c1a98b82541562fa08d7244e8a6b4b5c78a4"},
{file = "grpcio-1.72.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:dd03c8847c47ef7ac5455aafdfb5e553ecf84f228282bd6106762b379f27c25c"},
{file = "grpcio-1.72.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7497dbdf220b88b66004e2630fb2b1627df5e279db970d3cc20f70d39dce978d"},
{file = "grpcio-1.72.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c2cde3ae8ae901317c049394ed8d3c6964de6b814ae65fc68636a7337b63aa"},
{file = "grpcio-1.72.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7a66cef4bc1db81a54108a849e95650da640c9bc1901957bf7d3b1eeb3251ee8"},
{file = "grpcio-1.72.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fc0435ad45d540597f78978e3fd5515b448193f51f9065fb67dda566336e0f5f"},
{file = "grpcio-1.72.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:524bad78d610fa1f9f316d47b3aab1ff89d438ba952ee34e3e335ca80a27ba96"},
{file = "grpcio-1.72.1-cp311-cp311-win32.whl", hash = "sha256:409ee0abf7e74bbf88941046142452cf3d1f3863d34e11e8fd2b07375170c730"},
{file = "grpcio-1.72.1-cp311-cp311-win_amd64.whl", hash = "sha256:ea483e408fac55569c11158c3e6d6d6a8c3b0f798b68f1c10db9b22c5996e19b"},
{file = "grpcio-1.72.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:65a5ef28e5852bd281c6d01a923906e8036736e95e370acab8626fcbec041e67"},
{file = "grpcio-1.72.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:9e5c594a6c779d674204fb9bdaa1e7b71666ff10b34a62e7769fc6868b5d7511"},
{file = "grpcio-1.72.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:d324f4bdb990d852d79b38c59a12d24fcd47cf3b1a38f2e4d2b6d0b1031bc818"},
{file = "grpcio-1.72.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:841db55dd29cf2f4121b853b2f89813a1b6175163fbb92c5945fb1b0ca259ef2"},
{file = "grpcio-1.72.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00da930aa2711b955a538e835096aa365a4b7f2701bdc2ce1febb242a103f8a1"},
{file = "grpcio-1.72.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4b657773480267fbb7ad733fa85abc103c52ab62e5bc97791faf82c53836eefc"},
{file = "grpcio-1.72.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a08b483f17a6abca2578283a7ae3aa8d4d90347242b0de2898bdb27395c3f20b"},
{file = "grpcio-1.72.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:299f3ea4e03c1d0548f4a174b48d612412f92c667f2100e30a079ab76fdaa813"},
{file = "grpcio-1.72.1-cp312-cp312-win32.whl", hash = "sha256:addc721a3708ff789da1bf69876018dc730c1ec9d3d3cb6912776a00c535a5bc"},
{file = "grpcio-1.72.1-cp312-cp312-win_amd64.whl", hash = "sha256:22ea2aa92a60dff231ba5fcd7f0220a33c2218e556009996f858eeafe294d1c2"},
{file = "grpcio-1.72.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:294be6e9c323a197434569a41e0fb5b5aa0962fd5d55a3dc890ec5df985f611a"},
{file = "grpcio-1.72.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:41ec164dac8df2862f67457d9cdf8d8f8b6a4ca475a3ed1ba6547fff98d93717"},
{file = "grpcio-1.72.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:761736f75c6ddea3732d97eaabe70c616271f5f542a8be95515135fdd1a638f6"},
{file = "grpcio-1.72.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:082003cb93618964c111c70d69b60ac0dc6566d4c254c9b2a775faa2965ba8f8"},
{file = "grpcio-1.72.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8660f736da75424949c14f7c8b1ac60a25b2f37cabdec95181834b405373e8a7"},
{file = "grpcio-1.72.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2ada1abe2ad122b42407b2bfd79d6706a4940d4797f44bd740f5c98ca1ecda9b"},
{file = "grpcio-1.72.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:0db2766d0c482ee740abbe7d00a06cc4fb54f7e5a24d3cf27c3352be18a2b1e8"},
{file = "grpcio-1.72.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4bdb404d9c2187260b34e2b22783c204fba8a9023a166cf77376190d9cf5a08"},
{file = "grpcio-1.72.1-cp313-cp313-win32.whl", hash = "sha256:bb64722c3124c906a5b66e50a90fd36442642f653ba88a24f67d08e94bca59f3"},
{file = "grpcio-1.72.1-cp313-cp313-win_amd64.whl", hash = "sha256:329cc6ff5b431df9614340d3825b066a1ff0a5809a01ba2e976ef48c65a0490b"},
{file = "grpcio-1.72.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:8941b83addd503c1982090b4631804d0ff1edbbc6c85c9c20ed503b1dc65fef9"},
{file = "grpcio-1.72.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:d29b80290c5eda561a4c291d6d5b4315a2a5095ab37061118d6e0781858aca0a"},
{file = "grpcio-1.72.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:4ca56d955564db749c9c6d75e9c4c777854e22b2482d247fb6c5a02d5f28ea78"},
{file = "grpcio-1.72.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b08a3ef14d2b01eef13882c6d3a2d8fb5fcd73db81bd1e3ab69d4ee75215433a"},
{file = "grpcio-1.72.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd7df49801b3b323e4a21047979e3834cd286b32ee5ceee46f5217826274721f"},
{file = "grpcio-1.72.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9717617ba2ff65c058ef53b0d5e50f03e8350f0c5597f93bb5c980a31db990c8"},
{file = "grpcio-1.72.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:212db80b1e8aa7792d51269bfb32164e2333a9bb273370ace3ed2a378505cb01"},
{file = "grpcio-1.72.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a0d19947d4480af5f363f077f221e665931f479e2604280ac4eafe6daa71f77"},
{file = "grpcio-1.72.1-cp39-cp39-win32.whl", hash = "sha256:7622ef647dc911ed010a817d9be501df4ae83495b8e5cdd35b555bdcf3880a3e"},
{file = "grpcio-1.72.1-cp39-cp39-win_amd64.whl", hash = "sha256:f8d8fa7cd2a7f1b4207e215dec8bc07f1202682d9a216ebe028185c15faece30"},
{file = "grpcio-1.72.1.tar.gz", hash = "sha256:87f62c94a40947cec1a0f91f95f5ba0aa8f799f23a1d42ae5be667b6b27b959c"},
]
[package.extras]
protobuf = ["grpcio-tools (>=1.71.0)"]
protobuf = ["grpcio-tools (>=1.72.1)"]
[[package]]
name = "h11"
@@ -1949,23 +1949,23 @@ files = [
[[package]]
name = "protobuf"
version = "5.29.4"
version = "5.29.5"
description = ""
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "protobuf-5.29.4-cp310-abi3-win32.whl", hash = "sha256:13eb236f8eb9ec34e63fc8b1d6efd2777d062fa6aaa68268fb67cf77f6839ad7"},
{file = "protobuf-5.29.4-cp310-abi3-win_amd64.whl", hash = "sha256:bcefcdf3976233f8a502d265eb65ea740c989bacc6c30a58290ed0e519eb4b8d"},
{file = "protobuf-5.29.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:307ecba1d852ec237e9ba668e087326a67564ef83e45a0189a772ede9e854dd0"},
{file = "protobuf-5.29.4-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:aec4962f9ea93c431d5714ed1be1c93f13e1a8618e70035ba2b0564d9e633f2e"},
{file = "protobuf-5.29.4-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:d7d3f7d1d5a66ed4942d4fefb12ac4b14a29028b209d4bfb25c68ae172059922"},
{file = "protobuf-5.29.4-cp38-cp38-win32.whl", hash = "sha256:1832f0515b62d12d8e6ffc078d7e9eb06969aa6dc13c13e1036e39d73bebc2de"},
{file = "protobuf-5.29.4-cp38-cp38-win_amd64.whl", hash = "sha256:476cb7b14914c780605a8cf62e38c2a85f8caff2e28a6a0bad827ec7d6c85d68"},
{file = "protobuf-5.29.4-cp39-cp39-win32.whl", hash = "sha256:fd32223020cb25a2cc100366f1dedc904e2d71d9322403224cdde5fdced0dabe"},
{file = "protobuf-5.29.4-cp39-cp39-win_amd64.whl", hash = "sha256:678974e1e3a9b975b8bc2447fca458db5f93a2fb6b0c8db46b6675b5b5346812"},
{file = "protobuf-5.29.4-py3-none-any.whl", hash = "sha256:3fde11b505e1597f71b875ef2fc52062b6a9740e5f7c8997ce878b6009145862"},
{file = "protobuf-5.29.4.tar.gz", hash = "sha256:4f1dfcd7997b31ef8f53ec82781ff434a28bf71d9102ddde14d076adcfc78c99"},
{file = "protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079"},
{file = "protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc"},
{file = "protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671"},
{file = "protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015"},
{file = "protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61"},
{file = "protobuf-5.29.5-cp38-cp38-win32.whl", hash = "sha256:ef91363ad4faba7b25d844ef1ada59ff1604184c0bcd8b39b8a6bef15e1af238"},
{file = "protobuf-5.29.5-cp38-cp38-win_amd64.whl", hash = "sha256:7318608d56b6402d2ea7704ff1e1e4597bee46d760e7e4dd42a3d45e24b87f2e"},
{file = "protobuf-5.29.5-cp39-cp39-win32.whl", hash = "sha256:6f642dc9a61782fa72b90878af134c5afe1917c89a568cd3476d758d3c3a0736"},
{file = "protobuf-5.29.5-cp39-cp39-win_amd64.whl", hash = "sha256:470f3af547ef17847a28e1f47200a1cbf0ba3ff57b7de50d22776607cd2ea353"},
{file = "protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5"},
{file = "protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84"},
]
[[package]]
@@ -2735,14 +2735,14 @@ files = [
[[package]]
name = "typing-extensions"
version = "4.13.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
version = "4.14.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.8"
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"},
{file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"},
{file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"},
{file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"},
]
[[package]]
@@ -2857,14 +2857,14 @@ ua-parser = ">=0.10.0"
[[package]]
name = "uvicorn"
version = "0.34.2"
version = "0.34.3"
description = "The lightning-fast ASGI server."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403"},
{file = "uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328"},
{file = "uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885"},
{file = "uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a"},
]
[package.dependencies]
@@ -2872,7 +2872,7 @@ click = ">=7.0"
h11 = ">=0.8"
[package.extras]
standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
[[package]]
name = "virtualenv"

View File

@@ -93,9 +93,9 @@
}
},
"node_modules/@babel/core": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.3.tgz",
"integrity": "sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==",
"version": "7.27.4",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz",
"integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -104,10 +104,10 @@
"@babel/generator": "^7.27.3",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.27.3",
"@babel/helpers": "^7.27.3",
"@babel/parser": "^7.27.3",
"@babel/helpers": "^7.27.4",
"@babel/parser": "^7.27.4",
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.27.3",
"@babel/traverse": "^7.27.4",
"@babel/types": "^7.27.3",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
@@ -440,9 +440,9 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.3.tgz",
"integrity": "sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==",
"version": "7.27.4",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz",
"integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -454,9 +454,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.3.tgz",
"integrity": "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==",
"version": "7.27.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz",
"integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.3"
@@ -1265,9 +1265,9 @@
}
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.1.tgz",
"integrity": "sha512-B19lbbL7PMrKr52BNPjCqg1IyNUIjTcxKj8uX9zHO+PmWN93s19NDr/f69mIkEp2x9nmDJ08a7lgHaTTzvW7mw==",
"version": "7.27.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.4.tgz",
"integrity": "sha512-Glp/0n8xuj+E1588otw5rjJkTXfzW7FjH3IIUrfqiZOPQCd2vbg8e+DQE8jK9g4V5/zrxFW+D9WM9gboRPELpQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1571,9 +1571,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.3.tgz",
"integrity": "sha512-7EYtGezsdiDMyY80+65EzwiGmcJqpmcZCojSXaRgdrBaGtWTgDZKq69cPIVped6MkIM78cTQ2GOiEYjwOlG4xw==",
"version": "7.27.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.4.tgz",
"integrity": "sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1596,15 +1596,15 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.3.tgz",
"integrity": "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ==",
"version": "7.27.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz",
"integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.27.3",
"@babel/parser": "^7.27.3",
"@babel/parser": "^7.27.4",
"@babel/template": "^7.27.2",
"@babel/types": "^7.27.3",
"debug": "^4.3.1",
@@ -1658,9 +1658,9 @@
}
},
"node_modules/@csstools/css-calc": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz",
"integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
"integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
"dev": true,
"funding": [
{
@@ -1677,14 +1677,14 @@
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.4",
"@csstools/css-tokenizer": "^3.0.3"
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-color-parser": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz",
"integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==",
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz",
"integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==",
"dev": true,
"funding": [
{
@@ -1699,20 +1699,20 @@
"license": "MIT",
"dependencies": {
"@csstools/color-helpers": "^5.0.2",
"@csstools/css-calc": "^2.1.3"
"@csstools/css-calc": "^2.1.4"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.4",
"@csstools/css-tokenizer": "^3.0.3"
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-parser-algorithms": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz",
"integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
"integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
"dev": true,
"funding": [
{
@@ -1729,13 +1729,13 @@
"node": ">=18"
},
"peerDependencies": {
"@csstools/css-tokenizer": "^3.0.3"
"@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-tokenizer": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz",
"integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
"dev": true,
"funding": [
{
@@ -2282,9 +2282,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.27.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz",
"integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==",
"version": "9.28.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz",
"integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2452,13 +2452,13 @@
}
},
"node_modules/@intlify/core-base": {
"version": "11.1.4",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.1.4.tgz",
"integrity": "sha512-VNIanL84HNBNAoJjPA2V8EykT5NtgNDquO2MsDQcSheo7EcCt4uvH14IHBEDKVoL6k38NNICLuRhtKOKqW2ylA==",
"version": "11.1.5",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.1.5.tgz",
"integrity": "sha512-xGRkISwV/2Trqb8yVQevlHm5roaQqy+75qwUzEQrviaQF0o4c5VDhjBW7WEGEoKFx09HSgq7NkvK/DAyuerTDg==",
"license": "MIT",
"dependencies": {
"@intlify/message-compiler": "11.1.4",
"@intlify/shared": "11.1.4"
"@intlify/message-compiler": "11.1.5",
"@intlify/shared": "11.1.5"
},
"engines": {
"node": ">= 16"
@@ -2468,12 +2468,12 @@
}
},
"node_modules/@intlify/message-compiler": {
"version": "11.1.4",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.1.4.tgz",
"integrity": "sha512-fQWJwTOBFNFGNr4I5k629hQxTGEKsDWhhTzr6Y4CN4OXJw/dLB/VbbQm5jlylqnv44RBZN5GSD+d1nWpNcAR5A==",
"version": "11.1.5",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.1.5.tgz",
"integrity": "sha512-YLSBbjD7qUdShe3ZAat9Hnf9E8FRpN6qmNFD/x5Xg5JVXjsks0kJ90Zj6aAuyoppJQA/YJdWZ8/bB7k3dg2TjQ==",
"license": "MIT",
"dependencies": {
"@intlify/shared": "11.1.4",
"@intlify/shared": "11.1.5",
"source-map-js": "^1.0.2"
},
"engines": {
@@ -2484,9 +2484,9 @@
}
},
"node_modules/@intlify/shared": {
"version": "11.1.4",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.1.4.tgz",
"integrity": "sha512-zOW2L5+QnWRQgM/7WNSPxa6E0F3wR2/KEQV7P4s4AXzxzmg0MuzLNiixvkRJU5h0Xb3DnHic6zybKva28kabDw==",
"version": "11.1.5",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.1.5.tgz",
"integrity": "sha512-+I4vRzHm38VjLr/CAciEPJhGYFzWWW4HMTm+6H3WqknXLh0ozNX9oC8ogMUwTSXYR/wGUb1/lTpNziiCH5MybQ==",
"license": "MIT",
"engines": {
"node": ">= 16"
@@ -2601,9 +2601,9 @@
}
},
"node_modules/@pkgr/core": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz",
"integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==",
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz",
"integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3021,6 +3021,23 @@
"@popperjs/core": "^2.9.2"
}
},
"node_modules/@types/chai": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz",
"integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/deep-eql": "*"
}
},
"node_modules/@types/deep-eql": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/estree": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
@@ -3081,14 +3098,15 @@
}
},
"node_modules/@vitest/expect": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.4.tgz",
"integrity": "sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.0.tgz",
"integrity": "sha512-0v4YVbhDKX3SKoy0PHWXpKhj44w+3zZkIoVES9Ex2pq+u6+Bijijbi2ua5kE+h3qT6LBWFTNZSCOEU37H8Y5sA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "3.1.4",
"@vitest/utils": "3.1.4",
"@types/chai": "^5.2.2",
"@vitest/spy": "3.2.0",
"@vitest/utils": "3.2.0",
"chai": "^5.2.0",
"tinyrainbow": "^2.0.0"
},
@@ -3097,13 +3115,13 @@
}
},
"node_modules/@vitest/mocker": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.4.tgz",
"integrity": "sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.0.tgz",
"integrity": "sha512-HFcW0lAMx3eN9vQqis63H0Pscv0QcVMo1Kv8BNysZbxcmHu3ZUYv59DS6BGYiGQ8F5lUkmsfMMlPm4DJFJdf/A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "3.1.4",
"@vitest/spy": "3.2.0",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.17"
},
@@ -3112,7 +3130,7 @@
},
"peerDependencies": {
"msw": "^2.4.9",
"vite": "^5.0.0 || ^6.0.0"
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
},
"peerDependenciesMeta": {
"msw": {
@@ -3124,9 +3142,9 @@
}
},
"node_modules/@vitest/pretty-format": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.4.tgz",
"integrity": "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.0.tgz",
"integrity": "sha512-gUUhaUmPBHFkrqnOokmfMGRBMHhgpICud9nrz/xpNV3/4OXCn35oG+Pl8rYYsKaTNd/FAIrqRHnwpDpmYxCYZw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3137,13 +3155,13 @@
}
},
"node_modules/@vitest/runner": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.4.tgz",
"integrity": "sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.0.tgz",
"integrity": "sha512-bXdmnHxuB7fXJdh+8vvnlwi/m1zvu+I06i1dICVcDQFhyV4iKw2RExC/acavtDn93m/dRuawUObKsrNE1gJacA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "3.1.4",
"@vitest/utils": "3.2.0",
"pathe": "^2.0.3"
},
"funding": {
@@ -3151,13 +3169,13 @@
}
},
"node_modules/@vitest/snapshot": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.4.tgz",
"integrity": "sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.0.tgz",
"integrity": "sha512-z7P/EneBRMe7hdvWhcHoXjhA6at0Q4ipcoZo6SqgxLyQQ8KSMMCmvw1cSt7FHib3ozt0wnRHc37ivuUMbxzG/A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "3.1.4",
"@vitest/pretty-format": "3.2.0",
"magic-string": "^0.30.17",
"pathe": "^2.0.3"
},
@@ -3166,26 +3184,26 @@
}
},
"node_modules/@vitest/spy": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.4.tgz",
"integrity": "sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.0.tgz",
"integrity": "sha512-s3+TkCNUIEOX99S0JwNDfsHRaZDDZZR/n8F0mop0PmsEbQGKZikCGpTGZ6JRiHuONKew3Fb5//EPwCP+pUX9cw==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinyspy": "^3.0.2"
"tinyspy": "^4.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/utils": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.4.tgz",
"integrity": "sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.0.tgz",
"integrity": "sha512-gXXOe7Fj6toCsZKVQouTRLJftJwmvbhH5lKOBR6rlP950zUq9AitTUjnFoXS/CqjBC2aoejAztLPzzuva++XBw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "3.1.4",
"@vitest/pretty-format": "3.2.0",
"loupe": "^3.1.3",
"tinyrainbow": "^2.0.0"
},
@@ -3194,13 +3212,13 @@
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.15.tgz",
"integrity": "sha512-nGRc6YJg/kxNqbv/7Tg4juirPnjHvuVdhcmDvQWVZXlLHjouq7VsKmV1hIxM/8yKM0VUfwT/Uzc0lO510ltZqw==",
"version": "3.5.16",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.16.tgz",
"integrity": "sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.27.2",
"@vue/shared": "3.5.15",
"@vue/shared": "3.5.16",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
@@ -3225,26 +3243,26 @@
"license": "MIT"
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.15.tgz",
"integrity": "sha512-ZelQd9n+O/UCBdL00rlwCrsArSak+YLZpBVuNDio1hN3+wrCshYZEDUO3khSLAzPbF1oQS2duEoMDUHScUlYjA==",
"version": "3.5.16",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz",
"integrity": "sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.15",
"@vue/shared": "3.5.15"
"@vue/compiler-core": "3.5.16",
"@vue/shared": "3.5.16"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.15.tgz",
"integrity": "sha512-3zndKbxMsOU6afQWer75Zot/aydjtxNj0T2KLg033rAFaQUn2PGuE32ZRe4iMhflbTcAxL0yEYsRWFxtPro8RQ==",
"version": "3.5.16",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz",
"integrity": "sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.27.2",
"@vue/compiler-core": "3.5.15",
"@vue/compiler-dom": "3.5.15",
"@vue/compiler-ssr": "3.5.15",
"@vue/shared": "3.5.15",
"@vue/compiler-core": "3.5.16",
"@vue/compiler-dom": "3.5.16",
"@vue/compiler-ssr": "3.5.16",
"@vue/shared": "3.5.16",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.17",
"postcss": "^8.5.3",
@@ -3258,13 +3276,13 @@
"license": "MIT"
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.15.tgz",
"integrity": "sha512-gShn8zRREZbrXqTtmLSCffgZXDWv8nHc/GhsW+mbwBfNZL5pI96e7IWcIq8XGQe1TLtVbu7EV9gFIVSmfyarPg==",
"version": "3.5.16",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz",
"integrity": "sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.15",
"@vue/shared": "3.5.15"
"@vue/compiler-dom": "3.5.16",
"@vue/shared": "3.5.16"
}
},
"node_modules/@vue/devtools-api": {
@@ -3316,53 +3334,53 @@
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.15",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.15.tgz",
"integrity": "sha512-GaA5VUm30YWobCwpvcs9nvFKf27EdSLKDo2jA0IXzGS344oNpFNbEQ9z+Pp5ESDaxyS8FcH0vFN/XSe95BZtHQ==",
"version": "3.5.16",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.16.tgz",
"integrity": "sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.15"
"@vue/shared": "3.5.16"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.15",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.15.tgz",
"integrity": "sha512-CZAlIOQ93nj0OPpWWOx4+QDLCMzBNY85IQR4Voe6vIID149yF8g9WQaWnw042f/6JfvLttK7dnyWlC1EVCRK8Q==",
"version": "3.5.16",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.16.tgz",
"integrity": "sha512-bw5Ykq6+JFHYxrQa7Tjr+VSzw7Dj4ldR/udyBZbq73fCdJmyy5MPIFR9IX/M5Qs+TtTjuyUTCnmK3lWWwpAcFQ==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.15",
"@vue/shared": "3.5.15"
"@vue/reactivity": "3.5.16",
"@vue/shared": "3.5.16"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.15",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.15.tgz",
"integrity": "sha512-wFplHKzKO/v998up2iCW3RN9TNUeDMhdBcNYZgs5LOokHntrB48dyuZHspcahKZczKKh3v6i164gapMPxBTKNw==",
"version": "3.5.16",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.16.tgz",
"integrity": "sha512-T1qqYJsG2xMGhImRUV9y/RseB9d0eCYZQ4CWca9ztCuiPj/XWNNN+lkNBuzVbia5z4/cgxdL28NoQCvC0Xcfww==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.15",
"@vue/runtime-core": "3.5.15",
"@vue/shared": "3.5.15",
"@vue/reactivity": "3.5.16",
"@vue/runtime-core": "3.5.16",
"@vue/shared": "3.5.16",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.15",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.15.tgz",
"integrity": "sha512-Gehc693kVTYkLt6QSYEjGvqvdK2zZ/gf/D5zkgmvBdeB30dNnVZS8yY7+IlBmHRd1rR/zwaqeu06Ij04ZxBscg==",
"version": "3.5.16",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.16.tgz",
"integrity": "sha512-BrX0qLiv/WugguGsnQUJiYOE0Fe5mZTwi6b7X/ybGB0vfrPH9z0gD/Y6WOR1sGCgX4gc25L1RYS5eYQKDMoNIg==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.15",
"@vue/shared": "3.5.15"
"@vue/compiler-ssr": "3.5.16",
"@vue/shared": "3.5.16"
},
"peerDependencies": {
"vue": "3.5.15"
"vue": "3.5.16"
}
},
"node_modules/@vue/shared": {
"version": "3.5.15",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.15.tgz",
"integrity": "sha512-bKvgFJJL1ZX9KxMCTQY6xD9Dhe3nusd1OhyOb1cJYGqvAr0Vg8FIjHPMOEVbJ9GDT9HG+Bjdn4oS8ohKP8EvoA==",
"version": "3.5.16",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz",
"integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==",
"license": "MIT"
},
"node_modules/@vue/test-utils": {
@@ -3670,9 +3688,9 @@
}
},
"node_modules/browserslist": {
"version": "4.24.5",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz",
"integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==",
"version": "4.25.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz",
"integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==",
"dev": true,
"funding": [
{
@@ -3690,8 +3708,8 @@
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001716",
"electron-to-chromium": "^1.5.149",
"caniuse-lite": "^1.0.30001718",
"electron-to-chromium": "^1.5.160",
"node-releases": "^2.0.19",
"update-browserslist-db": "^1.1.3"
},
@@ -3780,9 +3798,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001718",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz",
"integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==",
"version": "1.0.30001720",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz",
"integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==",
"dev": true,
"funding": [
{
@@ -4248,9 +4266,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.158",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.158.tgz",
"integrity": "sha512-9vcp2xHhkvraY6AHw2WMi+GDSLPX42qe2xjYaVoZqFRJiOcilVQFq9mZmpuHEQpzlgGDelKlV7ZiGcmMsc8WxQ==",
"version": "1.5.161",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz",
"integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==",
"dev": true,
"license": "ISC"
},
@@ -4275,9 +4293,9 @@
}
},
"node_modules/es-abstract": {
"version": "1.23.10",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.10.tgz",
"integrity": "sha512-MtUbM072wlJNyeYAe0mhzrD+M6DIJa96CZAOBBrhDbgKnB4MApIKefcyAB1eOdYn8cUNZgvwBvEzdoAYsxgEIw==",
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
"integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4308,7 +4326,9 @@
"is-array-buffer": "^3.0.5",
"is-callable": "^1.2.7",
"is-data-view": "^1.0.2",
"is-negative-zero": "^2.0.3",
"is-regex": "^1.2.1",
"is-set": "^2.0.3",
"is-shared-array-buffer": "^1.0.4",
"is-string": "^1.1.1",
"is-typed-array": "^1.1.15",
@@ -4323,6 +4343,7 @@
"safe-push-apply": "^1.0.0",
"safe-regex-test": "^1.1.0",
"set-proto": "^1.0.0",
"stop-iteration-iterator": "^1.1.0",
"string.prototype.trim": "^1.2.10",
"string.prototype.trimend": "^1.0.9",
"string.prototype.trimstart": "^1.0.8",
@@ -4479,9 +4500,9 @@
}
},
"node_modules/eslint": {
"version": "9.27.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz",
"integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==",
"version": "9.28.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz",
"integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4491,7 +4512,7 @@
"@eslint/config-helpers": "^0.2.1",
"@eslint/core": "^0.14.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.27.0",
"@eslint/js": "9.28.0",
"@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -4556,14 +4577,14 @@
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz",
"integrity": "sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA==",
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz",
"integrity": "sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.11.0"
"synckit": "^0.11.7"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -4798,9 +4819,9 @@
"license": "BSD-3-Clause"
},
"node_modules/fdir": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz",
"integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -4876,9 +4897,9 @@
}
},
"node_modules/flag-icons": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-7.3.2.tgz",
"integrity": "sha512-QkaZ6Zvai8LIjx+UNAHUJ5Dhz9OLZpBDwCRWxF6YErxIcR16jTkIFm3bFu54EkvKQy4+wicW+Gm7/0631wVQyQ==",
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-7.5.0.tgz",
"integrity": "sha512-kd+MNXviFIg5hijH766tt+3x76ele1AXlo4zDdCxIvqWZhKt4T83bOtxUOOMlTx/EcFdUMH5yvQgYlFh1EqqFg==",
"license": "MIT"
},
"node_modules/flat-cache": {
@@ -5660,6 +5681,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/is-negative-zero": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
"integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-number-object": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
@@ -6637,9 +6671,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"version": "8.5.4",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz",
"integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==",
"funding": [
{
"type": "opencollective",
@@ -6656,7 +6690,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.8",
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -7364,6 +7398,20 @@
"dev": true,
"license": "MIT"
},
"node_modules/stop-iteration-iterator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
"integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"internal-slot": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -7639,9 +7687,9 @@
"license": "MIT"
},
"node_modules/synckit": {
"version": "0.11.6",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.6.tgz",
"integrity": "sha512-2pR2ubZSV64f/vqm9eLPz/KOvR9Dm+Co/5ChLgeHl0yEDRc6h5hXHoxEQH8Y5Ljycozd3p1k5TTSVdzYGkPvLw==",
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz",
"integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7684,9 +7732,9 @@
}
},
"node_modules/terser": {
"version": "5.39.2",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.2.tgz",
"integrity": "sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==",
"version": "5.40.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.40.0.tgz",
"integrity": "sha512-cfeKl/jjwSR5ar7d0FGmave9hFGJT8obyo0z+CrQOylLDbk7X81nPU6vq9VORa5jU30SkDnT2FXjLbR8HLP+xA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -7741,9 +7789,9 @@
}
},
"node_modules/tinypool": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz",
"integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.0.tgz",
"integrity": "sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -7761,9 +7809,9 @@
}
},
"node_modules/tinyspy": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
"integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz",
"integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8141,17 +8189,17 @@
}
},
"node_modules/vite-node": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.4.tgz",
"integrity": "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.0.tgz",
"integrity": "sha512-8Fc5Ko5Y4URIJkmMF/iFP1C0/OJyY+VGVe9Nw6WAdZyw4bTO+eVg9mwxWkQp/y8NnAoQY3o9KAvE1ZdA2v+Vmg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cac": "^6.7.14",
"debug": "^4.4.0",
"debug": "^4.4.1",
"es-module-lexer": "^1.7.0",
"pathe": "^2.0.3",
"vite": "^5.0.0 || ^6.0.0"
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
},
"bin": {
"vite-node": "vite-node.mjs"
@@ -8195,32 +8243,34 @@
}
},
"node_modules/vitest": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.4.tgz",
"integrity": "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.0.tgz",
"integrity": "sha512-P7Nvwuli8WBNmeMHHek7PnGW4oAZl9za1fddfRVidZar8wDZRi7hpznLKQePQ8JPLwSBEYDK11g+++j7uFJV8Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/expect": "3.1.4",
"@vitest/mocker": "3.1.4",
"@vitest/pretty-format": "^3.1.4",
"@vitest/runner": "3.1.4",
"@vitest/snapshot": "3.1.4",
"@vitest/spy": "3.1.4",
"@vitest/utils": "3.1.4",
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.0",
"@vitest/mocker": "3.2.0",
"@vitest/pretty-format": "^3.2.0",
"@vitest/runner": "3.2.0",
"@vitest/snapshot": "3.2.0",
"@vitest/spy": "3.2.0",
"@vitest/utils": "3.2.0",
"chai": "^5.2.0",
"debug": "^4.4.0",
"debug": "^4.4.1",
"expect-type": "^1.2.1",
"magic-string": "^0.30.17",
"pathe": "^2.0.3",
"picomatch": "^4.0.2",
"std-env": "^3.9.0",
"tinybench": "^2.9.0",
"tinyexec": "^0.3.2",
"tinyglobby": "^0.2.13",
"tinypool": "^1.0.2",
"tinyglobby": "^0.2.14",
"tinypool": "^1.1.0",
"tinyrainbow": "^2.0.0",
"vite": "^5.0.0 || ^6.0.0",
"vite-node": "3.1.4",
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
"vite-node": "3.2.0",
"why-is-node-running": "^2.3.0"
},
"bin": {
@@ -8236,8 +8286,8 @@
"@edge-runtime/vm": "*",
"@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"@vitest/browser": "3.1.4",
"@vitest/ui": "3.1.4",
"@vitest/browser": "3.2.0",
"@vitest/ui": "3.2.0",
"happy-dom": "*",
"jsdom": "*"
},
@@ -8266,16 +8316,16 @@
}
},
"node_modules/vue": {
"version": "3.5.15",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.15.tgz",
"integrity": "sha512-aD9zK4rB43JAMK/5BmS4LdPiEp8Fdh8P1Ve/XNuMF5YRf78fCyPE6FUbQwcaWQ5oZ1R2CD9NKE0FFOVpMR7gEQ==",
"version": "3.5.16",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.16.tgz",
"integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.15",
"@vue/compiler-sfc": "3.5.15",
"@vue/runtime-dom": "3.5.15",
"@vue/server-renderer": "3.5.15",
"@vue/shared": "3.5.15"
"@vue/compiler-dom": "3.5.16",
"@vue/compiler-sfc": "3.5.16",
"@vue/runtime-dom": "3.5.16",
"@vue/server-renderer": "3.5.16",
"@vue/shared": "3.5.16"
},
"peerDependencies": {
"typescript": "*"
@@ -8367,13 +8417,13 @@
}
},
"node_modules/vue-i18n": {
"version": "11.1.4",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.1.4.tgz",
"integrity": "sha512-0B2Q4rTSzQigfIQnsgNMgWOekouT2lr3hiKG3k7q3fQykr968BRdIUDnIvHisq/f1FPKbBznHpvAyGg78eDAyg==",
"version": "11.1.5",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.1.5.tgz",
"integrity": "sha512-XCwuaEA5AF97g1frvH/EI1zI9uo1XKTf2/OCFgts7NvUWRsjlgeHPrkJV+a3gpzai2pC4quZ4AnOHFO8QK9hsg==",
"license": "MIT",
"dependencies": {
"@intlify/core-base": "11.1.4",
"@intlify/shared": "11.1.4",
"@intlify/core-base": "11.1.5",
"@intlify/shared": "11.1.5",
"@vue/devtools-api": "^6.5.0"
},
"engines": {

View File

@@ -29,43 +29,13 @@
</router-link>
</div>
</nav>
<FooterComponent v-else/>
<FooterComponent v-else />
</template>
<script>
import { useRouter } from "vue-router";
// Importing the i18n
import { useI18n } from "vue-i18n";
// import the stores
<script setup>
import { useAuthStore } from "@/stores/authStore";
// Import the components
import FooterComponent from "@/components/FooterComponent.vue";
import UserAvatarComponent from "@/components/Users/UserAvatarComponent.vue";
// Import Notivue push
import { push } from "notivue";
export default {
components: {
UserAvatarComponent,
FooterComponent,
},
setup() {
const router = useRouter();
const authStore = useAuthStore();
const { locale, t } = useI18n();
async function handleLogout() {
try {
await authStore.logoutUser(router, locale);
} catch (error) {
push.error(`${t("navbarComponent.errorLogout")} - ${error}`);
}
}
return {
authStore,
handleLogout,
};
},
};
// Composables
const authStore = useAuthStore();
</script>

View File

@@ -1,117 +1,106 @@
<template>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<!-- Navbar brand + search in the left -->
<router-link :to="{ name: 'home' }" class="navbar-brand d-flex align-items-center">
<img src="/logo/logo.svg" alt="Logo" width="24" height="24" class="me-2 rounded" />
Endurain
</router-link>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<!-- Navbar brand + search in the left -->
<router-link :to="{ name: 'home' }" class="navbar-brand d-flex align-items-center">
<img src="/logo/logo.svg" alt="Logo" width="24" height="24" class="me-2 rounded" />
Endurain
</router-link>
<div class="d-none d-lg-flex w-100 justify-content-between">
<div class="navbar-nav me-auto" v-if="authStore.isAuthenticated">
<NavbarPipeComponent />
<div class="d-none d-lg-flex w-100 justify-content-between">
<div class="navbar-nav me-auto" v-if="authStore.isAuthenticated">
<NavbarPipeComponent />
<!-- Search -->
<router-link :to="{ name: 'search' }" class="nav-link link-body-emphasis">
<font-awesome-icon :icon="['fas', 'magnifying-glass']" />
<span class="ms-1">
{{ $t('navbarComponent.search') }}
</span>
</router-link>
</div>
<!-- Search -->
<router-link :to="{ name: 'search' }" class="nav-link link-body-emphasis">
<font-awesome-icon :icon="['fas', 'magnifying-glass']" />
<span class="ms-1">
{{ $t('navbarComponent.search') }}
</span>
</router-link>
</div>
<!-- Navigation middle -->
<div class="navbar-nav mx-auto" v-if="authStore.isAuthenticated">
<!-- if is logged in show activities button -->
<router-link :to="{ name: 'activities' }" class="nav-link link-body-emphasis">
<font-awesome-icon :icon="['fas', 'fa-person-running']" />
<span class="ms-1">
{{ $t('navbarComponent.activities') }}
</span>
</router-link>
<!-- if is logged in show gears button -->
<router-link :to="{ name: 'gears' }" class="nav-link link-body-emphasis">
<font-awesome-icon :icon="['fas', 'fa-bicycle']" />
<span class="ms-1">
{{ $t('navbarComponent.gear') }}
</span>
</router-link>
<!-- if is logged in show health button -->
<router-link :to="{ name: 'health' }" class="nav-link link-body-emphasis">
<font-awesome-icon :icon="['fas', 'fa-heart']" />
<span class="ms-1">
{{ $t('navbarComponent.health') }}
</span>
</router-link>
<!-- Summary link -->
<router-link :to="{ name: 'summary' }" class="nav-link link-body-emphasis">
<font-awesome-icon :icon="['fas', 'fa-calendar-alt']" />
<span class="ms-1">
{{ $t('navbarComponent.summary') }}
</span>
</router-link>
</div>
<!-- Navigation middle -->
<div class="navbar-nav mx-auto" v-if="authStore.isAuthenticated">
<!-- if is logged in show activities button -->
<router-link :to="{ name: 'activities' }" class="nav-link link-body-emphasis">
<font-awesome-icon :icon="['fas', 'fa-person-running']" />
<span class="ms-1">
{{ $t('navbarComponent.activities') }}
</span>
</router-link>
<!-- if is logged in show gears button -->
<router-link :to="{ name: 'gears' }" class="nav-link link-body-emphasis">
<font-awesome-icon :icon="['fas', 'fa-bicycle']" />
<span class="ms-1">
{{ $t('navbarComponent.gear') }}
</span>
</router-link>
<!-- if is logged in show health button -->
<router-link :to="{ name: 'health' }" class="nav-link link-body-emphasis">
<font-awesome-icon :icon="['fas', 'fa-heart']" />
<span class="ms-1">
{{ $t('navbarComponent.health') }}
</span>
</router-link>
<!-- Summary link -->
<router-link :to="{ name: 'summary' }" class="nav-link link-body-emphasis">
<font-awesome-icon :icon="['fas', 'fa-calendar-alt']" />
<span class="ms-1">
{{ $t('navbarComponent.summary') }}
</span>
</router-link>
</div>
<!-- Navigation end -->
<div class="navbar-nav ms-auto" v-if="authStore.isAuthenticated">
<NavbarLanguageSwitcherComponent />
<!-- Navigation end -->
<div class="navbar-nav ms-auto" v-if="authStore.isAuthenticated">
<NavbarLanguageSwitcherComponent />
<NavbarThemeSwitcherComponent />
<NavbarThemeSwitcherComponent />
<!-- Settings button -->
<router-link :to="{ name: 'settings' }" class="nav-link link-body-emphasis">
<font-awesome-icon :icon="['fas', 'fa-gear']" />
<span class="ms-1 d-lg-none">{{ $t('navbarComponent.settings') }}</span>
</router-link>
<!-- Settings button -->
<router-link :to="{ name: 'settings' }" class="nav-link link-body-emphasis">
<font-awesome-icon :icon="['fas', 'fa-gear']" />
<span class="ms-1 d-lg-none">{{ $t('navbarComponent.settings') }}</span>
</router-link>
<li class="nav-item dropdown">
<a
class="nav-link dropdown-toggle"
href="#"
role="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<UserAvatarComponent :user="authStore.user" :width="24" :height="24" :alignTop="2" />
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<router-link
:to="{ name: 'user', params: { id: authStore.user.id } }"
class="dropdown-item"
>
<font-awesome-icon :icon="['fas', 'circle-user']" />
<span class="ms-2">{{ $t('navbarComponent.profile') }}</span>
</router-link>
</li>
<li>
<hr class="dropdown-divider" />
</li>
<li>
<a class="dropdown-item" href="#" @click="handleLogout">
<font-awesome-icon :icon="['fas', 'fa-sign-out-alt']" />
<span class="ms-2">{{ $t('navbarComponent.logout') }}</span>
</a>
</li>
</ul>
</li>
</div>
</div>
<div class="navbar-nav" v-if="!authStore.isAuthenticated">
<router-link
:to="{ name: 'login' }"
class="nav-link link-body-emphasis d-flex align-items-center"
>
<font-awesome-icon :icon="['fas', 'fa-sign-in-alt']" />
<span class="ms-1">{{ $t('navbarComponent.login') }}</span>
</router-link>
</div>
</div>
</nav>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
aria-expanded="false">
<UserAvatarComponent :user="authStore.user" :width="24" :height="24" :alignTop="2" />
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li v-if="authStore.isAuthenticated && authStore.user.id">
<router-link :to="{ name: 'user', params: { id: authStore.user.id } }"
class="dropdown-item">
<font-awesome-icon :icon="['fas', 'circle-user']" />
<span class="ms-2">{{ $t('navbarComponent.profile') }}</span>
</router-link>
</li>
<li>
<hr class="dropdown-divider" />
</li>
<li>
<a class="dropdown-item" href="#" @click="handleLogout">
<font-awesome-icon :icon="['fas', 'fa-sign-out-alt']" />
<span class="ms-2">{{ $t('navbarComponent.logout') }}</span>
</a>
</li>
</ul>
</li>
</div>
</div>
<div class="navbar-nav" v-if="!authStore.isAuthenticated">
<router-link :to="{ name: 'login' }" class="nav-link link-body-emphasis d-flex align-items-center">
<font-awesome-icon :icon="['fas', 'fa-sign-in-alt']" />
<span class="ms-1">{{ $t('navbarComponent.login') }}</span>
</router-link>
</div>
</div>
</nav>
</template>
<script>
import { ref } from 'vue'
<script setup>
import { useRouter } from 'vue-router'
// Importing the i18n
import { useI18n } from 'vue-i18n'
@@ -125,30 +114,17 @@ import NavbarPipeComponent from '@/components/Navbar/NavbarPipeComponent.vue'
import NavbarThemeSwitcherComponent from '@/components/Navbar/NavbarThemeSwitcherComponent.vue'
import NavbarLanguageSwitcherComponent from '@/components/Navbar/NavbarLanguageSwitcherComponent.vue'
export default {
components: {
UserAvatarComponent,
NavbarPipeComponent,
NavbarThemeSwitcherComponent,
NavbarLanguageSwitcherComponent
},
setup() {
const router = useRouter()
const authStore = useAuthStore()
const { locale, t } = useI18n()
// Composables
const router = useRouter()
const authStore = useAuthStore()
const { locale, t } = useI18n()
async function handleLogout() {
try {
await authStore.logoutUser(router, locale)
} catch (error) {
push.error(`${t('navbarComponent.errorLogout')} - ${error}`)
}
}
return {
authStore,
handleLogout
}
}
// Methods
async function handleLogout() {
try {
await authStore.logoutUser(router, locale)
} catch (error) {
push.error(`${t('navbarComponent.errorLogout')} - ${error}`)
}
}
</script>

View File

@@ -1,5 +1,6 @@
{
"sessionExpired": "User session expired",
"logoutSuccess": "You have been successfully logged out",
"error401": "Invalid username or password",
"error403": "You do not have permission to access this resource",
"error500": "It was not possible to connect to the server. Please try again later",

View File

@@ -10,12 +10,9 @@
"labelSelectWeek": "Week",
"labelSelectMonth": "Month",
"labelSelectYear": "Year",
"labelSelectLifetime": "Lifetime",
"labelSelectPeriod": "Period",
"buttonPreviousPeriod": "Previous period",
"buttonNextPeriod": "Next period",
"loadingSummary": "Loading summary...",
"loadingActivities": "Loading activities...",
"buttonPreviousPeriod": "Previous",
"buttonNextPeriod": "Next",
"headerSummaryFor": "Summary for {period}",
"headerBreakdown": "Breakdown",
"headerActivitiesInPeriod": "Activities in period",
@@ -24,7 +21,6 @@
"errorLoadingSummaryLoad": "Error loading summary on page load",
"errorFetchingActivities": "Error fetching activities",
"noDataForPeriod": "No data for this period.",
"noActivitiesFound": "No activities found for the selected period.",
"colDay": "Day",
"colWeekNum": "Week #",
"colMonth": "Month",
@@ -38,14 +34,10 @@
"metricTotalElevation": "Total elevation",
"metricTotalCalories": "Total calories",
"metricTotalActivities": "Total activities",
"invalidYearSelectedOrInvalid": "Year is not selected or invalid, not fetching summary",
"invalidDateSelected": "Invalid date selected",
"invalidYearSelected": "Invalid year selected",
"noDateSelected": "No date selected",
"headerTypeBreakdown": "Breakdown by type",
"colActivityType": "Type",
"headerYear": "Year {year}",
"headerWeekStarting": "Week of {date}",
"colYear": "Year",
"invalidInputFormat": "Invalid input format. Please use YYYY-MM."
"colYear": "Year"
}

View File

@@ -11,7 +11,7 @@ export const summaryService = {
*/
getSummary(userId, viewType, params = {}, activityType = null) {
// Added activityType parameter
const url = `summaries/${viewType}`;
const url = `activities/summaries/${viewType}`;
const queryParams = new URLSearchParams(params); // Create params object
// Add activity type filter if provided

View File

@@ -41,7 +41,7 @@ export const useAuthStore = defineStore('auth', {
// Check if router is not null before trying to navigate
if (router) {
try {
await router.push('/login');
await router.push('/login?logoutSuccess=true');
} catch (navigationError) {
console.error('Navigation error:', navigationError);
}
@@ -59,6 +59,7 @@ export const useAuthStore = defineStore('auth', {
this.setLocale(this.user.preferred_language, locale);
},
clearUser(locale) {
this.isAuthenticated = false;
this.user = {
id: null,
name: '',
@@ -77,7 +78,6 @@ export const useAuthStore = defineStore('auth', {
is_strava_linked: null,
is_garminconnect_linked: null,
};
this.isAuthenticated = false;
if (this.user_websocket && this.user_websocket.readyState === WebSocket.OPEN) {
this.user_websocket.close();
}

View File

@@ -134,7 +134,7 @@ export function formatAverageSpeedImperial(speed) {
* @returns {boolean} True if the type of the activity is swimming (Indoor or Outdoor), false otherwise.
*/
export function activityTypeIsSwimming(activity) {
return activity.activity_type === 8 || activity.activity_type === 9;
return activity.activity_type === 8 || activity.activity_type === 9;
}
/**
@@ -152,9 +152,9 @@ export function formatPace(activity, unitSystem, lap = null, units = true, isRes
if (lap) {
pace = lap.enhanced_avg_pace;
}
if (isRest) {
return i18n.global.t("generalItems.labelRest");
}
if (isRest) {
return i18n.global.t("generalItems.labelRest");
}
if (
activityTypeIsSwimming(activity) ||
activity.activity_type === 13
@@ -309,18 +309,18 @@ export function formatPower(power) {
* @returns {string} The formatted elevation string with the appropriate unit, or a "not applicable" label if the input is null or undefined.
*/
export function formatElevation(meters, unitSystem, units = true) {
if (meters === null || meters === undefined) {
return i18n.global.t("generalItems.labelNoData");
}
const numericValue = Number(unitSystem) === 1 ? parseFloat(meters) : parseFloat(metersToFeet(meters));
const formattedValue = numericValue.toLocaleString(undefined, { maximumFractionDigits: 0 });
if (meters === null || meters === undefined) {
return i18n.global.t("generalItems.labelNoData");
}
const numericValue = Number(unitSystem) === 1 ? parseFloat(meters) : parseFloat(metersToFeet(meters));
const formattedValue = numericValue.toLocaleString(undefined, { maximumFractionDigits: 0 });
if (!units) {
return formattedValue;
}
if (!units) {
return formattedValue;
}
const unitLabel = Number(unitSystem) === 1 ? i18n.global.t("generalItems.unitsM") : i18n.global.t("generalItems.unitsFeet");
return `${formattedValue} ${unitLabel}`;
const unitLabel = Number(unitSystem) === 1 ? i18n.global.t("generalItems.unitsM") : i18n.global.t("generalItems.unitsFeet");
return `${formattedValue} ${unitLabel}`;
}
/**
@@ -330,12 +330,12 @@ export function formatElevation(meters, unitSystem, units = true) {
* @returns {string} A formatted string representing the calorie value with units, or a "not applicable" label if the input is null or undefined.
*/
export function formatCalories(calories) {
if (calories === null || calories === undefined) {
return i18n.global.t("generalItems.labelNoData");
}
const numericValue = parseFloat(calories);
const formattedValue = numericValue.toLocaleString(undefined, { maximumFractionDigits: 0 });
return `${formattedValue} ${i18n.global.t("generalItems.unitsCalories")}`;
if (calories === null || calories === undefined) {
return i18n.global.t("generalItems.labelNoData");
}
const numericValue = parseFloat(calories);
const formattedValue = numericValue.toLocaleString(undefined, { maximumFractionDigits: 0 });
return `${formattedValue} ${i18n.global.t("generalItems.unitsCalories")}`;
}
/**
@@ -364,7 +364,7 @@ export function getIcon(typeId) {
16: ["fas", "person-skiing-nordic"],
17: ["fas", "person-snowboarding"],
18: ["fas", "repeat"],
21: ["fas", "table-tennis-paddle-ball"], // Racquet sports
21: ["fas", "table-tennis-paddle-ball"],
22: ["fas", "table-tennis-paddle-ball"],
23: ["fas", "table-tennis-paddle-ball"],
24: ["fas", "table-tennis-paddle-ball"],
@@ -404,9 +404,9 @@ export function formatLocation(activity) {
} else {
locationParts.push(country);
}
}
}
return locationParts.join(""); // Join without extra spaces, comma is handled above
return locationParts.join(""); // Join without extra spaces, comma is handled above
}
/**
@@ -417,14 +417,14 @@ return locationParts.join(""); // Join without extra spaces, comma is handled ab
* @returns {string} The formatted distance string with appropriate units or a "No Data" label.
*/
export function formatRawDistance(meters, unitSystem) {
if (meters === null || meters === undefined || meters < 0) {
return i18n.global.t("generalItems.labelNoData");
}
const numericValue = Number(unitSystem) === 1 ? parseFloat(metersToKm(meters)) : parseFloat(metersToMiles(meters));
// Assuming metersToKm and metersToMiles return numbers or strings that can be parsed to numbers
// Use toLocaleString for formatting, allow for some decimal places for precision if needed
const formattedValue = numericValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
if (meters === null || meters === undefined || meters < 0) {
return i18n.global.t("generalItems.labelNoData");
}
const numericValue = Number(unitSystem) === 1 ? parseFloat(metersToKm(meters)) : parseFloat(metersToMiles(meters));
// Assuming metersToKm and metersToMiles return numbers or strings that can be parsed to numbers
// Use toLocaleString for formatting, allow for some decimal places for precision if needed
const formattedValue = numericValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const unitLabel = Number(unitSystem) === 1 ? i18n.global.t("generalItems.unitsKm") : i18n.global.t("generalItems.unitsMiles");
return `${formattedValue} ${unitLabel}`;
const unitLabel = Number(unitSystem) === 1 ? i18n.global.t("generalItems.unitsKm") : i18n.global.t("generalItems.unitsMiles");
return `${formattedValue} ${unitLabel}`;
}

View File

@@ -121,80 +121,30 @@ export function getMonthEndDate(jsDate) {
return DateTime.fromJSDate(jsDate, { zone: 'utc' }).startOf('month').plus({ months: 1 }).toJSDate();
}
/**
* Formats a JavaScript Date object into YYYY-MM-DD string (UTC).
* Handles non-Date inputs by attempting to parse them.
* @param {Date | string | number} jsDateInput - The input JavaScript Date object or a value parseable into a Date.
* @returns {string} - The formatted date string (YYYY-MM-DD), or an empty string if input is invalid.
* Formats a Date object into a string with the format "YYYY-MM".
*
* @param {Date} date - The date to format.
* @returns {string} The formatted date string in "YYYY-MM" format.
*/
export function formatDateToISOString(jsDateInput) {
let dt;
if (jsDateInput instanceof Date && !isNaN(jsDateInput.getTime())) {
dt = DateTime.fromJSDate(jsDateInput, { zone: 'utc' });
} else {
// Attempt to parse if not a valid Date object (e.g., could be a date string)
const parsedDate = new Date(jsDateInput);
if (isNaN(parsedDate.getTime())) {
console.error("formatDateToISOString received invalid date input:", jsDateInput);
return "";
}
dt = DateTime.fromJSDate(parsedDate, { zone: 'utc' });
}
if (dt.isValid) {
return dt.toISODate();
}
console.error("formatDateToISOString failed to create valid DateTime:", jsDateInput);
return "";
export function formatDateToMonthString(date) {
let year = date.getFullYear();
let month = String(date.getMonth() + 1).padStart(2, '0');
return `${year}-${month}`;
}
/**
* Formats a JavaScript Date object into a "YYYY-MM" string (UTC).
* Handles non-Date inputs by attempting to parse them.
* @param {Date | string | number} jsDateInput - The input JavaScript Date object or a value parseable into a Date.
* @returns {string} - The formatted month string (YYYY-MM), or an empty string if input is invalid.
*/
export function formatDateToMonthString(jsDateInput) {
let dt;
if (jsDateInput instanceof Date && !isNaN(jsDateInput.getTime())) {
dt = DateTime.fromJSDate(jsDateInput, { zone: 'utc' });
} else {
const parsedDate = new Date(jsDateInput);
if (isNaN(parsedDate.getTime())) {
console.error("formatDateToMonthString received invalid date input:", jsDateInput);
return "";
}
dt = DateTime.fromJSDate(parsedDate, { zone: 'utc' });
}
if (dt.isValid) {
return dt.toFormat('yyyy-MM');
}
console.error("formatDateToMonthString failed to create valid DateTime:", jsDateInput);
return "";
}
/**
* Formats a Date object into YYYY-MM-DD string.
* @param {Date} date - The input date.
* @returns {string} - The formatted date string.
* Formats a Date object into an ISO date string (YYYY-MM-DD).
*
* @param {Date} date - The date to format.
* @returns {string} The formatted date string in ISO format.
*/
export function formatDateISO(date) {
// Ensure input is a Date object
if (!(date instanceof Date)) {
console.error("formatDateISO received non-Date object:", date);
// Attempt to parse if it's a valid date string, otherwise return empty or throw
try {
date = new Date(date);
if (isNaN(date.getTime())) throw new Error("Invalid date input");
} catch (e) {
return ""; // Or handle error appropriately
}
}
// Check for invalid date after potential parsing
if (isNaN(date.getTime())) {
console.error("formatDateISO received invalid Date object:", date);
return "";
}
return date.toISOString().slice(0, 10);
let year = date.getFullYear();
let month = String(date.getMonth() + 1).padStart(2, '0');
let day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}

View File

@@ -29,12 +29,12 @@
<div class="row mb-3">
<div class="col">
<a class="w-100 btn btn-primary shadow-sm" href="#" role="button" data-bs-toggle="modal" data-bs-target="#addActivityModal">
{{ $t("homeView.buttonAddActivity") }}
</a>
</div>
<div class="col-auto" v-if="authStore.user.is_strava_linked == 1 || authStore.user.is_garminconnect_linked == 1">
<a class="w-100 btn btn-primary shadow-sm" href="#" role="button" @click="refreshActivities">
<font-awesome-icon :icon="['fas', 'arrows-rotate']" />
{{ $t("homeView.buttonAddActivity") }}
</a>
</div>
<div class="col-auto" v-if="authStore.user.is_strava_linked == 1 || authStore.user.is_garminconnect_linked == 1">
<a class="w-100 btn btn-primary shadow-sm" href="#" role="button" @click="refreshActivities">
<font-awesome-icon :icon="['fas', 'arrows-rotate']" />
</a>
</div>
</div>

View File

@@ -112,6 +112,10 @@ export default {
if (route.query.sessionExpired === "true") {
push.warning(t("loginView.sessionExpired"));
}
// Check if the logout was successful
if (route.query.logoutSuccess === "true") {
push.success(t("loginView.logoutSuccess"));
}
// Check if the public activity was not found
if (route.query.errorPublicActivityNotFound === "true") {
push.error(t("loginView.errorPublicActivityNotFound"));

View File

@@ -30,7 +30,7 @@
<span class="ms-1">{{ $t("navbarComponent.settings") }}</span>
</router-link>
</li>
<li class="nav-item">
<li class="nav-item" v-if="authStore.isAuthenticated && authStore.user.id">
<router-link :to="{ name: 'user', params: { id: authStore.user.id } }" class="nav-link link-body-emphasis w-100 py-3 fs-5">
<UserAvatarComponent :user="authStore.user" :width=24 :height=24 :alignTop=2 />
<span class="ms-2">{{ $t("navbarComponent.profile") }}</span>
@@ -39,7 +39,7 @@
</ul>
<ul class="navbar-nav bg-body-tertiary rounded shadow-sm mt-3">
<li>
<a class="nav-link link-body-emphasis w-100 py-3 fs-5" href="#">
<a class="nav-link link-body-emphasis w-100 py-3 fs-5" href="#" @click="handleLogout">
<font-awesome-icon :icon="['fas', 'fa-sign-out-alt']" />
<span class="ms-2">{{ $t("navbarComponent.logout") }}</span>
</a>
@@ -52,24 +52,23 @@
</div>
</template>
<script>
// import the stores
<script setup>
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useAuthStore } from "@/stores/authStore";
// Import the components
import { push } from 'notivue'
import FooterComponent from "@/components/FooterComponent.vue";
import UserAvatarComponent from "@/components/Users/UserAvatarComponent.vue";
export default {
components: {
UserAvatarComponent,
FooterComponent,
},
setup() {
const authStore = useAuthStore();
const router = useRouter()
const authStore = useAuthStore();
const { locale, t } = useI18n()
return {
authStore,
};
},
};
async function handleLogout() {
try {
await authStore.logoutUser(router, locale)
} catch (error) {
push.error(`${t('navbarComponent.errorLogout')} - ${error}`)
}
}
</script>

View File

@@ -32,7 +32,7 @@
<div class="col-12 mt-3 d-flex justify-content-end gap-3" v-if="selectedViewType !== 'lifetime'">
<button
class="btn btn-primary me-1"
:disabled="isAnyLoading || (selectedViewType === 'year' && selectedYear === 1900)"
:disabled="isAnyLoading || (selectedViewType === 'year' && selectedYear === 1900) || (selectedViewType === 'month' && selectedMonth === '1900-01') || (selectedViewType === 'week' && selectedDate === '1900-01-01')"
@click="navigatePeriod(-1)"
>
<span v-if="isAnyLoading" class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
@@ -40,7 +40,7 @@
</button>
<button
class="btn btn-primary"
:disabled="isAnyLoading || (selectedViewType === 'year' && selectedYear === todayYear)"
:disabled="isAnyLoading || (selectedViewType === 'year' && selectedYear === todayYear) || (selectedViewType === 'month' && selectedMonth === todayMonth) || (selectedViewType === 'week' && selectedDate === todayWeek)"
@click="navigatePeriod(1)"
>
<span v-if="isAnyLoading" class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
@@ -180,9 +180,11 @@ const selectedActivityType = ref("");
const activityTypes = ref([]);
const initialDate = new Date();
const selectedDate = ref(formatDateISO(getWeekStartDate(initialDate)));
const selectedYear = ref(initialDate.getFullYear());
const selectedMonth = ref(formatDateToMonthString(initialDate));
const todayWeek = selectedDate.value;
const todayMonth = formatDateToMonthString(initialDate);
const todayYear = initialDate.getFullYear();
const selectedYear = ref(todayYear);
const selectedMonth = ref(todayMonth);
// Data State
const summaryData = ref(null);
@@ -274,7 +276,7 @@ function setPageNumber(page) {
function updateViewType() {
if (selectedViewType.value !== "lifetime") {
if (selectedViewType.value === "month") {
selectedMonth.value = formatDateToMonthString(selectedDate.value);
selectedMonth.value = formatDateToMonthString(new Date(selectedDate.value));
} else if (selectedViewType.value === "year") {
console.log("Selected date:", selectedDate.value);
selectedYear.value = new Date(selectedDate.value).getUTCFullYear();
@@ -403,6 +405,7 @@ const performYearTriggerDataFetch = debounce(async () => {
}, 500);
async function performMonthTriggerDataFetch() {
console.log("selected month:", selectedMonth.value);
if (selectedViewType.value === "month") {
triggerDataFetch();
}
@@ -504,7 +507,7 @@ function navigatePeriod(direction) {
selectedDate.value = formatDateISO(date);
}
if (selectedViewType.value === 'month') {
selectedMonth.value = formatDateToMonthString(selectedDate.value);
selectedMonth.value = formatDateToMonthString(new Date(selectedDate.value));
}
} catch (error) {
push.error(`${t("summaryView.labelError")} - ${error}`);