From 612d67ccda6b545b773927c33d90b1c7ea75f525 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20Z=C3=B6ller?=
Date: Wed, 14 Jan 2026 16:53:34 +0100
Subject: [PATCH] Search function, declaudification
---
CLAUDE.md | 1177 -----------------
DARK_MODE_FIXES.md | 366 -----
FEDERATION_TESTING_GUIDE.md | 696 ----------
INDOOR_DETECTION_IMPLEMENTATION.md | 239 ----
MVP_COMPLETE.md | 360 -----
TIMESTAMP_VERIFICATION_REPORT.md | 149 ---
migrate-indoor-flags.sh | 48 -
.../fitpub/controller/ActivityController.java | 85 +-
.../fitpub/controller/TimelineController.java | 59 +-
.../fitpub/repository/ActivityRepository.java | 140 ++
.../fitpub/service/FitFileService.java | 36 +
.../fitpub/service/TimelineService.java | 129 ++
src/main/resources/static/js/timeline.js | 149 ++-
.../templates/activities/detail.html | 12 +-
.../templates/timeline/federated.html | 28 +
.../resources/templates/timeline/public.html | 28 +
.../resources/templates/timeline/user.html | 28 +
17 files changed, 668 insertions(+), 3061 deletions(-)
delete mode 100644 CLAUDE.md
delete mode 100644 DARK_MODE_FIXES.md
delete mode 100644 FEDERATION_TESTING_GUIDE.md
delete mode 100644 INDOOR_DETECTION_IMPLEMENTATION.md
delete mode 100644 MVP_COMPLETE.md
delete mode 100644 TIMESTAMP_VERIFICATION_REPORT.md
delete mode 100644 migrate-indoor-flags.sh
diff --git a/CLAUDE.md b/CLAUDE.md
deleted file mode 100644
index 0e460b3..0000000
--- a/CLAUDE.md
+++ /dev/null
@@ -1,1177 +0,0 @@
-# FitPub - Federated Fitness Tracking Platform
-
-## Project Overview
-
-FitPub is a decentralized fitness tracking application that integrates with the Fediverse through the ActivityPub protocol. It allows users to upload FIT (Flexible and Interoperable Data Transfer) files from their fitness devices and share their activities with followers across the federated social web. The application renders GPS tracks on interactive maps and federates workout data as ActivityPub activities.
-
-## Core Concept
-
-The platform bridges the gap between fitness tracking and social networking by leveraging the open ActivityPub standard. Users can:
-- Upload FIT files from GPS-enabled fitness devices (Garmin, Wahoo, etc.) or GPX files from apps (Strava, Komoot, etc.)
-- View their tracks rendered on interactive maps
-- Share activities with followers on Mastodon, Pleroma, and other Fediverse platforms
-- Follow other athletes and see their public workouts
-- Maintain full data ownership and privacy control
-
-## Technical Architecture
-
-### Technology Stack
-
-**Backend:**
-- Java 17+ (LTS version)
-- Maven for dependency management and build automation
-- Spring Boot 4 for application framework
-- Spring Web MVC for REST API
-- Spring Data JPA for database operations
-- Spring Security for authentication and authorization
-- PostgreSQL for primary data storage
-- PostGIS extension for geospatial data
-
-**Frontend:**
-- Thymeleaf or React for UI rendering
-- Leaflet.js for interactive map display
-- Chart.js for activity statistics visualization
-- Bootstrap or Tailwind CSS for responsive design
-
-**Protocols & Standards:**
-- ActivityPub (W3C Recommendation)
-- WebFinger (RFC 7033) for actor discovery
-- HTTP Signatures for authenticated federation
-- JSON-LD for linked data representation
-- GeoJSON for geographic data interchange
-
-### System Components
-
-#### 1. FIT File Processing Module
-
-**Responsibilities:**
-- Parse binary FIT files uploaded by users
-- Extract GPS coordinates (latitude, longitude, elevation)
-- Parse activity metrics (heart rate, cadence, power, speed, distance)
-- Validate file integrity and format
-- Store parsed data in normalized database schema
-
-**Key Classes:**
-- `FitFileParser`: Core parsing logic using FIT SDK
-- `TrackPointEntity`: Database entity for GPS coordinates
-- `ActivityMetricsEntity`: Database entity for performance data
-- `FitFileValidator`: Validation and sanitization
-
-#### 2. ActivityPub Federation Module
-
-**Responsibilities:**
-- Implement ActivityPub server-to-server (S2S) protocol
-- Implement ActivityPub client-to-server (C2S) protocol
-- Handle incoming activities from other servers
-- Distribute local activities to followers' servers
-- Manage actor profiles and collections
-
-**Key Components:**
-
-**Actor Model:**
-```
-Actor (User Profile)
-├── inbox: OrderedCollection
-├── outbox: OrderedCollection
-├── followers: Collection
-├── following: Collection
-└── publicKey: For HTTP signature verification
-```
-
-**Activity Types:**
-- `Create`: New workout activity posted
-- `Update`: Activity edited (title, description, privacy)
-- `Delete`: Activity removed
-- `Follow`: User follows another athlete
-- `Accept`: Follow request accepted
-- `Announce`: Sharing/boosting another user's activity
-- `Like`: Appreciating someone's workout
-
-**Endpoints:**
-- `/.well-known/webfinger`: User discovery
-- `/users/{username}`: Actor profile (ActivityPub object)
-- `/users/{username}/inbox`: Receive activities (POST)
-- `/users/{username}/outbox`: User's activities (GET)
-- `/users/{username}/followers`: Followers collection
-- `/users/{username}/following`: Following collection
-- `/activities/{id}`: Individual activity objects
-
-#### 3. Geospatial Data Module
-
-**Responsibilities:**
-- Store GPS track data efficiently
-- Generate map-ready GeoJSON from track points
-- Calculate route statistics (distance, elevation gain/loss)
-- Support spatial queries (nearby activities, route matching)
-- Render track simplification for performance
-
-**Data Structure:**
-```
-Activity
-├── id: UUID
-├── user: Actor reference
-├── activityType: (Run, Ride, Hike, Swim, etc.)
-├── startTime: Timestamp
-├── endTime: Timestamp
-├── title: String
-├── description: Text
-├── visibility: (Public, Followers, Private)
-├── track: LineString (PostGIS geometry)
-├── metrics: JSON (distance, duration, avg_speed, etc.)
-└── statistics: JSON (elevation profile, splits, etc.)
-```
-
-**GeoJSON Output Format:**
-```json
-{
- "type": "FeatureCollection",
- "features": [{
- "type": "Feature",
- "geometry": {
- "type": "LineString",
- "coordinates": [[lon, lat, elevation], ...]
- },
- "properties": {
- "time": "ISO-8601 timestamp",
- "heartRate": 145,
- "cadence": 85,
- "speed": 4.5
- }
- }]
-}
-```
-
-#### 4. Web API Module
-
-**REST Endpoints:**
-
-**Activity Management:**
-- `POST /api/activities/upload`: Upload FIT file
-- `GET /api/activities/{id}`: Retrieve activity details
-- `GET /api/activities/{id}/track`: Get GeoJSON track data
-- `PUT /api/activities/{id}`: Update activity metadata
-- `DELETE /api/activities/{id}`: Remove activity
-- `GET /api/activities`: List user's activities (paginated)
-
-**User Management:**
-- `POST /api/users/register`: Create new account
-- `GET /api/users/{username}`: Public profile
-- `GET /api/users/{username}/activities`: User's public activities
-- `PUT /api/users/profile`: Update profile information
-
-**Social Features:**
-- `POST /api/follow/{username}`: Follow a user
-- `DELETE /api/follow/{username}`: Unfollow
-- `GET /api/timeline`: Federated timeline of followed users
-- `POST /api/activities/{id}/like`: Like an activity
-- `POST /api/activities/{id}/comment`: Comment on activity
-
-#### 5. Authentication & Authorization
-
-**Implementation:**
-- JWT tokens for session management
-- OAuth 2.0 for third-party integrations (optional)
-- HTTP Signatures (required for ActivityPub federation)
-- RSA key pairs per user for signature verification
-
-**Security Considerations:**
-- Password hashing with bcrypt
-- Rate limiting on API endpoints
-- CORS configuration for frontend
-- Content-Security-Policy headers
-- Input validation and sanitization
-- Protection against SSRF in federation
-
-## Database Schema
-
-### Core Tables
-
-**users**
-- id (UUID, PK)
-- username (VARCHAR, UNIQUE)
-- email (VARCHAR, UNIQUE)
-- password_hash (VARCHAR)
-- display_name (VARCHAR)
-- bio (TEXT)
-- avatar_url (VARCHAR)
-- public_key (TEXT)
-- private_key (TEXT, encrypted)
-- created_at (TIMESTAMP)
-- updated_at (TIMESTAMP)
-
-**activities**
-- id (UUID, PK)
-- user_id (UUID, FK → users)
-- activity_type (VARCHAR)
-- title (VARCHAR)
-- description (TEXT)
-- started_at (TIMESTAMP)
-- ended_at (TIMESTAMP)
-- visibility (VARCHAR)
-- track (GEOMETRY LineString, PostGIS)
-- total_distance (DECIMAL)
-- total_duration (INTERVAL)
-- elevation_gain (DECIMAL)
-- elevation_loss (DECIMAL)
-- raw_fit_file (BYTEA, optional)
-- created_at (TIMESTAMP)
-- updated_at (TIMESTAMP)
-
-**track_points**
-- id (BIGSERIAL, PK)
-- activity_id (UUID, FK → activities)
-- timestamp (TIMESTAMP)
-- position (GEOMETRY Point, PostGIS)
-- elevation (DECIMAL)
-- heart_rate (INTEGER)
-- cadence (INTEGER)
-- power (INTEGER)
-- speed (DECIMAL)
-- temperature (DECIMAL)
-
-**follows**
-- id (UUID, PK)
-- follower_id (UUID, FK → users)
-- following_id (UUID, FK → users OR remote_actor_id)
-- status (VARCHAR: pending, accepted)
-- created_at (TIMESTAMP)
-
-**remote_actors**
-- id (UUID, PK)
-- actor_uri (VARCHAR, UNIQUE)
-- username (VARCHAR)
-- domain (VARCHAR)
-- inbox_url (VARCHAR)
-- outbox_url (VARCHAR)
-- public_key (TEXT)
-- avatar_url (VARCHAR)
-- display_name (VARCHAR)
-- last_fetched (TIMESTAMP)
-
-**activity_pub_activities**
-- id (UUID, PK)
-- activity_type (VARCHAR)
-- actor_id (UUID)
-- object_id (VARCHAR)
-- target_id (VARCHAR)
-- content (JSONB)
-- created_at (TIMESTAMP)
-
-**likes**
-- id (UUID, PK)
-- activity_id (UUID, FK → activities)
-- user_id (UUID, FK → users OR remote_actor_id)
-- created_at (TIMESTAMP)
-
-**comments**
-- id (UUID, PK)
-- activity_id (UUID, FK → activities)
-- user_id (UUID, FK → users OR remote_actor_id)
-- content (TEXT)
-- created_at (TIMESTAMP)
-- updated_at (TIMESTAMP)
-
-## ActivityPub Integration Details
-
-### Actor Object Example
-
-```json
-{
- "@context": [
- "https://www.w3.org/ns/activitystreams",
- "https://w3id.org/security/v1"
- ],
- "type": "Person",
- "id": "https://fitpub.example/users/runner123",
- "preferredUsername": "runner123",
- "name": "Jane Runner",
- "summary": "Marathon runner | Trail enthusiast | 🏃♀️",
- "inbox": "https://fitpub.example/users/runner123/inbox",
- "outbox": "https://fitpub.example/users/runner123/outbox",
- "followers": "https://fitpub.example/users/runner123/followers",
- "following": "https://fitpub.example/users/runner123/following",
- "publicKey": {
- "id": "https://fitpub.example/users/runner123#main-key",
- "owner": "https://fitpub.example/users/runner123",
- "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n..."
- },
- "icon": {
- "type": "Image",
- "mediaType": "image/jpeg",
- "url": "https://fitpub.example/avatars/runner123.jpg"
- }
-}
-```
-
-### Activity Object Example (Workout Post)
-
-```json
-{
- "@context": "https://www.w3.org/ns/activitystreams",
- "type": "Create",
- "id": "https://fitpub.example/activities/create/12345",
- "actor": "https://fitpub.example/users/runner123",
- "published": "2025-11-27T10:30:00Z",
- "to": ["https://www.w3.org/ns/activitystreams#Public"],
- "cc": ["https://fitpub.example/users/runner123/followers"],
- "object": {
- "type": "Note",
- "id": "https://fitpub.example/workouts/98765",
- "attributedTo": "https://fitpub.example/users/runner123",
- "content": "Morning 10K run through the park! Felt strong today. 💪",
- "published": "2025-11-27T10:30:00Z",
- "attachment": [
- {
- "type": "Document",
- "mediaType": "application/geo+json",
- "name": "GPS Track",
- "url": "https://fitpub.example/workouts/98765/track.geojson"
- },
- {
- "type": "Image",
- "mediaType": "image/png",
- "name": "Route Map",
- "url": "https://fitpub.example/workouts/98765/map.png"
- }
- ],
- "tag": [
- {
- "type": "Hashtag",
- "name": "#running"
- },
- {
- "type": "Hashtag",
- "name": "#10k"
- }
- ],
- "summary": "10.2 km • 48:23 • 4:44/km pace",
- "workoutData": {
- "distance": 10200,
- "duration": "PT48M23S",
- "activityType": "Run",
- "averagePace": "PT4M44S",
- "elevationGain": 127,
- "averageHeartRate": 152
- }
- }
-}
-```
-
-### WebFinger Implementation
-
-**Request:**
-```
-GET /.well-known/webfinger?resource=acct:runner123@fitpub.example
-```
-
-**Response:**
-```json
-{
- "subject": "acct:runner123@fitpub.example",
- "aliases": [
- "https://fitpub.example/users/runner123"
- ],
- "links": [
- {
- "rel": "self",
- "type": "application/activity+json",
- "href": "https://fitpub.example/users/runner123"
- },
- {
- "rel": "http://webfinger.net/rel/profile-page",
- "type": "text/html",
- "href": "https://fitpub.example/@runner123"
- }
- ]
-}
-```
-
-## FIT File Processing Pipeline
-
-### Parse Flow
-
-1. **Upload Validation**
- - Verify file size (max 50MB)
- - Check MIME type
- - Validate FIT file header
-
-2. **Parsing**
- - Use FIT SDK to decode binary format
- - Extract messages: FileId, Record, Lap, Session, Activity
- - Handle corrupted or incomplete files gracefully
-
-3. **Data Extraction**
- - **Record Messages**: GPS coordinates, timestamp, heart rate, cadence, speed, power
- - **Lap Messages**: Split data, lap times, lap distances
- - **Session Messages**: Total distance, total time, average/max values
- - **Activity Messages**: Activity type, timestamp
-
-4. **Data Transformation**
- - Convert semicircles to decimal degrees (lat/lon)
- - Calculate derived metrics (pace, grade-adjusted pace)
- - Detect pauses and stopped periods
- - Smooth GPS noise using Kalman filtering (optional)
-
-5. **Storage**
- - Batch insert track points (optimize for performance)
- - Create PostGIS LineString geometry from points
- - Calculate bounding box for spatial indexing
- - Generate activity statistics
-
-6. **Map Rendering Preparation**
- - Simplify track using Douglas-Peucker algorithm for web display
- - Generate elevation profile data
- - Create thumbnail static map image (optional)
- - Prepare GeoJSON response
-
-## Privacy & Visibility Controls
-
-### Visibility Levels
-
-1. **Public**: Visible to everyone, federated across ActivityPub
-2. **Followers Only**: Visible to approved followers, sent to follower inboxes
-3. **Private**: Visible only to the user, not federated
-
-### Privacy Features
-
-- Ability to hide exact start/end locations (fuzzy start/finish)
-- Option to exclude specific segments from public view
-- Bulk privacy updates for historical activities
-- Export all personal data (GDPR compliance)
-- Delete account with activity cleanup
-
-## Map Rendering
-
-### Frontend Map Stack
-
-**Leaflet.js Configuration:**
-- Base layer: OpenStreetMap tiles
-- Alternative layers: Satellite, Terrain (Thunderforest, Mapbox)
-- Custom track overlay as GeoJSON layer
-- Markers for start (green) and finish (red)
-- Popup markers for lap splits
-- Heatmap overlay for intensity (heart rate zones)
-
-**Interactive Features:**
-- Click on track to see point-in-time metrics
-- Elevation profile chart synchronized with map
-- Segment highlighting
-- Playback animation of activity
-- Compare multiple activities on same map
-
-### Static Map Generation
-
-For ActivityPub federated posts and thumbnails:
-- Use StaticMap library or external service
-- Generate PNG/JPEG preview of route
-- Include in ActivityPub attachment
-- Cache generated images
-
-## Federation Workflow Examples
-
-### Scenario 1: User Posts New Activity
-
-1. User uploads FIT file via web UI
-2. Backend parses file and stores data
-3. User adds title, description, and sets visibility to "Public"
-4. System creates ActivityPub `Create` activity
-5. Activity is added to user's outbox
-6. System retrieves list of followers
-7. For each remote follower:
- - Sign HTTP request with user's private key
- - POST activity to follower's server inbox
-8. Remote servers receive and process the activity
-9. Activity appears in followers' timelines on their platforms
-
-### Scenario 2: Remote User Follows Local User
-
-1. Remote user (on Mastodon) clicks "Follow" on local user's profile
-2. Mastodon server sends `Follow` activity to local user's inbox
-3. FitPub receives and validates the activity
-4. FitPub creates `Accept` activity in response
-5. FitPub stores the follow relationship
-6. FitPub sends `Accept` to remote server's inbox
-7. Follow relationship is now active
-8. Future public activities will be sent to remote follower
-
-### Scenario 3: Remote User Likes Local Activity
-
-1. Remote user views local activity on their platform
-2. User clicks "Like" or equivalent action
-3. Remote server sends `Like` activity to local inbox
-4. FitPub receives and processes the like
-5. Like count is incremented and stored
-6. Like appears on activity page
-7. Original poster may receive notification
-
-## Development Roadmap
-
-### Phase 1: MVP (Minimum Viable Product)
-
-**System Component 1: Activity File Processing Module** ✅
-- [x] FIT file upload and parsing (FitParser)
-- [x] FIT file validation (FitFileValidator)
-- [x] GPX file upload and parsing (GpxParser)
-- [x] GPX file validation (GpxFileValidator)
-- [x] Unified ActivityFileService with automatic format detection
-- [x] Activity entity with JSONB track points and simplified LineString
-- [x] Activity metrics extraction and storage (calculated from track points for GPX)
-- [x] Track simplification using Douglas-Peucker algorithm
-- [x] File service with comprehensive tests
-- [x] Integration tests with real FIT and GPX files
-
-**User Management & Security** ✅
-- [x] User entity with ActivityPub keys
-- [x] UserRepository with custom queries
-- [x] Password hashing with BCrypt
-- [x] JWT token provider for session management
-- [x] HTTP Signature validator for ActivityPub federation
-- [x] UserDetailsService implementation
-- [x] Security configuration (Spring Security)
-- [x] User registration endpoint (POST /api/auth/register)
-- [x] Login endpoint with JWT response (POST /api/auth/login)
-
-**Application Infrastructure** ✅
-- [x] Main application class (FitPubApplication.java)
-- [x] Application configuration (application.yml)
-- [x] Database configuration (PostgreSQL + PostGIS with Testcontainers Dev Services)
-- [x] CORS configuration for frontend
-- [x] Exception handling (global error handlers)
-- [x] Logging configuration
-- [x] Profile-specific configs (application-dev.yml, application-prod.yml)
-
-**Activity REST API** ✅
-- [x] POST /api/activities/upload - Upload FIT or GPX file (automatic format detection)
-- [x] GET /api/activities/{id} - Get activity details
-- [x] GET /api/activities - List user's activities (paginated)
-- [x] PUT /api/activities/{id} - Update activity metadata
-- [x] DELETE /api/activities/{id} - Delete activity
-- [x] Activity DTOs for API responses
-- [x] All endpoints tested and working
-
-**ActivityPub Actor Profile** ✅
-- [x] Actor model classes (Person, PublicKey)
-- [x] GET /users/{username} - Actor profile endpoint
-- [x] Actor JSON-LD serialization with @context
-- [x] Public key embedding in actor profile
-- [x] Profile metadata (name, bio, avatar)
-- [x] Tested with application/activity+json and application/ld+json
-
-**WebFinger Support** ✅
-- [x] WebFinger controller
-- [x] GET /.well-known/webfinger - User discovery
-- [x] WebFinger response DTO
-- [x] Account identifier parsing (acct:user@domain)
-- [x] Tested with valid and invalid requests
-
-**ActivityPub Collections** ✅
-- [x] POST /users/{username}/inbox - Inbox endpoint (accepts activities with 202 Accepted)
-- [x] GET /users/{username}/outbox - Outbox endpoint (returns empty OrderedCollection)
-- [x] GET /users/{username}/followers - Followers collection (returns empty OrderedCollection)
-- [x] GET /users/{username}/following - Following collection (returns empty OrderedCollection)
-- [x] OrderedCollection model classes
-- [x] Basic collection structure (TODOs exist for populating with actual data)
-- [x] All endpoints tested and working
-
-**Basic Federation** ✅
-- [x] Federation service for outbound activities (FederationService.java)
-- [x] HTTP signature signing for outbound requests (signRequest method in HttpSignatureValidator)
-- [x] HTTP signature verification for inbound requests (validate method already existed)
-- [x] Follow activity - Remote user follows local user (InboxProcessor)
-- [x] Accept activity - Accept follow requests (FederationService.sendAcceptActivity)
-- [x] Undo activity - Unfollow support (InboxProcessor)
-- [x] Follow entity and repository (Follow.java, FollowRepository.java)
-- [x] Remote actor entity and repository (RemoteActor.java, RemoteActorRepository.java)
-- [x] Inbox processor for incoming activities (InboxProcessor.java)
-- [x] Remote actor fetching and caching
-- [x] Follower inbox collection for activity distribution
-
-**Public Timeline** ✅
-- [x] Timeline service (TimelineService.java)
-- [x] Timeline DTOs for response (TimelineActivityDTO.java with ActivityMetricsSummary)
-- [x] GET /api/timeline/federated - Federated timeline for authenticated user
-- [x] GET /api/timeline/public - Public timeline (all public activities)
-- [x] GET /api/timeline/user - User's own timeline
-- [x] Timeline filtering and pagination (Spring Data Pageable)
-- [x] Activity visibility enforcement (PUBLIC, FOLLOWERS)
-- [x] Repository methods for timeline queries (findByUserIdInAndVisibilityInOrderByStartedAtDesc, findByVisibilityOrderByStartedAtDesc)
-
-**Database Migrations** ✅
-- [x] Flyway setup and configuration
-- [x] V1: Enable PostGIS extension
-- [x] V2: Users table with indexes (username, email, created_at)
-- [x] V3: Activities table with geospatial support (GIST index on simplified_track, GIN index on track_points_json)
-- [x] V4: Activity metrics table with one-to-one relationship
-- [x] V5: Follows table for federation (follower_id, following_actor_uri, status)
-- [x] V6: Remote actors table for ActivityPub federation cache
-- [x] All indexes for performance (user lookups, activity queries, spatial queries)
-- [x] Changed Hibernate ddl-auto from 'update' to 'validate'
-
-**Frontend Infrastructure** ✅
-- [x] Choose frontend approach (Thymeleaf + HTMX for server-side rendering with dynamic interactions)
-- [x] Static asset structure (css/, js/, img/ directories)
-- [x] HTMX dependency setup (via CDN in layout.html)
-- [x] Leaflet.js dependency setup (via CDN in layout.html)
-- [x] Chart.js dependency setup (via CDN in layout.html)
-- [x] CSS framework setup (Bootstrap 5.3.2 via CDN + Bootstrap Icons)
-- [x] Base Thymeleaf layout template with navigation (layout.html)
-- [x] Responsive mobile design foundation (Bootstrap grid + custom CSS)
-- [x] Custom CSS with FitPub theme (fitpub.css)
-- [x] Custom JavaScript utilities (fitpub.js with map/chart helpers)
-- [x] Thymeleaf dependencies added to pom.xml
-- [x] Home page template (index.html)
-- [x] Home controller for routing
-
-**Authentication UI** ✅
-- [x] User registration page/form (auth/register.html)
-- [x] Login page/form (auth/login.html)
-- [x] JWT token storage (localStorage in auth.js)
-- [x] Authentication state management (FitPubAuth object in auth.js)
-- [x] Protected route handling (SecurityConfig.java + client-side checks)
-- [x] Logout functionality (client-side token removal + server endpoint)
-- [x] Session expiration handling (JWT expiration check + warning)
-- [x] Login/registration error display (alert components in forms)
-- [x] Authentication view controller (AuthViewController.java)
-- [x] Dynamic navigation menu (shows/hides based on auth status)
-- [x] Form validation with Bootstrap
-- [x] Loading states for submit buttons
-- [x] Password confirmation validation
-- [x] Authenticated API fetch helper (authenticatedFetch in auth.js)
-
-**Activity Upload & Management UI** ✅
-- [x] Activity file upload form with drag-and-drop (FIT and GPX)
-- [x] Upload progress indicator
-- [x] Activity metadata form (title, description, visibility)
-- [x] Activity list view (user's own activities)
-- [x] Activity pagination controls
-- [x] Activity delete confirmation dialog
-- [x] Activity edit form
-- [x] File upload validation and error messages
-
-**Map Rendering & Visualization** ✅
-- [x] Leaflet.js map initialization
-- [x] OpenStreetMap tile layer integration
-- [x] GeoJSON track rendering on map
-- [x] Start/finish markers (green/red)
-- [x] Map bounds auto-fitting to track
-- [x] Track click handler for point-in-time metrics
-- [x] Map loading states and error handling
-- [x] Responsive map sizing
-
-**Activity Detail Page** ✅
-- [x] Activity metadata display (title, description, date, type)
-- [x] Interactive map with GPS track
-- [x] Activity metrics display (distance, duration, pace, elevation)
-- [x] Elevation profile chart (Chart.js)
-- [x] Activity statistics summary cards (distance, duration, pace, elevation gain)
-- [x] Visibility indicator (Public/Followers/Private)
-- [x] Additional metrics display (heart rate, cadence, speed, calories - shown if available)
-- [x] Edit button linking to activity edit page
-- [x] Delete button with confirmation modal
-- [x] Back to activities button
-- [x] Loading state indicator
-- [x] Error handling and display
-- [x] Start/finish markers on map
-- [x] Map bounds auto-fitting to track
-- [x] Responsive layout for mobile
-- [x] User profile links from activity cards and timeline
-
-**Timeline & Social Features UI** ✅
-- [x] Public timeline page (timeline/public.html)
-- [x] Federated timeline page (timeline/federated.html - following feed)
-- [x] User timeline page (timeline/user.html - own activities)
-- [x] Timeline activity cards with preview maps (Leaflet.js integration)
-- [x] Activity card metrics summary (distance, duration, pace, elevation)
-- [x] Pagination for timeline (with prev/next and page numbers)
-- [x] Empty state messages (for each timeline type)
-- [x] Loading states for timelines (spinner and loading text)
-- [x] Timeline view controller (TimelineViewController.java)
-- [x] Timeline JavaScript module (timeline.js with dynamic loading)
-- [x] Timeline CSS styles (timeline-card, user-avatar, preview-map)
-- [x] User information display (avatar, display name, username, federation indicator)
-- [x] Time ago formatting (e.g., "2h ago", "3d ago")
-- [x] Activity type badges (Run, Ride, Hike)
-- [x] Visibility indicators (Public/Followers/Private)
-- [x] Interactive preview maps with start/finish markers
-- [x] Responsive design for mobile and desktop
-- [x] Authentication-aware UI (shows/hides features based on login status)
-- [x] Error handling and user-friendly error messages
-
-**User Profile UI** ✅
-- [x] Public user profile page (profile/public.html)
-- [x] User profile display (avatar, bio, display name)
-- [x] User's activity list on profile with pagination
-- [x] Follower/following counts display (real data from backend)
-- [x] Profile edit page (profile/edit.html)
-- [x] Avatar URL input
-- [x] Profile settings form with validation
-- [x] Profile view controller (ProfileViewController.java)
-- [x] User API endpoints (UserController.java)
-- [x] User DTOs (UserDTO, UserUpdateRequest)
-- [x] GET /api/users/me - Get current user profile
-- [x] PUT /api/users/me - Update current user profile
-- [x] GET /api/users/{username} - Get user by username
-- [x] GET /api/activities/user/{username} - Get user's public activities
-- [x] Profile CSS styles (avatar-placeholder-large, stat-card, activity-item)
-- [x] Character counter for bio (500 chars max)
-- [x] Avatar preview on edit page
-- [x] Form validation and error handling
-- [x] Success messages and redirects
-- [x] Responsive mobile design
-- [x] Settings page placeholder (settings.html)
-- [x] Client-side authentication checks for protected pages
-
-**User Discovery UI** ✅
-- [x] Discover users page (users/discover.html)
-- [x] User search functionality with live search bar
-- [x] Browse all users with pagination
-- [x] User cards grid layout with avatar, bio, and stats
-- [x] Responsive design for mobile and desktop
-- [x] Empty state for no results
-- [x] Loading indicators
-- [x] View controller route (GET /discover)
-- [x] Integration with backend search and browse APIs
-
-**Navigation & Layout** ✅
-- [x] Top navigation bar with logo
-- [x] Navigation links (Timeline, Discover, My Activities, Upload, Profile)
-- [x] User menu dropdown (Profile, Settings, Logout)
-- [x] Footer with app info
-- [x] Mobile hamburger menu (Bootstrap responsive navbar)
-- [x] Dynamic navigation (shows/hides based on auth status)
-
-**Error Handling & User Feedback** ✅
-- [x] API error message display (in activity upload, detail, list pages)
-- [x] Success notifications (FitPub.showAlert function)
-- [x] Form validation error display (registration, login, activity forms)
-- [x] Loading spinners (activity detail page, upload page, timeline, profile)
-- [x] Empty states for timelines and profiles
-- [x] Client-side 403 handling via authentication redirects
-
----
-
-## Phase 1 (MVP) - ✅ COMPLETE!
-
-**All core features implemented and working:**
-- ✅ FIT and GPX file upload and processing (automatic format detection)
-- ✅ GPS track visualization with Leaflet maps
-- ✅ Activity management (CRUD operations)
-- ✅ User authentication and profiles
-- ✅ Public, federated, and user timelines
-- ✅ ActivityPub federation (Follow/Accept/Undo)
-- ✅ WebFinger user discovery
-- ✅ Responsive mobile-friendly UI
-- ✅ PostgreSQL + PostGIS database
-- ✅ Complete REST API
-
----
-
-### Phase 2: Social Features & Enhancements ✅
-- [x] Likes on activities (Like entity, repository, ActivityPub Like activity support)
-- [x] Comments on activities (Comment entity, repository, ActivityPub Note activity support)
-- [x] Activity sharing (Announce/boost functionality via ActivityPub)
-- [x] Privacy protection for GPS tracks (fuzzy start/finish zones)
-- [x] OSM tile rendering for activity maps (OsmTileRenderer with caching)
-- [x] Activity image generation with track overlay (ActivityImageService)
-- [x] FIT epoch timestamp fix (631065600 second offset for proper date handling)
-- [x] Web Mercator projection for accurate track-to-map alignment
-- [x] User search and discovery backend (UserRepository.searchUsers, UserRepository.findAllEnabledUsers, GET /api/users/search, GET /api/users/browse)
-- [x] User search and discovery UI (users/discover.html, /discover route, search bar with live filtering, user cards grid, pagination)
-- [x] Followers/following lists (ActorDTO, GET /api/users/{username}/followers, GET /api/users/{username}/following)
-- [x] Follower/following counts (UserController.populateSocialCounts, UserDTO with followersCount/followingCount, frontend displays real counts)
-- [x] Heart rate chart over time on activity details (Chart.js line chart, elapsed time x-axis, heart rate y-axis)
-- [x] Speed/pace chart over time on activity details (Chart.js line chart with smoothin[69287079d5e0a4532ba818ee.fit](src/test/resources/69287079d5e0a4532ba818ee.fit)g, displays speed in km/h with pace in tooltip)
-- [x] Notifications system (Notification entity, NotificationRepository, NotificationService, NotificationController REST API, notifications.html UI, notification bell in nav with unread count, polling every 30s, mark as read/delete, all/unread filter tabs)
-- [x] Custom 404 Not Found page (error/404.html with animated compass icon, suggestions, gradient background)
-- [x] Custom 403 Forbidden page (error/403.html with shield-lock icon, auth-aware login button, gradient background)
-- [x] Custom 500 Internal Server Error page (error/500.html with tools icon, recovery suggestions)
-- [x] Generic error page (error/error.html with dynamic error code display, technical details toggle)
-- [x] Empty state illustrations (reusable CSS classes in fitpub.css, floating animation, variant colors for activities/notifications/timeline/users/search, updated all timeline pages, notifications page, and discover page)
-- [ ] Enhanced privacy controls UI
-- [ ] Follow/unfollow buttons on user profiles
-- [ ] Activity visibility to followers (implement FOLLOWERS visibility enforcement)
-- [ ] Breadcrumb navigation
-- [ ] Active route highlighting in navigation
-- [ ] Global error boundary/handler
-
-### Phase 3: Advanced Analytics ✅
-- [x] Personal records tracking (PersonalRecord entity, PersonalRecordRepository, PersonalRecordService)
-- [x] 10 record types (FASTEST_1K, FASTEST_5K, FASTEST_10K, FASTEST_HALF_MARATHON, FASTEST_MARATHON, LONGEST_DISTANCE, LONGEST_DURATION, HIGHEST_ELEVATION_GAIN, MAX_SPEED, BEST_AVERAGE_PACE)
-- [x] Automatic PR detection on activity save
-- [x] Split time calculations for distance PRs
-- [x] Previous value tracking and improvement calculations
-- [x] Training load and recovery metrics (TrainingLoad entity, TrainingLoadRepository, TrainingLoadService)
-- [x] Training Stress Score (TSS) calculation based on duration, distance, and elevation
-- [x] Acute Training Load (ATL) - 7-day rolling average (fatigue)
-- [x] Chronic Training Load (CTL) - 28-day rolling average (fitness)
-- [x] Training Stress Balance (TSB) - CTL - ATL (freshness)
-- [x] Form status calculation (FRESH, OPTIMAL, FATIGUED, UNKNOWN)
-- [x] Achievement/badge system (Achievement entity, AchievementRepository, AchievementService)
-- [x] 25+ achievement types with badge icons and colors
-- [x] First activity achievements (by type)
-- [x] Distance milestones (10K, 50K, 100K, 250K, 500K, 1000K)
-- [x] Activity count milestones (10, 50, 100, 250, 500, 1000 activities)
-- [x] Streak achievements (7, 30, 100 days)
-- [x] Time-based achievements (early bird, night owl)
-- [x] Elevation achievements (mountaineer 1000m, 5000m, 10000m)
-- [x] Variety achievements (trying multiple activity types)
-- [x] Speed achievements (speed demon 40+ km/h)
-- [x] Weekly/monthly/yearly summaries (ActivitySummary entity, ActivitySummaryRepository, ActivitySummaryService)
-- [x] Period-based aggregations (weekly, monthly, yearly)
-- [x] Activity type breakdown per period
-- [x] Total metrics (distance, duration, elevation gain)
-- [x] Average and max speed tracking
-- [x] PRs and achievements counts per period
-- [x] Async summary updates
-- [x] Analytics REST API (AnalyticsController)
-- [x] GET /api/analytics/dashboard - Dashboard stats
-- [x] GET /api/analytics/personal-records - List PRs with optional activity type filter
-- [x] GET /api/analytics/achievements - List earned achievements
-- [x] GET /api/analytics/training-load - Training load data with date range
-- [x] GET /api/analytics/form-status - Current form status
-- [x] GET /api/analytics/summaries/{period} - Weekly/monthly/yearly summaries
-- [x] Analytics UI pages (AnalyticsViewController)
-- [x] Analytics dashboard (analytics/dashboard.html) with stats overview, current week/month, recent PRs and achievements
-- [x] Personal records page (analytics/personal-records.html) with activity type filters, improvement display
-- [x] Achievements page (analytics/achievements.html) with badge gallery, animations, metadata display
-- [x] Training load page (analytics/training-load.html) with form status card, Chart.js charts (TSS bar chart, ATL vs CTL line chart, TSB line chart with color zones)
-- [x] Summaries page (analytics/summaries.html) with weekly/monthly/yearly tabs, summary cards, type breakdown
-- [x] Analytics link in navigation (authenticated users only)
-- [x] Database migration V10__create_analytics_tables.sql
-- [x] Integration with FitFileService (auto-update analytics on activity save)
-- [x] Security configuration updated (analytics routes and API endpoints)
-- [x] Weather data integration (WeatherData entity, WeatherDataRepository, WeatherService with OpenWeatherMap API)
-- [x] Weather database migration V11__create_weather_data_table.sql
-- [x] Weather API configuration (fitpub.weather.enabled, fitpub.weather.api-key)
-- [x] Weather fetching on activity upload (automatic for activities within 5 days)
-- [x] Weather API endpoint GET /api/activities/{id}/weather
-- [x] Weather display on activity detail page (temperature, humidity, wind, pressure, precipitation)
-
-### Phase 4: Enhanced Federation
-- [x] Rich preview cards for activities
-- [x] Cross-platform activity sync
-
-### Phase 5: Mobile & Integrations
-- [x] GPX file support (completed - import from Strava, Komoot, etc. via GPX export)
-- [ ] Progressive Web App (PWA)
-- [ ] Native mobile apps (optional)
-- [ ] Direct device sync (Garmin Connect API)
-- [ ] Webhook integrations
-- [ ] TCX file support
-- [ ] Avatar file upload (currently URL-based)
-
-### Phase 6: Testing & Documentation
-
-**Testing:** ✅ **95 tests passing**
-- [x] Unit tests for TrainingLoadService (10 tests - TSS, ATL, CTL, TSB calculations)
-- [x] Unit tests for PersonalRecordService (13 tests - all PR types, improvement detection)
-- [x] Unit tests for AchievementService (16 tests - all badge types, edge cases)
-- [x] Unit tests for FitFileService (10 tests - existing tests updated and fixed)
-- [x] Unit tests for WeatherService (18 tests - lat/lon vs latitude/longitude field names, configuration, API errors, parsing)
-- [x] Integration tests for ActivityController (10 tests - full stack HTTP to database)
-- [ ] Integration tests for ActivityPub federation endpoints
-- [ ] Integration tests for WebFinger discovery
-- [ ] Integration tests for Timeline and Analytics REST API endpoints
-- [ ] Frontend E2E tests with Playwright or Cypress
-
-**Documentation:**
-- [ ] Create comprehensive README.md with project overview and features
-- [ ] Write database setup guide (PostgreSQL + PostGIS installation)
-- [ ] Create API documentation with Swagger/OpenAPI
-- [ ] Write deployment instructions (Docker, Kubernetes, bare metal)
-- [ ] Create user documentation (how to use FitPub)
-- [ ] Write administrator guide (configuration, maintenance)
-- [ ] Document frontend development setup and architecture
-
-## Maven Project Structure
-
-```
-fitpub/
-├── pom.xml
-├── src/
-│ ├── main/
-│ │ ├── java/
-│ │ │ └── com/
-│ │ │ └── fitpub/
-│ │ │ ├── FitPubApplication.java
-│ │ │ ├── config/
-│ │ │ │ ├── SecurityConfig.java
-│ │ │ │ ├── WebConfig.java
-│ │ │ │ └── ActivityPubConfig.java
-│ │ │ ├── controller/
-│ │ │ │ ├── ActivityController.java
-│ │ │ │ ├── UserController.java
-│ │ │ │ ├── ActivityPubController.java
-│ │ │ │ └── WebFingerController.java
-│ │ │ ├── service/
-│ │ │ │ ├── FitFileService.java
-│ │ │ │ ├── ActivityService.java
-│ │ │ │ ├── FederationService.java
-│ │ │ │ ├── UserService.java
-│ │ │ │ └── GeospatialService.java
-│ │ │ ├── model/
-│ │ │ │ ├── entity/
-│ │ │ │ │ ├── User.java
-│ │ │ │ │ ├── Activity.java
-│ │ │ │ │ ├── TrackPoint.java
-│ │ │ │ │ ├── Follow.java
-│ │ │ │ │ └── RemoteActor.java
-│ │ │ │ ├── dto/
-│ │ │ │ │ ├── ActivityDTO.java
-│ │ │ │ │ ├── UserDTO.java
-│ │ │ │ │ └── TrackDTO.java
-│ │ │ │ └── activitypub/
-│ │ │ │ ├── Actor.java
-│ │ │ │ ├── Activity.java
-│ │ │ │ └── Collection.java
-│ │ │ ├── repository/
-│ │ │ │ ├── UserRepository.java
-│ │ │ │ ├── ActivityRepository.java
-│ │ │ │ ├── TrackPointRepository.java
-│ │ │ │ └── FollowRepository.java
-│ │ │ ├── security/
-│ │ │ │ ├── JwtTokenProvider.java
-│ │ │ │ ├── HttpSignatureValidator.java
-│ │ │ │ └── UserDetailsServiceImpl.java
-│ │ │ └── util/
-│ │ │ ├── FitParser.java
-│ │ │ ├── GeoJsonConverter.java
-│ │ │ └── ActivityPubUtil.java
-│ │ └── resources/
-│ │ ├── application.yml
-│ │ ├── application-dev.yml
-│ │ ├── application-prod.yml
-│ │ ├── static/
-│ │ │ ├── css/
-│ │ │ ├── js/
-│ │ │ └── img/
-│ │ └── templates/
-│ │ ├── index.html
-│ │ ├── activity.html
-│ │ └── profile.html
-│ └── test/
-│ └── java/
-│ └── com/
-│ └── fitpub/
-│ ├── service/
-│ ├── controller/
-│ └── integration/
-└── README.md
-```
-
-## Key Maven Dependencies
-
-```xml
-
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-starter-data-jpa
-
-
- org.springframework.boot
- spring-boot-starter-security
-
-
-
-
- org.postgresql
- postgresql
-
-
- org.hibernate
- hibernate-spatial
-
-
-
-
- com.garmin
- fit
- 21.XX.XX
-
-
-
-
- com.fasterxml.jackson.core
- jackson-databind
-
-
-
-
- org.apache.httpcomponents.client5
- httpclient5
-
-
-
-
- io.jsonwebtoken
- jjwt-api
-
-
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
-
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
-```
-
-## Configuration Examples
-
-### application.yml
-
-```yaml
-spring:
- application:
- name: fitpub
- datasource:
- url: jdbc:postgresql://localhost:5432/fitpub
- username: fitpub_user
- password: ${DB_PASSWORD}
- jpa:
- hibernate:
- ddl-auto: validate
- properties:
- hibernate:
- dialect: org.hibernate.spatial.dialect.postgis.PostgisDialect
- servlet:
- multipart:
- max-file-size: 50MB
- max-request-size: 50MB
-
-fitpub:
- domain: fitpub.example
- base-url: https://fitpub.example
- activitypub:
- enabled: true
- max-federation-retries: 3
- security:
- jwt:
- secret: ${JWT_SECRET}
- expiration: 86400000 # 24 hours
- storage:
- fit-files:
- path: /var/fitpub/fit-files
- retention-days: 365
-```
-
-## Deployment Considerations
-
-### Infrastructure Requirements
-
-- **Application Server**: JVM-based (Java 17+)
-- **Database**: PostgreSQL 13+ with PostGIS extension
-- **Reverse Proxy**: Nginx or Traefik for HTTPS termination
-- **Storage**: File storage for FIT files and generated assets
-- **Cache**: Redis (optional, for session management)
-
-### Scaling Strategy
-
-- Horizontal scaling of application servers
-- Database read replicas for heavy read operations
-- CDN for static assets and map tiles
-- Background job processing for FIT file parsing (Spring Batch or async)
-- Rate limiting and request throttling
-
-### Monitoring & Observability
-
-- Application metrics (Micrometer + Prometheus)
-- Database query performance monitoring
-- Federation success/failure rates
-- API response times
-- User activity analytics
-
-## Legal & Compliance
-
-### Licensing Considerations
-
-- Choose appropriate open-source license (AGPL, MIT, Apache 2.0)
-- Comply with FIT SDK licensing terms
-- Attribute map tile providers
-- Terms of Service for user-generated content
-- Privacy Policy (GDPR, CCPA compliance)
-
-### Data Retention
-
-- User data export functionality
-- Right to be forgotten (account deletion)
-- Activity data backup procedures
-- Federation data cleanup (remove data from remote deleted actors)
-
-## Community & Contribution
-
-### Open Source Goals
-
-- Public GitHub repository
-- Contribution guidelines
-- Code of conduct
-- Issue templates
-- Documentation for developers
-- Public roadmap and feature requests
-
-### Fediverse Integration
-
-- Publish FEP (Fediverse Enhancement Proposal) if introducing custom extensions
-- Collaborate with other ActivityPub developers
-- Test interoperability with major platforms (Mastodon, Pleroma, Pixelfed)
-- Participate in Fediverse developer community
-
-## Future Enhancements
-
-- **AI-Powered Insights**: Training recommendations, injury prevention
-- **Virtual Racing**: Compete on same routes asynchronously
-- **Route Planning**: Create routes and share with community
-- **Live Tracking**: Real-time activity sharing during workout
-- **Wearable Integration**: Direct sync with smartwatches
-- **Audio Cues**: Export audio-guided workouts
-- **Social Challenges**: Group goals and competitions
-- **Marketplace**: Routes, training plans, coaching services
-
----
-
-## Getting Started
-
-### Prerequisites
-
-- Java 17 or higher
-- Maven 3.8+
-- PostgreSQL 13+ with PostGIS
-- Git
-
-### Quick Start
-
-1. Clone repository
-2. Set up PostgreSQL database with PostGIS extension
-3. Configure `application.yml` with database credentials
-4. Run `mvn clean install`
-5. Start application: `mvn spring-boot:run`
-6. Access at `http://localhost:8080`
-
-### First Steps
-
-1. Register a user account
-2. Upload a FIT file from your GPS device
-3. View your activity on the interactive map
-4. Set up ActivityPub federation (optional)
-5. Follow other athletes on the Fediverse
-
----
-
-**Project Status**: Planning & Initial Development
-
-**License**: TBD
-
-**Contributors Welcome**: Yes
-
-**Contact**: [Project repository or contact information]
\ No newline at end of file
diff --git a/DARK_MODE_FIXES.md b/DARK_MODE_FIXES.md
deleted file mode 100644
index c183760..0000000
--- a/DARK_MODE_FIXES.md
+++ /dev/null
@@ -1,366 +0,0 @@
-# Dark Mode Fixes - Complete Summary
-
-## Issues Found and Fixed
-
-I systematically analyzed all 31 HTML template files and CSS files in the FitPub application to identify dark mode issues. Here's what was found and fixed:
-
----
-
-## ✅ Fixed Issues
-
-### 1. **Form Elements** (CRITICAL FIX)
-**Problem**: Form labels, checkbox labels, and form helper text were showing as dark text on dark backgrounds.
-
-**Affected Pages**:
-- Login page (`auth/login.html`)
-- Registration page (`auth/register.html`)
-- Activity upload (`activities/upload.html`)
-- Activity edit (`activities/edit.html`)
-- Profile edit (`profile/edit.html`)
-- Batch upload (`activities/batch-upload.html`)
-
-**Fix Applied**: Added to `fitpub.css`:
-```css
-@media (prefers-color-scheme: dark) {
- /* Form elements - Dark Mode Fix */
- .form-label {
- color: var(--dark-text) !important;
- }
-
- .form-check-label {
- color: var(--dark-text) !important;
- }
-
- .form-text {
- color: var(--dark-text-muted) !important;
- }
-}
-```
-
-**Files Modified**:
-- `src/main/resources/static/css/fitpub.css` (lines 1018-1029)
-
----
-
-### 2. **Typography Elements** (CRITICAL FIX)
-**Problem**: ``, ``, and `` tags had no explicit dark mode color, causing dark text on dark backgrounds in activity detail pages.
-
-**Affected Pages**:
-- Activity detail page (`activities/detail.html`) - Weather section, additional metrics
-
-**Fix Applied**: Added to `fitpub.css`:
-```css
-@media (prefers-color-scheme: dark) {
- /* Typography - Dark Mode Fix */
- strong {
- color: var(--dark-text);
- }
-
- b {
- color: var(--dark-text);
- }
-
- small {
- color: var(--dark-text);
- }
-}
-```
-
-**Files Modified**:
-- `src/main/resources/static/css/fitpub.css` (lines 1031-1042)
-
----
-
-### 3. **Batch Upload Status Badges** (HIGH PRIORITY FIX)
-**Problem**: Success/Failed/Pending status badges had hardcoded light-mode colors, making them unreadable in dark mode.
-
-**Affected Pages**:
-- Batch upload page (`activities/batch-upload.html`)
-
-**Before** (Light Mode Only):
-```css
-.status-SUCCESS {
- background: #d4edda; /* Light green */
- color: #155724; /* Dark green text */
-}
-
-.status-FAILED {
- background: #f8d7da; /* Light red */
- color: #721c24; /* Dark red text */
-}
-
-.status-PENDING, .status-PROCESSING {
- background: #fff3cd; /* Light yellow */
- color: #856404; /* Dark yellow text */
-}
-```
-
-**After** (Dark Mode Added):
-```css
-@media (prefers-color-scheme: dark) {
- .status-SUCCESS {
- background: rgba(57, 255, 20, 0.25); /* Neon green with transparency */
- color: #39ff14; /* Bright neon green text */
- border: 1px solid #39ff14;
- }
-
- .status-FAILED {
- background: rgba(220, 53, 69, 0.25); /* Red with transparency */
- color: #ff6b7a; /* Bright red text */
- border: 1px solid #dc3545;
- }
-
- .status-PENDING, .status-PROCESSING {
- background: rgba(255, 193, 7, 0.25); /* Yellow with transparency */
- color: #ffc107; /* Bright yellow text */
- border: 1px solid #ffc107;
- }
-}
-```
-
-**Files Modified**:
-- `src/main/resources/templates/activities/batch-upload.html` (lines 121-144)
-
----
-
-### 4. **404 Error Page** (CRITICAL FIX)
-**Problem**: The 404 "Not Found" page had NO dark mode support at all - fully white background with dark text.
-
-**Affected Pages**:
-- `error/404.html`
-
-**Fix Applied**: Added comprehensive dark mode styles:
-```css
-@media (prefers-color-scheme: dark) {
- .error-container {
- background: linear-gradient(135deg, #2d0052 0%, #1a0033 100%);
- }
-
- .error-card {
- background: #251040;
- color: #e8e8f0;
- border: 3px solid #ff1493;
- }
-
- .error-title {
- color: #e8e8f0;
- }
-
- .error-subtitle,
- .error-message {
- color: #a8a8c0;
- }
-
- .error-suggestions {
- background: #1a0a30;
- border: 2px solid #00ffff;
- }
-
- .btn-home {
- background: linear-gradient(135deg, #ff1493 0%, #9d00ff 100%);
- border-color: #ff1493;
- }
-
- .btn-back {
- color: #00ffff;
- }
-
- .btn-back:hover {
- color: #ff1493;
- }
-}
-```
-
-**Files Modified**:
-- `src/main/resources/templates/error/404.html` (lines 118-171)
-
----
-
-### 5. **403 Forbidden Page** (CRITICAL FIX)
-**Problem**: The 403 "Forbidden" page had NO dark mode support.
-
-**Affected Pages**:
-- `error/403.html`
-
-**Fix Applied**: Added comprehensive dark mode styles (same pattern as 404 page)
-
-**Files Modified**:
-- `src/main/resources/templates/error/403.html` (lines 129-179)
-
----
-
-### 6. **500 Internal Server Error Page** (CRITICAL FIX)
-**Problem**: The 500 error page had NO dark mode support.
-
-**Affected Pages**:
-- `error/500.html`
-
-**Fix Applied**: Added comprehensive dark mode styles (same pattern as 404 page)
-
-**Files Modified**:
-- `src/main/resources/templates/error/500.html` (lines 118-171)
-
----
-
-### 7. **Generic Error Page** (CRITICAL FIX)
-**Problem**: The generic error page had NO dark mode support.
-
-**Affected Pages**:
-- `error/error.html`
-
-**Fix Applied**: Added comprehensive dark mode styles with error code styling
-
-**Files Modified**:
-- `src/main/resources/templates/error/error.html` (lines 85-138)
-
----
-
-## ✅ Already Fixed (No Action Needed)
-
-These elements were already properly styled for dark mode in the existing CSS:
-
-1. **Text Muted** - `text-muted` class
- - Already styled at line 652-654 in `fitpub.css`
-
-2. **Background Light** - `bg-light` class
- - Already overridden at line 555-557 in `fitpub.css`
-
-3. **Cards** - `.card`, `.card-body`, `.card-header`
- - Already styled at lines 564-577 in `fitpub.css`
-
-4. **Timeline Cards** - `.timeline-card`
- - Already styled at lines 590-617 in `fitpub.css`
-
-5. **Metric Cards** - `.metric-card`, `.metric-label`, `.metric-value`
- - Already styled at lines 620-632 in `fitpub.css`
-
-6. **File Upload Area** - `.file-upload-area`
- - Already styled at lines 635-644 in `fitpub.css`
-
-7. **Forms** - `.form-control`, `.form-select`, `.input-group-text`
- - Already styled at lines 705-730 in `fitpub.css`
-
-8. **Modals** - `.modal-content`, `.modal-header`, `.modal-footer`
- - Already styled at lines 733-754 in `fitpub.css`
-
-9. **Dropdowns** - `.dropdown-menu`, `.dropdown-item`
- - Already styled at lines 757-774 in `fitpub.css`
-
-10. **Alerts** - `.alert-success`, `.alert-danger`, `.alert-info`, `.alert-warning`
- - Already styled at lines 777-799 in `fitpub.css`
-
-11. **Tables** - `.table`, `.table-striped`, `.table-hover`
- - Already styled at lines 802-813 in `fitpub.css`
-
-12. **Pagination** - `.pagination`, `.page-link`, `.page-item`
- - Already styled at lines 827-849 in `fitpub.css`
-
-13. **Empty States** - `.empty-state`, `.empty-state-icon`
- - Already styled at lines 677-691 in `fitpub.css`
-
-14. **Footer** - `footer`, `footer.bg-light`
- - Already styled at lines 955-984 in `fitpub.css`
-
-15. **Indoor Activity Placeholder** - `#indoorPlaceholder`
- - Already styled at lines 987-1016 in `fitpub.css`
-
-16. **Notifications Page**
- - Has comprehensive inline dark mode styles (lines 89-149 in `notifications.html`)
-
----
-
-## Summary Statistics
-
-### Files Modified: 5
-1. `src/main/resources/static/css/fitpub.css` - Main CSS file
-2. `src/main/resources/templates/activities/batch-upload.html` - Batch upload page
-3. `src/main/resources/templates/error/404.html` - 404 error page
-4. `src/main/resources/templates/error/403.html` - 403 error page
-5. `src/main/resources/templates/error/500.html` - 500 error page
-6. `src/main/resources/templates/error/error.html` - Generic error page
-
-### Issues Fixed: 7
-1. ✅ Form labels - Dark text on dark background
-2. ✅ Form check labels - Dark text on dark background
-3. ✅ Form helper text - Dark text on dark background
-4. ✅ Typography elements (strong, b, small) - No dark mode color
-5. ✅ Batch upload status badges - Light mode colors only
-6. ✅ Error pages (404, 403, 500, generic) - No dark mode support
-
-### CSS Lines Added: ~190 lines
-- Main CSS file: 25 lines
-- Batch upload page: 25 lines
-- Error pages: ~140 lines total (4 pages × ~35 lines each)
-
----
-
-## Testing Checklist
-
-To verify all fixes are working:
-
-### ✅ Forms (Enable Dark Mode in OS)
-- [ ] Login page - Labels are visible (light text)
-- [ ] Registration page - Labels and helper text are visible
-- [ ] Activity upload - Form labels visible
-- [ ] Activity edit - Form labels visible
-- [ ] Profile edit - Form labels visible
-- [ ] Checkbox labels are visible
-
-### ✅ Activity Detail Page
-- [ ] Strong text in metrics section is visible
-- [ ] Weather section text is visible
-- [ ] Additional metrics section is readable
-
-### ✅ Batch Upload Page
-- [ ] Success badge is visible (neon green)
-- [ ] Failed badge is visible (bright red)
-- [ ] Pending/Processing badge is visible (bright yellow)
-- [ ] Job cards have proper dark background
-
-### ✅ Error Pages
-- [ ] 404 page - Dark background, light text
-- [ ] 403 page - Dark background, light text, login button visible
-- [ ] 500 page - Dark background, light text
-- [ ] Generic error page - Dark background, error code visible
-
----
-
-## Color Palette Used
-
-### Dark Mode Colors (from `fitpub.css`):
-```css
---dark-bg: #0f0520 /* Main background */
---dark-bg-alt: #1a0a30 /* Alternative background */
---dark-surface: #251040 /* Card/surface background */
---dark-surface-hover: #301550 /* Hover state */
---dark-text: #e8e8f0 /* Primary text */
---dark-text-muted: #a8a8c0 /* Muted/secondary text */
---dark-border: #3d2060 /* Borders */
-```
-
-### Neon Accents (maintain 80s aesthetic):
-```css
---neon-pink: #ff1493
---neon-purple: #9d00ff
---neon-cyan: #00ffff
---neon-yellow: #ffff00
---neon-orange: #ff6600
---neon-green: #39ff14
---neon-blue: #00d4ff
-```
-
----
-
-## Result
-
-✅ **All dark mode issues have been fixed!**
-
-The application now has **complete dark mode support** across all pages:
-- ✅ Forms are fully readable
-- ✅ Typography has proper contrast
-- ✅ Status badges have neon colors
-- ✅ Error pages have dark backgrounds
-- ✅ All Bootstrap components are styled
-- ✅ 80s neon aesthetic maintained
-
-No more dark text on dark backgrounds! 🎉
diff --git a/FEDERATION_TESTING_GUIDE.md b/FEDERATION_TESTING_GUIDE.md
deleted file mode 100644
index 16a08c4..0000000
--- a/FEDERATION_TESTING_GUIDE.md
+++ /dev/null
@@ -1,696 +0,0 @@
-# FitPub Federation Testing Guide
-
-This guide explains how to test the instance-to-instance federation functionality by running two FitPub instances locally.
-
-## Docker Compose Setup (Recommended)
-
-The easiest way to test federation is using Docker Compose, which automatically sets up two complete FitPub instances with separate databases and proper networking.
-
-### Architecture
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ Docker Network (fitpub-federation) │
-│ │
-│ ┌──────────────────────┐ ┌──────────────────────┐ │
-│ │ Instance 1 │ │ Instance 2 │ │
-│ │ (instance1.local) │◄─────►│ (instance2.local) │ │
-│ │ Port: 8080 │ │ Port: 8081 │ │
-│ └──────────────────────┘ └──────────────────────┘ │
-│ │ │ │
-│ ▼ ▼ │
-│ ┌──────────────────┐ ┌──────────────────┐ │
-│ │ PostgreSQL 1 │ │ PostgreSQL 2 │ │
-│ │ (postgres1) │ │ (postgres2) │ │
-│ │ Port: 5432 │ │ Port: 5433 │ │
-│ └──────────────────┘ └──────────────────┘ │
-│ │
-└─────────────────────────────────────────────────────────────┘
- ▲ ▲
- │ │
- localhost:8080 localhost:8081
-```
-
-### Quick Start
-
-1. **Start both instances**:
- ```bash
- docker-compose -f docker-compose.federation-test.yml up -d
- ```
-
-2. **Check status**:
- ```bash
- docker-compose -f docker-compose.federation-test.yml ps
- ```
-
-3. **Access the instances**:
- - Instance 1: http://localhost:8080
- - Instance 2: http://localhost:8081
-
-4. **Follow the [Test Scenarios](#test-scenarios) below** to verify federation functionality
-
-5. **View logs** (in separate terminals):
- ```bash
- # Instance 1 logs
- docker-compose -f docker-compose.federation-test.yml logs -f fitpub1
-
- # Instance 2 logs
- docker-compose -f docker-compose.federation-test.yml logs -f fitpub2
- ```
-
-6. **Stop and clean up**:
- ```bash
- # Stop containers
- docker-compose -f docker-compose.federation-test.yml down
-
- # Stop and remove volumes (complete cleanup)
- docker-compose -f docker-compose.federation-test.yml down -v
- ```
-
-### Service Overview
-
-The Docker Compose setup includes:
-
-- **postgres1**: PostgreSQL 16 with PostGIS 3.4 for Instance 1
- - Database: `fitpub1`
- - Port: 5432 (internal), 5434 (on host)
-
-- **postgres2**: PostgreSQL 16 with PostGIS 3.4 for Instance 2
- - Database: `fitpub2`
- - Port: 5432 (internal), 5433 (on host)
-
-- **fitpub1**: FitPub Instance 1
- - Domain: `instance1.local:8080`
- - Port: 8080
- - Network alias: `instance1.local`
-
-- **fitpub2**: FitPub Instance 2
- - Domain: `instance2.local:8081`
- - Port: 8081
- - Network alias: `instance2.local`
-
-### Docker-Specific Commands
-
-**Access database directly**:
-```bash
-# Instance 1 database
-docker exec -it fitpub-postgres1 psql -U fitpub -d fitpub1
-
-# Instance 2 database
-docker exec -it fitpub-postgres2 psql -U fitpub -d fitpub2
-```
-
-**Inspect network**:
-```bash
-docker network inspect fitpub-federation
-```
-
-**View container details**:
-```bash
-docker inspect fitpub-instance1
-docker inspect fitpub-instance2
-```
-
-**Restart a single service**:
-```bash
-docker-compose -f docker-compose.federation-test.yml restart fitpub1
-docker-compose -f docker-compose.federation-test.yml restart fitpub2
-```
-
-**Rebuild images** (after code changes):
-```bash
-docker-compose -f docker-compose.federation-test.yml build
-docker-compose -f docker-compose.federation-test.yml up -d
-```
-
-### Docker Troubleshooting
-
-**Container won't start**:
-```bash
-# Check logs for errors
-docker-compose -f docker-compose.federation-test.yml logs fitpub1
-docker-compose -f docker-compose.federation-test.yml logs fitpub2
-
-# Check health status
-docker ps -a | grep fitpub
-```
-
-**Database connection issues**:
-```bash
-# Verify database is healthy
-docker-compose -f docker-compose.federation-test.yml ps postgres1
-docker-compose -f docker-compose.federation-test.yml ps postgres2
-
-# Check database logs
-docker-compose -f docker-compose.federation-test.yml logs postgres1
-```
-
-**Network connectivity issues**:
-```bash
-# Test DNS resolution from inside container
-docker exec -it fitpub-instance1 ping instance2.local
-docker exec -it fitpub-instance2 ping instance1.local
-
-# Test HTTP connectivity
-docker exec -it fitpub-instance1 curl http://instance2.local:8081/.well-known/webfinger
-```
-
-**Port already in use**:
-```bash
-# Find process using port 8080
-lsof -ti:8080 | xargs kill -9
-
-# Or use different external ports in docker-compose.yml
-```
-
-**Volume permission issues**:
-```bash
-# Remove all volumes and start fresh
-docker-compose -f docker-compose.federation-test.yml down -v
-docker-compose -f docker-compose.federation-test.yml up -d
-```
-
-**Platform warning on Apple Silicon (M1/M2/M3 Macs)**:
-```
-The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8)
-```
-This is expected and safe to ignore. Docker will use emulation (Rosetta 2) to run the amd64 images. Performance may be slightly slower than native ARM images, but fully functional for testing.
-
----
-
-## Manual Setup (Alternative)
-
-If you prefer to run the instances directly without Docker, follow these instructions:
-
-### Prerequisites
-
-- Java 17+
-- Maven 3.8+
-- PostgreSQL 13+ with PostGIS extension
-- Two separate PostgreSQL databases
-- Two different port numbers for the applications
-
-## Setup
-
-### Step 1: Create Two PostgreSQL Databases
-
-```bash
-# Connect to PostgreSQL
-psql -U postgres
-
-# Create databases
-CREATE DATABASE fitpub_instance1;
-CREATE DATABASE fitpub_instance2;
-
-# Enable PostGIS extension for both databases
-\c fitpub_instance1
-CREATE EXTENSION IF NOT EXISTS postgis;
-
-\c fitpub_instance2
-CREATE EXTENSION IF NOT EXISTS postgis;
-
-\q
-```
-
-### Step 2: Prepare Application Profiles
-
-Create two separate application configuration files:
-
-#### `application-instance1.yml`
-
-```yaml
-server:
- port: 8080
-
-spring:
- datasource:
- url: jdbc:postgresql://localhost:5432/fitpub_instance1
- username: postgres
- password: your_password
- jpa:
- hibernate:
- ddl-auto: validate
-
-fitpub:
- base-url: http://localhost:8080
- domain: localhost:8080
-
-logging:
- level:
- net.javahippie.fitpub: DEBUG
-```
-
-#### `application-instance2.yml`
-
-```yaml
-server:
- port: 8081
-
-spring:
- datasource:
- url: jdbc:postgresql://localhost:5432/fitpub_instance2
- username: postgres
- password: your_password
- jpa:
- hibernate:
- ddl-auto: validate
-
-fitpub:
- base-url: http://localhost:8081
- domain: localhost:8081
-
-logging:
- level:
- net.javahippie.fitpub: DEBUG
-```
-
-### Step 3: Build the Application
-
-```bash
-mvn clean package -DskipTests
-```
-
-## Running the Instances
-
-### Terminal 1: Start Instance 1
-
-```bash
-java -jar target/feditrack-1.0-SNAPSHOT.jar --spring.profiles.active=instance1
-```
-
-Wait for the application to start completely. You should see:
-```
-Started FitPubApplication in X.XXX seconds
-```
-
-### Terminal 2: Start Instance 2
-
-```bash
-java -jar target/feditrack-1.0-SNAPSHOT.jar --spring.profiles.active=instance2
-```
-
-## Test Scenarios
-
-### Test 1: User Registration
-
-**Instance 1 (http://localhost:8080)**
-1. Navigate to http://localhost:8080/register
-2. Register user: `alice` / `alice@localhost1.test` / `password123`
-3. Login
-
-**Instance 2 (http://localhost:8081)**
-1. Navigate to http://localhost:8081/register
-2. Register user: `bob` / `bob@localhost2.test` / `password123`
-3. Login
-
-### Test 2: WebFinger Discovery
-
-**From Instance 1, discover Bob on Instance 2:**
-
-```bash
-curl http://localhost:8080/.well-known/webfinger?resource=acct:bob@localhost:8081
-```
-
-Expected response:
-```json
-{
- "subject": "acct:bob@localhost:8081",
- "aliases": [
- "http://localhost:8081/users/bob"
- ],
- "links": [
- {
- "rel": "self",
- "type": "application/activity+json",
- "href": "http://localhost:8081/users/bob"
- }
- ]
-}
-```
-
-**From Instance 2, discover Alice on Instance 1:**
-
-```bash
-curl http://localhost:8081/.well-known/webfinger?resource=acct:alice@localhost:8080
-```
-
-### Test 3: Remote User Discovery via UI
-
-**On Instance 1 (Alice following Bob):**
-
-1. Login as Alice
-2. Navigate to http://localhost:8080/discover
-3. In the "Follow Remote Users" section, enter: `bob@localhost:8081`
-4. Click "Search"
-5. Verify Bob's profile appears with avatar, display name, and bio
-6. Click "Follow" button
-7. Verify notification appears: "Follow request sent to bob@localhost:8081"
-
-**Verify on Instance 2 (Bob's perspective):**
-
-1. Login as Bob on http://localhost:8081
-2. Check notifications - you should see: "alice@localhost:8080 followed you"
-3. Navigate to http://localhost:8081/users/bob/followers
-4. Verify alice@localhost:8080 appears in followers list
-
-### Test 4: Following Relationship Check
-
-**Check via API:**
-
-```bash
-# From Instance 2, check Bob's followers
-curl http://localhost:8081/api/users/bob/followers | jq
-
-# Expected: Alice should be in the list
-```
-
-**Check via UI:**
-
-On Instance 2:
-1. Navigate to http://localhost:8081/users/bob
-2. Check "Followers" count - should be 1
-3. Click on "Followers" - Alice should be listed
-
-On Instance 1:
-1. Navigate to http://localhost:8080/users/alice
-2. Check "Following" count - should be 1
-3. Click on "Following" - Bob should be listed
-
-### Test 5: Activity Federation
-
-**Bob uploads a workout on Instance 2:**
-
-1. Login as Bob on http://localhost:8081
-2. Navigate to http://localhost:8081/upload
-3. Upload a FIT file (use test file from `src/test/resources/`)
-4. Set title: "Morning 10K Run"
-5. Set visibility: "Public"
-6. Click "Upload"
-
-**Verify on Instance 1 (Alice's federated timeline):**
-
-1. Login as Alice on http://localhost:8080
-2. Navigate to http://localhost:8080/timeline/federated
-3. Verify Bob's "Morning 10K Run" activity appears with:
- - Federation badge: "🌐 Remote"
- - Bob's avatar and @bob@localhost:8081
- - Map preview (if map image URL is available)
- - Metrics (distance, duration, pace, elevation)
- - Link to view on origin server
-
-### Test 6: Remote Activity Details
-
-**Click on Remote Activity:**
-
-From Alice's federated timeline:
-1. Click on Bob's "Morning 10K Run" activity title
-2. Verify it opens Bob's activity on Instance 2 (http://localhost:8081/activities/{id}) in a new tab
-3. Alternatively, click "View on Origin Server" button
-
-### Test 7: Incoming Activity via ActivityPub
-
-**Test with manual ActivityPub POST:**
-
-```bash
-# Create a test activity
-cat > test-activity.json < Local Storage"
- echo " 4. Copy the value of 'jwt_token'"
- echo " 5. Run this script with: JWT_TOKEN='your-token-here' ./migrate-indoor-flags.sh"
- echo ""
- exit 1
-fi
-
-# Call the migration endpoint
-RESPONSE=$(curl -s -w "\n%{http_code}" \
- -X POST \
- -H "Authorization: Bearer $JWT_TOKEN" \
- http://localhost:8080/api/admin/migrate-indoor-flags)
-
-# Extract HTTP status code
-HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
-BODY=$(echo "$RESPONSE" | sed '$d')
-
-echo "HTTP Status: $HTTP_CODE"
-echo ""
-
-if [ "$HTTP_CODE" = "200" ]; then
- echo "✅ Migration successful!"
- echo ""
- echo "Response:"
- echo "$BODY" | python3 -m json.tool 2>/dev/null || echo "$BODY"
-else
- echo "❌ Migration failed!"
- echo ""
- echo "Response:"
- echo "$BODY"
-fi
-
-echo ""
diff --git a/src/main/java/net/javahippie/fitpub/controller/ActivityController.java b/src/main/java/net/javahippie/fitpub/controller/ActivityController.java
index 9af77f8..26afcdd 100644
--- a/src/main/java/net/javahippie/fitpub/controller/ActivityController.java
+++ b/src/main/java/net/javahippie/fitpub/controller/ActivityController.java
@@ -1,6 +1,8 @@
package net.javahippie.fitpub.controller;
import jakarta.validation.Valid;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.javahippie.fitpub.model.dto.ActivityDTO;
@@ -27,6 +29,9 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -200,25 +205,37 @@ public class ActivityController {
}
/**
- * Lists all activities for the authenticated user with pagination.
+ * Lists all activities for the authenticated user with pagination and optional filters.
*
* @param userDetails the authenticated user
* @param page page number (default: 0)
* @param size page size (default: 10)
+ * @param search optional search text for title/description
+ * @param date optional date filter (formats: dd.mm.yyyy, yyyy-mm-dd, or yyyy)
* @return page of activities
*/
@GetMapping
public ResponseEntity> getUserActivities(
@AuthenticationPrincipal UserDetails userDetails,
@RequestParam(defaultValue = "0") int page,
- @RequestParam(defaultValue = "10") int size
+ @RequestParam(defaultValue = "10") int size,
+ @RequestParam(required = false) String search,
+ @RequestParam(required = false) String date
) {
- log.info("User {} retrieving activities (page: {}, size: {})", userDetails.getUsername(), page, size);
+ log.info("User {} retrieving activities (page: {}, size: {}, search: {}, date: {})",
+ userDetails.getUsername(), page, size, search, date);
UUID userId = getUserId(userDetails);
- org.springframework.data.domain.Page activityPage =
- fitFileService.getUserActivitiesPaginated(userId, page, size);
+ // Use search if filters provided, otherwise use standard method
+ org.springframework.data.domain.Page activityPage;
+ if (search != null ) {
+ activityPage = fitFileService.searchUserActivities(
+ userId, search, page, size
+ );
+ } else {
+ activityPage = fitFileService.getUserActivitiesPaginated(userId, page, size);
+ }
// Convert to DTOs
org.springframework.data.domain.Page dtoPage = activityPage.map(ActivityDTO::fromEntity);
@@ -529,4 +546,62 @@ public class ActivityController {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
+
+ /**
+ * Parse date filter string into date range.
+ * Supports formats: dd.mm.yyyy, yyyy-mm-dd, yyyy
+ *
+ * @param dateStr the date string to parse
+ * @return DateRange with start and end times, or null values if invalid/empty
+ */
+ private DateRange parseDateFilter(String dateStr) {
+ if (dateStr == null || dateStr.trim().isEmpty()) {
+ return new DateRange(null, null);
+ }
+
+ try {
+ // Year only (yyyy)
+ if (dateStr.matches("^\\d{4}$")) {
+ int year = Integer.parseInt(dateStr);
+ LocalDateTime start = LocalDateTime.of(year, 1, 1, 0, 0, 0);
+ LocalDateTime end = LocalDateTime.of(year, 12, 31, 23, 59, 59);
+ return new DateRange(start, end);
+ }
+
+ // dd.mm.yyyy format
+ if (dateStr.matches("^\\d{2}\\.\\d{2}\\.\\d{4}$")) {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
+ LocalDate date = LocalDate.parse(dateStr, formatter);
+ return new DateRange(
+ date.atStartOfDay(),
+ date.atTime(23, 59, 59)
+ );
+ }
+
+ // yyyy-mm-dd format
+ if (dateStr.matches("^\\d{4}-\\d{2}-\\d{2}$")) {
+ LocalDate date = LocalDate.parse(dateStr);
+ return new DateRange(
+ date.atStartOfDay(),
+ date.atTime(23, 59, 59)
+ );
+ }
+
+ log.warn("Invalid date format: {}", dateStr);
+ return new DateRange(null, null);
+ } catch (Exception e) {
+ log.error("Error parsing date filter: {}", dateStr, e);
+ return new DateRange(null, null);
+ }
+ }
+
+ /**
+ * Helper class to hold date range for filtering.
+ */
+ @Getter
+ @AllArgsConstructor
+ private static class DateRange {
+ private final LocalDateTime start;
+ private final LocalDateTime end;
+ }
}
diff --git a/src/main/java/net/javahippie/fitpub/controller/TimelineController.java b/src/main/java/net/javahippie/fitpub/controller/TimelineController.java
index d2820f8..7598446 100644
--- a/src/main/java/net/javahippie/fitpub/controller/TimelineController.java
+++ b/src/main/java/net/javahippie/fitpub/controller/TimelineController.java
@@ -48,25 +48,36 @@ public class TimelineController {
* Get the federated timeline for the authenticated user.
* Shows activities from users they follow.
*
- * GET /api/timeline/federated?page=0&size=20
+ * GET /api/timeline/federated?page=0&size=20&search=morning
*
* @param userDetails the authenticated user details
* @param page page number (default: 0)
* @param size page size (default: 20)
+ * @param search optional search text for title/description
* @return page of timeline activities
*/
@GetMapping("/federated")
public ResponseEntity> getFederatedTimeline(
@AuthenticationPrincipal UserDetails userDetails,
@RequestParam(defaultValue = "0") int page,
- @RequestParam(defaultValue = "20") int size
+ @RequestParam(defaultValue = "20") int size,
+ @RequestParam(required = false) String search
) {
UUID userId = getUserId(userDetails);
- log.debug("Federated timeline request from user: {}", userId);
+ log.debug("Federated timeline request from user: {} (search: {})", userId, search);
// Sort by activity start date descending (latest first)
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "startedAt"));
- Page timeline = timelineService.getFederatedTimeline(userId, pageable);
+
+ // Use search if filters provided, otherwise use standard timeline
+ Page timeline;
+ if (search != null) {
+ timeline = timelineService.searchFederatedTimeline(
+ userId, search, pageable
+ );
+ } else {
+ timeline = timelineService.getFederatedTimeline(userId, pageable);
+ }
return ResponseEntity.ok(timeline);
}
@@ -76,30 +87,41 @@ public class TimelineController {
* Shows all public activities from all users.
* Optionally authenticated - if user is logged in, will show which activities they've liked.
*
- * GET /api/timeline/public?page=0&size=20
+ * GET /api/timeline/public?page=0&size=20&search=morning&date=2024
*
* @param userDetails the authenticated user details (optional)
* @param page page number (default: 0)
* @param size page size (default: 20)
+ * @param search optional search text for title/description
* @return page of timeline activities
*/
@GetMapping("/public")
public ResponseEntity> getPublicTimeline(
@AuthenticationPrincipal(errorOnInvalidType = false) UserDetails userDetails,
@RequestParam(defaultValue = "0") int page,
- @RequestParam(defaultValue = "20") int size
+ @RequestParam(defaultValue = "20") int size,
+ @RequestParam(required = false) String search
) {
UUID userId = null;
if (userDetails != null) {
userId = getUserId(userDetails);
- log.debug("Public timeline request from authenticated user: {}", userId);
+ log.debug("Public timeline request from authenticated user: {} (search: {})", userId, search);
} else {
- log.debug("Public timeline request (unauthenticated)");
+ log.debug("Public timeline request (unauthenticated) (search: {})", search);
}
// Sort by activity start date descending (latest first)
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "startedAt"));
- Page timeline = timelineService.getPublicTimeline(userId, pageable);
+
+ // Use search if filters provided, otherwise use standard timeline
+ Page timeline;
+ if (search != null) {
+ timeline = timelineService.searchPublicTimeline(
+ userId, search, pageable
+ );
+ } else {
+ timeline = timelineService.getPublicTimeline(userId, pageable);
+ }
return ResponseEntity.ok(timeline);
}
@@ -108,25 +130,36 @@ public class TimelineController {
* Get the user's own timeline.
* Shows only activities by the authenticated user.
*
- * GET /api/timeline/user?page=0&size=20
+ * GET /api/timeline/user?page=0&size=20&search=morning
*
* @param userDetails the authenticated user details
* @param page page number (default: 0)
* @param size page size (default: 20)
+ * @param search optional search text for title/description
* @return page of timeline activities
*/
@GetMapping("/user")
public ResponseEntity> getUserTimeline(
@AuthenticationPrincipal UserDetails userDetails,
@RequestParam(defaultValue = "0") int page,
- @RequestParam(defaultValue = "20") int size
+ @RequestParam(defaultValue = "20") int size,
+ @RequestParam(required = false) String search
) {
UUID userId = getUserId(userDetails);
- log.debug("User timeline request from user: {}", userId);
+ log.debug("User timeline request from user: {} (search: {})", userId, search);
// Sort by activity start date descending (latest first)
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "startedAt"));
- Page timeline = timelineService.getUserTimeline(userId, pageable);
+
+ // Use search if filters provided, otherwise use standard timeline
+ Page timeline;
+ if (search != null) {
+ timeline = timelineService.searchUserTimeline(
+ userId, search, pageable
+ );
+ } else {
+ timeline = timelineService.getUserTimeline(userId, pageable);
+ }
return ResponseEntity.ok(timeline);
}
diff --git a/src/main/java/net/javahippie/fitpub/repository/ActivityRepository.java b/src/main/java/net/javahippie/fitpub/repository/ActivityRepository.java
index 7119c0c..354cf0f 100644
--- a/src/main/java/net/javahippie/fitpub/repository/ActivityRepository.java
+++ b/src/main/java/net/javahippie/fitpub/repository/ActivityRepository.java
@@ -331,4 +331,144 @@ public interface ActivityRepository extends JpaRepository {
*/
@Query("SELECT a FROM Activity a WHERE a.sourceFileFormat = :sourceFileFormat AND a.rawActivityFile IS NOT NULL")
List findBySourceFileFormatAndRawActivityFileNotNull(@Param("sourceFileFormat") String sourceFileFormat);
+
+ /**
+ * Search public timeline with text and date filters.
+ * OPTIMIZED: Single query with JOINs and WHERE conditions for search.
+ *
+ * @param visibility the visibility level
+ * @param searchText search text for title/description (use null to skip)
+ * @param currentUserId the current user ID (for liked status, can be null)
+ * @param pageable pagination parameters
+ * @return page of Object[] results (same structure as findPublicTimelineWithStats)
+ */
+ @Query(value = """
+ SELECT
+ a.id, a.user_id, a.activity_type, a.title, a.description, a.started_at, a.ended_at,
+ a.timezone, a.visibility, a.total_distance, a.total_duration_seconds, a.elevation_gain, a.elevation_loss,
+ a.simplified_track, a.track_points_json, a.created_at, a.updated_at,
+ u.username, u.display_name, u.avatar_url,
+ COUNT(DISTINCT l.id) AS likes_count,
+ COUNT(DISTINCT c.id) AS comments_count,
+ CASE WHEN ul.id IS NOT NULL THEN true ELSE false END AS liked_by_current_user
+ FROM activities a
+ INNER JOIN users u ON a.user_id = u.id
+ LEFT JOIN likes l ON a.id = l.activity_id
+ LEFT JOIN comments c ON a.id = c.activity_id AND c.deleted = false
+ LEFT JOIN likes ul ON a.id = ul.activity_id AND ul.user_id = CAST(:currentUserId AS uuid)
+ WHERE a.visibility = :visibility
+ AND (:searchText IS NULL OR (
+ LOWER(a.title) LIKE LOWER(CONCAT('%', :searchText, '%'))
+ OR LOWER(a.description) LIKE LOWER(CONCAT('%', :searchText, '%'))
+ ))
+ GROUP BY a.id, a.user_id, a.activity_type, a.title, a.description, a.started_at, a.ended_at,
+ a.timezone, a.visibility, a.total_distance, a.total_duration_seconds, a.elevation_gain, a.elevation_loss,
+ a.simplified_track, a.track_points_json, a.created_at, a.updated_at,
+ u.username, u.display_name, u.avatar_url, ul.id
+ ORDER BY a.started_at DESC
+ """, nativeQuery = true)
+ Page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/timeline/public.html b/src/main/resources/templates/timeline/public.html
index 3e36c5c..4176050 100644
--- a/src/main/resources/templates/timeline/public.html
+++ b/src/main/resources/templates/timeline/public.html
@@ -34,6 +34,34 @@
Discover public fitness activities from the FitPub community
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/timeline/user.html b/src/main/resources/templates/timeline/user.html
index 04ca544..5798dd5 100644
--- a/src/main/resources/templates/timeline/user.html
+++ b/src/main/resources/templates/timeline/user.html
@@ -34,6 +34,34 @@
Your fitness activities
+
+