Skip to content

Changelog

Links and up-to-date content

This page serves content pulled from the repository as-is; links may be broken.
Links work when viewing in the repository. For accurate content, see https://github.com/onprem-hipster-timer/backend.

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

No unreleased changes.


v2026.06.15-552ec57 - 2026-06-15

Added

  • Per-request timezone for Todo responses: The Todo endpoints (POST/GET/PATCH /v1/todos, GET /v1/todos/{id}) gained an optional timezone query parameter (e.g. Asia/Seoul, +09:00, UTC), matching the existing Schedule/Timer behavior. TodoRead.to_timezone converts the UTC-naive deadline/created_at (and nested schedules) to the requested timezone; when omitted, values are returned in UTC.

Fixed

  • Holiday sync timezone crash: The holiday sync batch failed when updating holiday_hashes.updated_at, because a timezone-aware datetime.now(timezone.utc) was assigned to a TIMESTAMP WITHOUT TIME ZONE (naive) column, raising TypeError: can't subtract offset-naive and offset-aware datetimes / asyncpg.exceptions.DataError during autoflush. save_holidays now stores the hash timestamp as UTC-naive. Holiday dates ingested from the KASI (천문연구원) API — whose locdate values carry no timezone — are now explicitly assigned KST (Asia/Seoul) at the receiving DTO (HolidayItem.to_utc_naive_range) before being normalized to UTC-naive. (#41)
  • Holiday insert NotNullViolationError on created_at/updated_at: HolidayModel lost its TimestampMixin during an earlier model-relocation refactor, so inserts into holidays omitted created_at/updated_at. Because the holidays table is created via create_all (not Alembic) and existing deployments already had those columns as NOT NULL from when the mixin was present, new rows were rejected with asyncpg.exceptions.NotNullViolationError — surfaced once the #41 fix let the sync proceed to the holiday insert. TimestampMixin was restored on HolidayModel, so timestamps are populated as UTC-naive. (#41)
  • Todo deadline timezone (same bug class as #41): TodoCreate/TodoUpdate.deadline lacked the UTC-normalization validator that the Schedule/Timer DTOs already had, so a client-supplied timezone-aware deadline was written directly to the naive todo.deadline column — the same asyncpg.DataError class as #41 on PostgreSQL. Both DTOs now run ensure_utc_naive on deadline, converting aware datetimes to UTC-naive before storage. A timezone-handling guide was added at docs/development/timezone.ko.md. (#41)

v2026.06.15-53559c3 - 2026-06-15

Added

  • Friend display info: Friend list and pending-request responses now carry the counterparty's display info so a UI can show who a request/friend is. FriendRead gained display_name/avatar_url, and PendingRequestRead gained requester_display_name/requester_avatar_url. A new GET /v1/users/me returns the caller's profile and a shareable friend_code. Display info is sourced only from standard OIDC claims (namedisplay_name, pictureavatar_url) of each user's own validated access token via just-in-time provisioning into a new user_profile table — no UserInfo/Admin-API calls, so the backend stays provider-agnostic. JIT sync runs on every authenticated endpoint, so any active user is addressable even if they never open the social UI. The WebSocket timer.friend_activity payload also gained display_name. (#20)
  • Friend requests by code or email: POST /v1/friends/requests takes exactly one of { "friend_code": "<value>" } or { "email": "<value>" } (sending both fields, neither field, null, or a malformed email returns 422):
  • friend code: resolved against a stored CSPRNG-generated URL-safe token (secrets.token_urlsafe(32)) that is not derived from the OIDC sub. An unknown code returns 404.
  • email (format-validated): matched against the stored normalized verified_email, populated only when the token's email_verified is true. To prevent account enumeration the endpoint always returns 202 {"ok": true} regardless of whether the email matched, was your own, was a duplicate, or was blocked. Rate-limited tighter than other writes (20/min). Verified email is stored only for friend-request matching and is not returned through search, lists, or API responses. There is deliberately no user-search/directory endpoint. (#20)

Changed

  • Friend request body: POST /v1/friends/requests now accepts an explicit { "email" } or { "friend_code" } (exactly one, validated) instead of { "addressee_id": "<sub>" } — an OIDC sub could never be obtained by a client. (#20)

Security

  • Holiday API over HTTPS: The default HOLIDAY_API_BASE_URL was changed from http://apis.data.go.kr/... to https://apis.data.go.kr/..., so Korea Astronomy and Space Science Institute (Public Data Portal) holiday lookups — which carry the HOLIDAY_API_SERVICE_KEY — are no longer transmitted in cleartext. All released versions up to and including v2026.06.12-cfa3200 ship the http:// default. Operators running those tags should inject the HTTPS address directly through the HOLIDAY_API_BASE_URL environment variable until they upgrade to a build containing this change:
HOLIDAY_API_BASE_URL=https://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService

v2026.06.12-cfa3200 - 2026-06-12

Changed

  • authlib.josejoserfc migration: OIDC JWT verification in app/core/auth.py was migrated off the deprecated authlib.jose module (deprecated since Authlib 1.7.0, slated for removal in Authlib 2.0.0) to the standalone joserfc package, which Authlib already pulls in transitively. JWKS loading (KeySet.import_key_set), decoding + claim validation (jwt.decode + JWTClaimsRegistry), and error handling (joserfc.errors.JoseError) were ported with no behavioral change to token verification. Signature verification is now restricted to asymmetric algorithms (RS256/384/512, ES256/384/512), preventing the alg confusion / none attacks that Authlib's unrestricted default allowed. authlib was dropped from requirements.in (and the lockfiles) in favor of a direct joserfc dependency. (#34, #37)

v2026.06.09-30c31db - 2026-06-09

Fixed

  • Cross-platform lockfile (Windows installability): uvloop (a uvicorn[standard] transitive dependency with no Windows wheel) was pinned without an environment marker, so pip install -r requirements.txt failed on Windows with uvloop does not support Windows. uvloop is now declared directly in requirements.in with a ; sys_platform != 'win32' marker, which pip-compile preserves in the lockfile and Dependabot retains across regenerations (it reads but never rewrites requirements.in). Linux/production installs are unchanged. Verified: fresh Windows venv install succeeds (uvloop skipped); Linux test suite still passes (824 tests).

v2026.06.09-91639bf - 2026-06-09

Changed

  • Strict Content-Type checking for JSON requests: As part of the FastAPI dependency upgrade (0.132+, via #29), requests carrying a JSON body must include a Content-Type: application/json header. A missing header now returns 422 Unprocessable Entity, and an incorrect media type returns 415 Unsupported Media Type; bodyless requests (GET, DELETE) are unaffected. The previous lenient behavior can be restored with FastAPI(strict_content_type=False). Documented in the API Overview (Korean and English).

v2026.03.31-41a4f1d - 2026-03-31

Added

  • WebSocket close code specification: Documented close codes (1000, 1008, 1011, 4029) in both Korean and English WebSocket API docs with reconnection guidance per code. Updated reconnection example to block retry on 1008 (auth failure). (#15, #18)

v2026.03.09-2c3b92f - 2026-03-09

Added

  • UpdateMixin.apply_update for ORM models: Added a generic partial-update method to UpdateMixin (inherited by all models via UUIDBase). Accepts a dict (from model_dump()) and applies values to model columns with built-in safeguards.
  • Protected fields: Primary keys (auto-detected via SQLAlchemy mapper), TimestampMixin fields (created_at, updated_at — derived from TimestampMixin.__annotations__), and owner_id are always excluded from updates.
  • Nullable safety: Setting None on a non-nullable column is silently skipped; nullable columns accept None normally. Nullable status is correctly resolved via ColumnProperty.columns[0].nullable.
  • Custom exclusion: Callers can pass exclude=["field"] to protect additional fields per use case.

Changed

  • Centralized Visibility API controller: Extracted visibility management from each domain (Schedule, Timer, Todo, Meeting) into a dedicated /v1/visibility/{resource_type}/{resource_id} endpoint with PUT/GET/DELETE operations.
  • Breaking change: visibility field removed from all Create/Update DTOs. Clients must set visibility via a separate PUT /v1/visibility/{type}/{id} call after resource creation.
  • MISSING sentinel for Update DTOs: Replaced Optional[T] = None + model_dump(exclude_unset=True) pattern with pydantic.experimental.missing_sentinel.MISSING across all Update DTOs (TodoUpdate, ScheduleUpdate, TagGroupUpdate, TagUpdate, TimerUpdate, MeetingUpdate).
  • Fields now use T | None = MISSING for explicit 3-way semantics: MISSING (not sent, keep current), None (clear value), value (set value).
  • Removed all model_dump(exclude_unset=True) calls from CRUD and Service layers; model_dump() now automatically excludes MISSING fields.
  • Fixed validate_time_order to use isinstance checks, preventing TypeError when cross-field validators receive MISSING sentinel values.

v2026.03.05-6194559 - 2026-03-05

Added

  • Automated versioning and release pipeline: Merging to main now automatically generates a CalVer tag (v{YYYY}.{MM}.{DD}-{SHORT_HASH}), builds and pushes the Docker image, updates this CHANGELOG, and creates a GitHub Release.
  • License and liability notice in /health: The health check response now includes AGPL-3.0 license notice and infrastructure liability disclaimer.

Changed

  • Production information hardening: /health no longer exposes version or environment fields in production environments.

v2026.03.04-d027c48 - 2026-03-04

Changed

  • Meeting result availability grid structure (d027c48): Changed availability_grid response from a nested map (Map<date, Map<time, count>>) to a typed array grouped by date (List[AvailabilityDateGroup]). Improves type safety and eliminates additionalProperties in OpenAPI schema.
  • Breaking change: Clients consuming GET /v1/meetings/{id}/result must update their parsing logic.
  • Before: { "2024-02-01": { "09:00": 1 } }
  • After: [{ "date": "2024-02-01", "slots": [{ "time": "09:00", "count": 1 }] }]

v2026.03.03-5609ccd - 2026-03-03

Removed

  • TagGroup.is_todo_group field (5609ccd): Removed unused boolean flag from TagGroup model, DTOs, and database schema. The field had no business logic enforcing it and was only referenced in a one-time migration script.
  • Breaking change: is_todo_group field is no longer present in TagGroup API responses.

v2026.02.23-e582adc - 2026-02-23

Fixed

  • TimerService elapsed time calculation (e582adc): Fixed incorrect accumulation of elapsed time during pause/resume cycles. Refactored to use a dedicated method and added tests for pause/resume cycles.
  • Upgrade notice: Patches before 2026.02.23-e582adc contain the above bug. Upgrade to 2026.02.23-e582adc or later is recommended.

v2026.01.30 - 2026-01-30

Added

Core Features

  • Schedule Management
  • CRUD operations for schedules
  • RRULE-based recurring schedule support
  • Exception date handling for recurring schedules
  • Virtual instance expansion for date range queries
  • Timezone support (KST, UTC, custom offsets)

  • Timer Sessions

  • Timer creation linked to Schedule or Todo
  • Pause/Resume/Stop state management
  • Elapsed time tracking
  • WebSocket real-time synchronization
  • Pause history tracking

  • Todo Management

  • Hierarchical tree structure (unlimited depth)
  • Circular reference prevention
  • Ancestor inclusion in filtered queries
  • Automatic Schedule creation from deadline
  • Statistics API (count by tag)

  • Tag System

  • TagGroup for logical categorization
  • Custom colors (#RRGGBB format)
  • Tag assignment to Schedule, Timer, Todo
  • Unique tag names within group

  • Holiday Integration

  • Korea Astronomy and Space Science Institute API integration
  • Background synchronization on startup

Social Features

  • Friendship
  • Request/Accept/Reject workflow
  • Bidirectional unique constraint
  • Block functionality
  • Friend list and request list APIs

  • Visibility Control

  • 5-level visibility (PRIVATE, FRIENDS, SELECTED_FRIENDS, ALLOWED_EMAILS, PUBLIC)
  • Resource-specific settings (Schedule, Timer, Todo, Meeting)
  • AllowList for selected friends
  • AllowEmail for email/domain-based access

  • Meeting (Schedule Coordination)

  • Meeting creation with date range and time slots
  • Participant registration via shared link
  • Available time slot selection
  • Common availability query

API

  • REST API with OpenAPI/Swagger documentation
  • GraphQL API with Strawberry (Apollo Sandbox)
  • WebSocket API for real-time timer sync

Authentication & Security

  • OIDC authentication support
  • JWT token validation with JWKS caching
  • Rate limiting (HTTP and WebSocket)
  • Cloudflare and trusted proxy support
  • CORS configuration

Infrastructure

  • SQLite (development) and PostgreSQL (production) support
  • Alembic database migrations
  • Docker and Docker Compose support
  • Multi-platform builds (amd64, arm64)
  • GitHub Actions CI/CD
  • MkDocs documentation site

Database Migrations

Migration Description
ee97307fb363 Initial schema (Schedule, Timer, Todo, Tag)
d8eaba7f881e Add tag_group_id to Schedule
341423c03b1a Make tag_group_id required for Todos
9b9bdc029ff3 Add Todo model, update Schedule model
1f4bc4f1de04 Change Todo parent FK to SET NULL
cf7d6e2ef7a7 Add owner_id to all models
62a5cb5aae21 Add todo_id to Timer, make schedule optional
a1b2c3d4e5f6 Add Friendship and Visibility tables
b2c3d4e5f6a7 Add Friendship bidirectional unique constraint
c3d4e5f6a7b8 Add pause history to Timer
d4e5f6a7b8c9 Change Timer status to UPPERCASE
e5f6a7b8c9d0 Add VisibilityAllowEmail and Meeting tables

Version History Format

Types of Changes

  • Added: New features
  • Changed: Changes in existing functionality
  • Deprecated: Soon-to-be removed features
  • Removed: Removed features
  • Fixed: Bug fixes
  • Security: Security vulnerability fixes