> For the complete documentation index, see [llms.txt](https://codefied.gitbook.io/fishcaster/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://codefied.gitbook.io/fishcaster/backend-api-specification.md).

# Backend API Specification

This document outlines the **RESTful APIs** for the Fishing Weather Mobile App backend. The APIs are simple, focused, and cover all functional requirements from the provided scope. They handle **location selection**, **weather criteria management**, **weather data fetching**, **scoring**, and **user/admin tasks**. Each API includes its purpose, HTTP method, request/response schemas, and corner case handling.

## Assumptions

* **Tech Stack**: RESTful backend (e.g., Node.js/Express, FastAPI) with a database (e.g., PostgreSQL, MongoDB).
* **Authentication**: APIs requiring user access use a JWT token in the `Authorization` header. Admin APIs require a separate admin token.
* **Weatherbit APIs**: Used for current, hourly historical, hourly forecast, and daily forecast data. API keys are stored securely.
* **Caching**: Weather data cached in the database with a `fetched_at` timestamp to enforce 1-hour minimum between calls.
* **Error Handling**: All APIs return standard error responses (e.g., `{ "error": "Invalid location" }`) with appropriate HTTP status codes.

## API Overview

The APIs are grouped by functionality:

1. **User Management**: Handle user accounts and admin access.
2. **Location Management**: Manage user-selected locations.
3. **Criteria Management**: Handle pre-defined, custom, and “Hot Fishing” criteria sets.
4. **Weather Data**: Fetch and cache weather data from Weatherbit.
5. **Scoring**: Calculate scores for time periods.
6. **Miscellaneous**: Support sharing and info pages.

## APIs

### 1. User Management

#### `POST /users/login`

**Purpose**: Authenticate a user and return a JWT token.

* **Request**:

  ```json
  {
    "email": "user@example.com",
    "password": "securepassword"
  }
  ```
* **Response** (200 OK):

  ```json
  {
    "user_id": "uuid",
    "token": "jwt_token"
  }
  ```
* **Corner Cases**:
  * Invalid credentials: Return 401 Unauthorized with `{ "error": "Invalid email or password" }`.
  * Missing fields: Return 400 Bad Request with `{ "error": "Email and password required" }`.

#### `POST /admin/login`

**Purpose**: Authenticate an admin and return an admin JWT token.

* **Request**:

  ```json
  {
    "username": "admin",
    "password": "adminpassword"
  }
  ```
* **Response** (200 OK):

  ```json
  {
    "admin_id": "uuid",
    "token": "jwt_token"
  }
  ```
* **Corner Cases**:
  * Invalid credentials: Return 401 Unauthorized with `{ "error": "Invalid username or password" }`.
  * Missing fields: Return 400 Bad Request with `{ "error": "Username and password required" }`.

#### `GET /admin/users`

**Purpose**: List all user accounts (admin only).

* **Headers**: `Authorization: Bearer <admin_token>`
* **Response** (200 OK):

  ```json
  [
    {
      "user_id": "uuid",
      "email": "user@example.com",
      "created_at": "2025-04-24T12:00:00Z"
    }
  ]
  ```
* **Corner Cases**:
  * Invalid token: Return 401 Unauthorized with `{ "error": "Invalid token" }`.
  * No users: Return empty array `[]`.

#### `DELETE /admin/users/:user_id`

**Purpose**: Delete a user account and their data (admin only).

* **Headers**: `Authorization: Bearer <admin_token>`
* **Response** (204 No Content): No body.
* **Corner Cases**:
  * User not found: Return 404 Not Found with `{ "error": "User not found" }`.
  * Invalid token: Return 401 Unauthorized with `{ "error": "Invalid token" }`.
  * Cascade deletion: Ensure user’s criteria and cached weather data are also deleted.

***

### 2. Location Management

#### `POST /locations`

**Purpose**: Save a user’s selected location and fetch initial weather data.

* **Headers**: `Authorization: Bearer <user_token>`
* **Request**:

  ```json
  {
    "name": "Miami, FL",
    "lat": 25.7617,
    "lon": -80.1918
  }
  ```
* **Response** (201 Created):

  ```json
  {
    "location_id": "uuid",
    "name": "Miami, FL",
    "lat": 25.7617,
    "lon": -80.1918
  }
  ```
* **Backend Logic**:
  * Validate lat/long format.
  * If only `name` is provided, use a geocoding service (e.g., Weatherbit or OpenStreetMap) to convert to lat/long.
  * Trigger weather data fetch (see `GET /weather`) and cache results.
* **Corner Cases**:
  * Invalid location: Return 400 Bad Request with `{ "error": "Invalid location or coordinates" }`.
  * Geocoding failure: Return 400 Bad Request with `{ "error": "Could not resolve location" }`.
  * API rate limit: Use cached data if available; otherwise, queue request and return 429 Too Many Requests.

#### `GET /locations`

**Purpose**: List user’s saved locations.

* **Headers**: `Authorization: Bearer <user_token>`
* **Response** (200 OK):

  ```json
  [
    {
      "location_id": "uuid",
      "name": "Miami, FL",
      "lat": 25.7617,
      "lon": -80.1918
    }
  ]
  ```
* **Corner Cases**:
  * No locations: Return empty array `[]`.
  * Invalid token: Return 401 Unauthorized with `{ "error": "Invalid token" }`.

***

### 3. Criteria Management

#### `GET /criteria`

**Purpose**: List all available criteria sets (pre-defined and user’s custom/“Hot Fishing”).

* **Headers**: `Authorization: Bearer <user_token>`
* **Response** (200 OK):

  ```json
  [
    {
      "criteria_id": "uuid",
      "name": "Best Fishing",
      "type": "predefined",
      "variables": [
        {
          "name": "temperature",
          "range": [60, 80],
          "points": 2,
          "auto_red": false
        }
      ]
    },
    {
      "criteria_id": "uuid",
      "name": "3/2/25 15:40 Hot Fishing",
      "type": "hot_fishing",
      "variables": [...]
    }
  ]
  ```
* **Corner Cases**:
  * No custom criteria: Return only pre-defined criteria.
  * Missing pre-defined criteria: Seed database with “Best Fishing” and “Comfort and Safety” if not present.

#### `POST /criteria/custom`

**Purpose**: Create a custom criteria set.

* **Headers**: `Authorization: Bearer <user_token>`
* **Request**:

  ```json
  {
    "name": "My Criteria",
    "variables": [
      {
        "name": "temperature",
        "range": [60, 80],
        "points": 2,
        "auto_red": false
      },
      {
        "name": "wind_speed",
        "range": [5, 15],
        "points": 1,
        "auto_red": false
      }
    ]
  }
  ```
* **Response** (201 Created):

  ```json
  {
    "criteria_id": "uuid",
    "name": "My Criteria",
    "type": "custom",
    "variables": [...]
  }
  ```
* **Backend Logic**:
  * Validate variables (e.g., range format, points 1–3, at least one variable).
  * Store in database linked to user.
* **Corner Cases**:
  * Duplicate name: Return 400 Bad Request with `{ "error": "Criteria name already exists" }`.
  * Invalid variables: Return 400 Bad Request with `{ "error": "Invalid variable format" }`.
  * Empty variables: Return 400 Bad Request with `{ "error": "At least one variable required" }`.

#### `POST /criteria/hot-fishing`

**Purpose**: Save current weather conditions as a “Hot Fishing” criteria set.

* **Headers**: `Authorization: Bearer <user_token>`
* **Request**:

  ```json
  {
    "location_id": "uuid",
    "name": "3/2/25 15:40 Hot Fishing Lake X"
  }
  ```
* **Response** (201 Created):

  ```json
  {
    "criteria_id": "uuid",
    "name": "3/2/25 15:40 Hot Fishing Lake X",
    "type": "hot_fishing",
    "variables": [
      {
        "name": "temperature",
        "range": [50, 70],
        "points": 1,
        "auto_red": false
      }
    ]
  }
  ```
* **Backend Logic**:
  * Fetch current weather data for `location_id` (use cache if <1 hour old; otherwise, call Weatherbit Current Weather API).
  * Calculate ranges (e.g., ±10°F for temperature, ±40° for wind direction) per the “Hot Fishing” table.
  * Exclude variables not met at the time (e.g., no dropping pressure → exclude from criteria).
  * Use server time for default name if client time is unreliable.
* **Corner Cases**:
  * Stale cache: Fetch fresh data if >1 hour old; if API fails, use last cached data with warning.
  * Missing weather data: Exclude affected variables and log issue.
  * Wind direction edge case: Handle 0°/360° ranges by normalizing (e.g., add 360° if needed).
  * Duplicate name: Append counter (e.g., “#2”) to ensure uniqueness.

#### `PATCH /criteria/custom/:criteria_id`

**Purpose**: Update a custom criteria set.

* **Headers**: `Authorization: Bearer <user_token>`
* **Request**:

  ```json
  {
    "name": "Updated Criteria",
    "variables": [...]
  }
  ```
* **Response** (200 OK):

  ```json
  {
    "criteria_id": "uuid",
    "name": "Updated Criteria",
    "type": "custom",
    "variables": [...]
  }
  ```
* **Corner Cases**:
  * Criteria not found: Return 404 Not Found with `{ "error": "Criteria not found" }`.
  * Not custom criteria: Return 403 Forbidden with `{ "error": "Cannot edit predefined or hot_fishing criteria" }`.
  * Invalid updates: Validate as in `POST /criteria/custom`.

#### `DELETE /criteria/:criteria_id`

**Purpose**: Delete a custom or “Hot Fishing” criteria set.

* **Headers**: `Authorization: Bearer <user_token>`
* **Response** (204 No Content): No body.
* **Corner Cases**:
  * Criteria not found: Return 404 Not Found with `{ "error": "Criteria not found" }`.
  * Pre-defined criteria: Return 403 Forbidden with `{ "error": "Cannot delete predefined criteria" }`.
  * Not owned by user: Return 403 Forbidden with `{ "error": "Unauthorized" }`.

***

### 4. Weather Data

#### `GET /weather/:location_id`

**Purpose**: Fetch and cache weather data for a location.

* **Headers**: `Authorization: Bearer <user_token>`
* **Response** (200 OK):

  ```json
  {
    "location_id": "uuid",
    "current": {
      "temp": 75, // °F
      "pressure": 30.1, // inHg
      "wind_speed": 10, // mph
      ...
    },
    "hourly_forecast": [
      {
        "time": "2025-04-25T08:00:00Z",
        "temp":  تعمیر

  }
  ```
* **Backend Logic**:
  * Check cache for data <1 hour old.
  * If stale or missing, fetch from Weatherbit APIs (Current, Hourly Historical, Hourly Forecast, Daily Forecast).
  * Convert units (Celsius → °F, millibars → inHg, m/s → mph, UTC → local time).
  * Cache data with `fetched_at` timestamp.
* **Corner Cases**:
  * API failure: Use cached data (even if >1 hour old) and log error.
  * Missing data: Exclude affected variables from scoring.
  * Large forecast data: Cache only 08:00, 13:00, 18:00 for 7 days.
  * Timezone error: Use location’s timezone or fallback to UTC.

***

### 5. Scoring

#### `POST /scores`

**Purpose**: Calculate scores for a location and criteria set across time periods (current + 7 days).

* **Headers**: `Authorization: Bearer <user_token>`
* **Request**:

  ```json
  {
    "location_id": "uuid",
    "criteria_id": "uuid"
  }
  ```
* **Response** (200 OK):

  ```json
  [
    {
      "period": "current",
      "score": 75,
      "color": "green",
      "safety_flag": false
    },
    {
      "period": "2025-04-25_morning",
      "score": 60,
      "color": "yellow",
      "safety_flag": true
    }
  ]
  ```
* **Backend Logic**:
  * Fetch weather data for `location_id` (use `GET /weather/:location_id` logic).
  * Compare against `criteria_id` variables.
  * Calculate score: `(points_earned / total_possible_points) * 100`.
  * Apply color: Green (≥70%), Yellow (30–69%), Red (<30% or auto red).
  * Flag safety conditions (e.g., thunderstorms, high winds) with “!”.
* **Corner Cases**:
  * Missing weather data: Exclude variable and adjust denominator.
  * Auto red override: Set red and safety flag if safety condition met.
  * Zero denominator: Return 0% score.
  * Historical data gap: Skip trend variables.
  * Rounding: Round score to nearest integer.

#### `GET /scores/:period`

**Purpose**: Get detailed score breakdown for a specific period.

* **Headers**: `Authorization: Bearer <user_token>`
* **Query Params**:
  * `location_id`: UUID
  * `criteria_id`: UUID
* **Response** (200 OK):

  ```json
  [
    {
      "variable": "temperature",
      "criteria": "60–80°F",
      "actual": "75°F",
      "match": true,
      "safety_flag": false
    },
    {
      "variable": "thunderstorm",
      "criteria": "No thunderstorms",
      "actual": "Thunderstorm",
      "match": false,
      "safety_flag": true
    }
  ]
  ```
* **Corner Cases**:
  * Missing data: Mark as “Data unavailable” and non-matching.
  * Criteria mismatch: Use criteria from original score.
  * Empty table: Return message if no valid variables.

***

### 6. Miscellaneous

#### `GET /share`

**Purpose**: Generate data for sharing the scoring screen.

* **Headers**: `Authorization: Bearer <user_token>`
* **Query Params**:
  * `location_id`: UUID
  * `criteria_id`: UUID
* **Response** (200 OK):

  ```json
  {
    "scores": [
      {
        "period": "current",
        "score": 75,
        "color": "green"
      }
    ],
    "template": "Check out my fishing forecast! Download the app at [TBD website]."
  }
  ```
* **Corner Cases**:
  * No scores: Return 400 Bad Request with `{ "error": "No scores available" }`.
  * Missing website: Use placeholder text and log for admin.

#### `GET /info`

**Purpose**: Return app instructions, support email, and disclaimer.

* **Response** (200 OK):

  ```json
  {
    "instructions": "How to use the app...",
    "support_email": "support@example.com",
    "disclaimer": "Use at your own risk..."
  }
  ```
* **Corner Cases**:
  * Missing content: Return default message and notify admin.
  * Large content: Ensure response is compact.

***

## Implementation Notes

* **Database Schema** (PostgreSQL example):

  ```sql
  CREATE TABLE users (user_id UUID PRIMARY KEY, email TEXT, password_hash TEXT);
  CREATE TABLE locations (location_id UUID PRIMARY KEY, user_id UUID, name TEXT, lat FLOAT, lon FLOAT);
  CREATE TABLE criteria (criteria_id UUID PRIMARY KEY, user_id UUID, name TEXT, type TEXT, variables JSONB);
  CREATE TABLE weather_cache (cache_id UUID PRIMARY KEY, location_id UUID, data_type TEXT, data JSONB, fetched_at TIMESTAMP);
  CREATE TABLE admins (admin_id UUID PRIMARY KEY, username TEXT, password_hash TEXT);
  ```
* **Caching**: Store weather data in `weather_cache` with `fetched_at` to enforce 1-hour rule.
* **Security**:
  * Sanitize inputs to prevent SQL injection/XSS.
  * Use HTTPS and JWT for authentication.
* **Error Handling**:
  * Standard response: `{ "error": "message" }` with 400, 401, 403, 404, or 500 status.
  * Log all errors for debugging.
* **Performance**:
  * Index database fields (e.g., `user_id`, `location_id`).
  * Throttle API calls to Weatherbit to avoid rate limits.
* **Testing**:
  * Unit test scoring logic, caching, and API responses.
  * Simulate Weatherbit failures and invalid inputs.

## Next Steps

1. Set up backend framework and database.
2. Implement Weatherbit API integration with caching.
3. Build `/weather/:location_id` and `/scores` first, as they’re core to functionality.
4. Test corner cases (e.g., missing data, auto red conditions).
5. Document APIs in OpenAPI/Swagger for frontend team.

Ask if you hit any roadblocks—this design keeps it simple but covers all bases!
