register_rest_route( $ns, '/upcoming-events', [ [ 'methods' => 'GET', 'callback' => 'f3_rest_get_upcoming_events', 'permission_callback' => '__return_true' ], [ 'methods' => 'POST', 'callback' => 'f3_rest_create_upcoming_event', 'permission_callback' => 'f3_admin_permission' ], ]); register_rest_route( $ns, '/upcoming-events/(?P[a-f0-9\\-]+)', [ [ 'methods' => 'PUT', 'callback' => 'f3_rest_update_upcoming_event', 'permission_callback' => 'f3_admin_permission' ], [ 'methods' => 'DELETE', 'callback' => 'f3_rest_delete_upcoming_event', 'permission_callback' => 'f3_admin_permission' ], ]); function f3_admin_permission( WP_REST_Request $req ): bool { $token = $req->get_header('X-F3-Admin-Token'); if ( ! $token ) return false; $stored = get_option('f3_admin_token_hash'); return $stored && hash_equals( $stored, hash('sha256', $token) ); } function f3_rest_get_upcoming_events(): WP_REST_Response { $cached = get_transient('f3_upcoming_events'); if ( $cached !== false ) return rest_ensure_response( $cached ); $url = trailingslashit( f3_cfg('supabase_url') ) . 'rest/v1/upcoming_events'; $url .= '?is_published=eq.true&order=event_date.asc'; $rows = f3_supabase_get( $url ); set_transient('f3_upcoming_events', $rows, MINUTE_IN_SECONDS * 10); return rest_ensure_response( $rows ); } function f3_rest_create_upcoming_event( WP_REST_Request $req ): WP_REST_Response { delete_transient('f3_upcoming_events'); $body = $req->get_json_params(); $url = trailingslashit( f3_cfg('supabase_url') ) . 'rest/v1/upcoming_events'; $key = defined('F3_SUPABASE_SERVICE_KEY') ? F3_SUPABASE_SERVICE_KEY : ''; $resp = wp_remote_post( $url, [ 'headers' => [ 'apikey' => $key, 'Authorization' => 'Bearer ' . $key, 'Content-Type' => 'application/json', 'Prefer' => 'return=representation' ], 'body' => wp_json_encode( $body ), 'timeout' => 15, ]); $code = wp_remote_retrieve_response_code( $resp ); $data = json_decode( wp_remote_retrieve_body( $resp ), true ); return new WP_REST_Response( $data, ( $code >= 200 && $code < 300 ) ? 201 : $code ); } function f3_rest_update_upcoming_event( WP_REST_Request $req ): WP_REST_Response { delete_transient('f3_upcoming_events'); $id = $req->get_param('id'); $body = $req->get_json_params(); $body['updated_at'] = date('c'); $url = trailingslashit( f3_cfg('supabase_url') ) . 'rest/v1/upcoming_events?id=eq.' . urlencode($id); $key = defined('F3_SUPABASE_SERVICE_KEY') ? F3_SUPABASE_SERVICE_KEY : ''; $resp = wp_remote_request( $url, [ 'method' => 'PATCH', 'headers' => [ 'apikey' => $key, 'Authorization' => 'Bearer ' . $key, 'Content-Type' => 'application/json', 'Prefer' => 'return=representation' ], 'body' => wp_json_encode( $body ), 'timeout' => 15, ]); $code = wp_remote_retrieve_response_code( $resp ); $data = json_decode( wp_remote_retrieve_body( $resp ), true ); return new WP_REST_Response( $data, $code ); } function f3_rest_delete_upcoming_event( WP_REST_Request $req ): WP_REST_Response { delete_transient('f3_upcoming_events'); $id = $req->get_param('id'); $url = trailingslashit( f3_cfg('supabase_url') ) . 'rest/v1/upcoming_events?id=eq.' . urlencode($id); $key = defined('F3_SUPABASE_SERVICE_KEY') ? F3_SUPABASE_SERVICE_KEY : ''; wp_remote_request( $url, [ 'method' => 'DELETE', 'headers' => [ 'apikey' => $key, 'Authorization' => 'Bearer ' . $key ], 'timeout' => 15, ]); return new WP_REST_Response( ['deleted' => true], 200 ); }