OSNBT6AANZB3TF7HAJ35N3Z2EGDU5VQ4LGQORKMA25ACMNV35CQQC * This file implements the server-side API for Skraak using Hono on Cloudflare Workers.* It provides authentication middleware and several API endpoints for retrieving data* from the Neon PostgreSQL database.
* This file implements the server-side API using Hono on Cloudflare Workers.* It handles:* - JWT-based authentication via Kinde identity provider* - API endpoints for retrieving acoustic datasets, locations, clusters* - File and selection management with filtering capabilities* - Species and call type information** The API connects to a Neon PostgreSQL database for data storage and retrieval.* All endpoints except the health check (/api) require authentication.
* Structure of the JWT token payload* Contains standard JWT claims plus any additional custom claims
* Structure of the JWT token payload from Kinde identity provider** @interface JWTPayload* @description Defines the structure of the JWT token payload used for authentication* Contains standard JWT claims (sub, iat, exp, aud, iss) plus any additional custom claims* Used for extracting user information after successful token verification
* No authentication required
** @route GET /api* @authentication None - publicly accessible* @returns {Object} Simple status object with "ok" status* @description Simple endpoint to verify API is running and responding* Can be used for health checks and monitoring
* Returns datasets owned by the authenticated user
** @route GET /api/datasets* @authentication Required* @returns {Object} Response containing:* - data: Array of dataset objects with id, name, description, public status, createdAt, owner* - userId: The authenticated user's ID (for debugging)* @description Returns active datasets limited to 20 items* @example Response:* {* "data": [* { "id": "123", "name": "Bird Sounds 2024", "description": "Costa Rica recordings", "public": true, ... }* ],* "userId": "user_123abc"* }
* Supports pagination with optional page and pageSize parameters
** @route GET /api/locations* @authentication Required* @param {string} datasetId - Required query parameter specifying the dataset to fetch locations from* @param {number} [page=1] - Optional page number for pagination (starts at 1)* @param {number} [pageSize=100] - Optional page size (1-100, defaults to 100)* @returns {Object} Response containing:* - data: Array of location objects with id, name, latitude, longitude, description* - pagination: Object with pagination metadata (currentPage, pageSize, totalPages, totalItems, etc.)* @error 400 - If datasetId is missing or page is invalid* @description Returns active locations for the specified dataset with pagination support
* Includes associated recording patterns through a LEFT JOIN
** @route GET /api/clusters* @authentication Required* @param {string} locationId - Required query parameter specifying the location to fetch clusters from* @returns {Object} Response containing:* - data: Array of cluster objects with recording pattern information* @error 400 - If locationId is missing* @description Returns clusters for the specified location along with their recording patterns* Performs a LEFT JOIN with the cyclicRecordingPattern table to include recording duration data* Results are returned with recording pattern information embedded in each cluster object
* Supports pagination and filtering by:* - Solar night (true/false)* - Civil night (true/false)* - Species ID (for files with selections labeled with a specific species)** Returns file data with metadata, moth metadata, and associated species
* @route GET /api/files* @authentication Required* @param {string} clusterId - Required query parameter specifying the cluster to fetch files from* @param {number} [page=1] - Optional page number for pagination (starts at 1)* @param {number} [pageSize=100] - Optional page size (10-500, defaults to 100)* @param {string} [solarNight] - Optional filter for files recorded during solar night ('true'/'false')* @param {string} [civilNight] - Optional filter for files recorded during civil night ('true'/'false')* @param {string} [speciesId] - Optional filter for files with selections labeled with specific species* @returns {Object} Response containing:* - data: Array of file objects with metadata, mothMetadata, and species information* - pagination: Object with pagination metadata* - filters: Object showing the filters that were applied* @error 400 - If clusterId is missing or page is invalid* @description Returns audio files for the specified cluster with comprehensive metadata:* - Basic file information (name, path, timestamp, duration, etc.)* - File metadata (JSON format)* - Recording device metadata (gain, battery voltage, temperature)* - Species found in each file** When speciesId is provided, only returns files that have at least one* selection labeled with the specified species.
* Returns a nested structure with species and their associated call types* Uses a JOIN approach to efficiently fetch related data in a single query
* @route GET /api/species* @authentication Required* @param {string} datasetId - Required query parameter specifying the dataset to fetch species from* @returns {Object} Response containing:* - data: Array of species objects, each with an array of associated call types* @error 400 - If datasetId is missing* @description Returns species associated with the specified dataset along with their call types* Each species object includes:* - id, label, ebirdCode, description* - callTypes: Array of call type objects with id and label** Uses an efficient JOIN approach to fetch data in a single query to avoid N+1 query problems* Results are transformed to provide a nested structure with call types inside species objects
* Supports pagination and filtering by:* - Solar night (true/false)* - Civil night (true/false)** Returns file data with metadata, moth metadata, and associated species
* @route GET /api/selections* @authentication Required* @param {string} datasetId - Required query parameter specifying the dataset* @param {string} speciesId - Required query parameter specifying the species to filter by* @param {number} [page=1] - Optional page number for pagination (starts at 1)* @param {number} [pageSize=100] - Optional page size (10-500, defaults to 100)* @param {string} [solarNight] - Optional filter for files recorded during solar night ('true'/'false')* @param {string} [civilNight] - Optional filter for files recorded during civil night ('true'/'false')* @returns {Object} Response containing:* - data: Array of file objects with metadata, mothMetadata, and species information* - pagination: Object with pagination metadata* - filters: Object showing the filters that were applied* @error 400 - If datasetId or speciesId is missing or page is invalid* @description Returns files that contain selections labeled with the specified species* Designed for cross-cluster searches within a dataset** Each file object includes:* - Basic file information (name, path, timestamp, duration, etc.)* - File metadata (JSON format)* - Recording device metadata (gain, battery voltage, temperature)* - Species information** Uses efficient query optimization with:* - Parallel Promise.all for metadata queries* - Proper SQL JOINs to avoid N+1 query problems* - Data transformation for optimal client-side consumption
// Skraak Component Flow Diagram at Friday 16th May 2025// To render this diagram, copy the contents and paste into the Mermaid playground:// https://www.mermaidchart.com/play//// This diagram visualizes the component hierarchy and API flow in the React application.graph TDApp["App"] --> KindeProvider["KindeProvider (Auth Context)"]KindeProvider --> PublicContent["PublicContent"]KindeProvider --> AuthorisedContent["AuthorisedContent"]%% Public content branchPublicContent --> PC_Auth{"isAuthenticated?"}PC_Auth -- "No" --> Authenticate["Authenticate"]PC_Auth -- "Yes" --> NoPublicContent["(No Content)"]%% Authorized content branchAuthorisedContent --> AC_Auth{"isAuthenticated?"}AC_Auth -- "No" --> NoAuthorisedContent["(No Content)"]AC_Auth -- "Yes" --> AC_Content["Authorized Content"]AC_Content --> UserProfile["UserProfile"]AC_Content --> AC_View{"activeView?"}%% Datasets viewAC_View -- "datasets" --> Datasets["DatasetsAPI: /api/datasets(fetch datasets)"]%% Dataset viewAC_View -- "dataset" --> Dataset["Dataset(Tabs component)"]Dataset --> DatasetTabs["Tabs"]DatasetTabs --> Locations["LocationsAPI: /api/locations?datasetId={id}(fetch locations)"]DatasetTabs --> Species["SpeciesAPI: /api/species?datasetId={id}(fetch species)"]%% Clusters viewAC_View -- "clusters" --> Clusters["ClustersAPI: /api/clusters?locationId={id}(fetch clusters)"]%% Cluster viewAC_View -- "cluster" --> ClusterComponent["ClusterAPI: /api/files?clusterId={id}(fetch files)API: /api/species?datasetId={id}(fetch species for filter)"]%% Selections viewAC_View -- "selections" --> Selections["SelectionsAPI: /api/selections?datasetId={id}&speciesId={id}API: /api/species?datasetId={id}(fetch species for filter)"]%% User interactionsDatasets -- "onDatasetSelect(id, name)" --> AC_ContentLocations -- "onLocationSelect(id, name)" --> AC_ContentClusters -- "onClusterSelect(id, name)" --> AC_ContentSpecies -- "onSpeciesSelect(id, name)" --> AC_Content%% Authentication flowUserProfile -. "useKindeAuth()" .-> KindeProviderPublicContent -. "useKindeAuth()" .-> KindeProviderAuthorisedContent -. "useKindeAuth()" .-> KindeProviderDatasets -. "useKindeAuth()" .-> KindeProviderLocations -. "useKindeAuth()" .-> KindeProviderClusters -. "useKindeAuth()" .-> KindeProviderClusterComponent -. "useKindeAuth()" .-> KindeProviderSpecies -. "useKindeAuth()" .-> KindeProviderSelections -. "useKindeAuth()" .-> KindeProvider