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:
User Management: Handle user accounts and admin access.
Location Management: Manage user-selected locations.
Criteria Management: Handle pre-defined, custom, and “Hot Fishing” criteria sets.
Weather Data: Fetch and cache weather data from Weatherbit.
Scoring: Calculate scores for time periods.
Miscellaneous: Support sharing and info pages.
APIs
1. User Management
POST /users/login
POST /users/login
Purpose: Authenticate a user and return a JWT token.
Request:
{ "email": "user@example.com", "password": "securepassword" }
Response (200 OK):
{ "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
POST /admin/login
Purpose: Authenticate an admin and return an admin JWT token.
Request:
{ "username": "admin", "password": "adminpassword" }
Response (200 OK):
{ "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
GET /admin/users
Purpose: List all user accounts (admin only).
Headers:
Authorization: Bearer <admin_token>
Response (200 OK):
[ { "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
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
POST /locations
Purpose: Save a user’s selected location and fetch initial weather data.
Headers:
Authorization: Bearer <user_token>
Request:
{ "name": "Miami, FL", "lat": 25.7617, "lon": -80.1918 }
Response (201 Created):
{ "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
GET /locations
Purpose: List user’s saved locations.
Headers:
Authorization: Bearer <user_token>
Response (200 OK):
[ { "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
GET /criteria
Purpose: List all available criteria sets (pre-defined and user’s custom/“Hot Fishing”).
Headers:
Authorization: Bearer <user_token>
Response (200 OK):
[ { "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
POST /criteria/custom
Purpose: Create a custom criteria set.
Headers:
Authorization: Bearer <user_token>
Request:
{ "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):
{ "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
POST /criteria/hot-fishing
Purpose: Save current weather conditions as a “Hot Fishing” criteria set.
Headers:
Authorization: Bearer <user_token>
Request:
{ "location_id": "uuid", "name": "3/2/25 15:40 Hot Fishing Lake X" }
Response (201 Created):
{ "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
PATCH /criteria/custom/:criteria_id
Purpose: Update a custom criteria set.
Headers:
Authorization: Bearer <user_token>
Request:
{ "name": "Updated Criteria", "variables": [...] }
Response (200 OK):
{ "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
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
GET /weather/:location_id
Purpose: Fetch and cache weather data for a location.
Headers:
Authorization: Bearer <user_token>
Response (200 OK):
{ "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
POST /scores
Purpose: Calculate scores for a location and criteria set across time periods (current + 7 days).
Headers:
Authorization: Bearer <user_token>
Request:
{ "location_id": "uuid", "criteria_id": "uuid" }
Response (200 OK):
[ { "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
(useGET /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
GET /scores/:period
Purpose: Get detailed score breakdown for a specific period.
Headers:
Authorization: Bearer <user_token>
Query Params:
location_id
: UUIDcriteria_id
: UUID
Response (200 OK):
[ { "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
GET /share
Purpose: Generate data for sharing the scoring screen.
Headers:
Authorization: Bearer <user_token>
Query Params:
location_id
: UUIDcriteria_id
: UUID
Response (200 OK):
{ "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
GET /info
Purpose: Return app instructions, support email, and disclaimer.
Response (200 OK):
{ "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):
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
withfetched_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
Set up backend framework and database.
Implement Weatherbit API integration with caching.
Build
/weather/:location_id
and/scores
first, as they’re core to functionality.Test corner cases (e.g., missing data, auto red conditions).
Document APIs in OpenAPI/Swagger for frontend team.
Ask if you hit any roadblocks—this design keeps it simple but covers all bases!
Last updated