load->model('user_model'); // Check if users logged in if ($this->user_model->validate_session() == 0) { // user is not logged in redirect('user/login'); } $this->load->model('api_model'); $data['api_keys'] = $this->api_model->keys(); $data['page_title'] = "API"; $this->load->view('interface_assets/header', $data); $this->load->view('api/help'); $this->load->view('interface_assets/footer'); } function edit($key) { $this->load->model('user_model'); // Check if users logged in if ($this->user_model->validate_session() == 0) { // user is not logged in redirect('user/login'); } $this->load->model('api_model'); $this->load->helper(array('form', 'url')); $this->load->library('form_validation'); $this->form_validation->set_rules('api_desc', 'API Description', 'required'); $this->form_validation->set_rules('api_key', 'API Key is required do not change this field', 'required'); $data['api_info'] = $this->api_model->key_description($key); if ($this->form_validation->run() == FALSE) { $data['page_title'] = "Edit API Description"; $this->load->view('interface_assets/header', $data); $this->load->view('api/description'); $this->load->view('interface_assets/footer'); } else { // Success! $this->api_model->update_key_description($this->input->post('api_key'), $this->input->post('api_desc')); $this->session->set_flashdata('notice', 'API Key ' . $this->input->post('api_key') . " description has been updated."); redirect('api/help'); } } function generate($rights) { $this->load->model('user_model'); // Check if users logged in if ($this->user_model->validate_session() == 0) { // user is not logged in redirect('user/login'); } $this->load->model('api_model'); $data['api_keys'] = $this->api_model->generate_key($rights); redirect('api/help'); } function delete($key) { $this->load->model('user_model'); // Check if users logged in if ($this->user_model->validate_session() == 0) { // user is not logged in redirect('user/login'); } $this->load->model('api_model'); $this->api_model->delete_key($key); $this->session->set_flashdata('notice', 'API Key ' . $key . " has been deleted"); redirect('api/help'); } // Example of authing function auth($key) { $this->load->model('api_model'); header("Content-type: text/xml"); if ($this->api_model->access($key) == "No Key Found" || $this->api_model->access($key) == "Key Disabled") { echo ""; echo "Key Invalid - either not found or disabled"; echo ""; } else { echo ""; echo "Valid"; echo "" . $this->api_model->access($key) . ""; echo ""; } } function check_auth($key) { $this->load->model('api_model'); header("Content-type: text/xml"); if ($this->api_model->access($key) == "No Key Found" || $this->api_model->access($key) == "Key Disabled") { // set the content type as json header("Content-type: application/json"); // set the http response code to 401 http_response_code(401); // return the json with the status as failed echo json_encode(['status' => 'failed', 'reason' => "missing or invalid api key"]); } else { // set the content type as json header("Content-type: application/json"); // set the http response code to 200 http_response_code(200); // return the json echo json_encode(['status' => 'valid', 'rights' => $this->api_model->access($key)]); } } function station_info($key) { $this->load->model('api_model'); $this->load->model('stations'); header("Content-type: application/json"); if (substr($this->api_model->access($key), 0, 1) == 'r') { /* Checkpermission for _r_eading */ $this->api_model->update_last_used($key); $userid = $this->api_model->key_userid($key); $station_ids = array(); $stations = $this->stations->all_of_user($userid); foreach ($stations->result() as $row) { $result['station_id'] = $row->station_id; $result['station_profile_name'] = $row->station_profile_name; $result['station_gridsquare'] = $row->station_gridsquare; $result['station_callsign'] = $row->station_callsign;; $result['station_active'] = $row->station_active; array_push($station_ids, $result); } echo json_encode($station_ids); } else { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "missing or invalid api key"]); } } /** * Get station logbooks that have public slugs for the API key owner. * GET /api/logbook_public_slugs/{key} */ function logbook_public_slugs($key) { header('Content-type: application/json'); $this->load->model('api_model'); $this->load->model('logbooks_model'); if (!$key || $this->api_model->authorize($key) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => 'missing or invalid api key']); return; } $this->api_model->update_last_used($key); $user_id = $this->api_model->key_userid($key); $query = $this->logbooks_model->public_slugs_by_user($user_id); $logbooks = array(); foreach ($query->result() as $row) { $logbooks[] = array( 'logbook_id' => (int)$row->logbook_id, 'logbook_name' => $row->logbook_name, 'public_slug' => $row->public_slug, ); } http_response_code(200); echo json_encode(array( 'status' => 'success', 'count' => count($logbooks), 'logbooks' => $logbooks, ), JSON_PRETTY_PRINT); } /** * Get all accessible station logbooks (owned + shared) that have public slugs for the API key owner. * GET /api/logbook_public_slugs_accessible/{key} */ function logbook_public_slugs_accessible($key) { header('Content-type: application/json'); $this->load->model('api_model'); $this->load->model('logbooks_model'); if (!$key || $this->api_model->authorize($key) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => 'missing or invalid api key']); return; } $this->api_model->update_last_used($key); $user_id = $this->api_model->key_userid($key); $query = $this->logbooks_model->public_slugs_accessible_by_user($user_id); $logbooks = array(); foreach ($query->result() as $row) { $logbooks[] = array( 'logbook_id' => (int)$row->logbook_id, 'logbook_name' => $row->logbook_name, 'public_slug' => $row->public_slug, 'access_level' => $row->access_level, ); } http_response_code(200); echo json_encode(array( 'status' => 'success', 'count' => count($logbooks), 'logbooks' => $logbooks, ), JSON_PRETTY_PRINT); } /* * * Function: QSO * Task: allows passing of ADIF data to Cloudlog */ function qso() { header('Content-type: application/json'); $this->load->model('api_model'); $this->load->model('stations'); $return_msg = array(); $return_count = 0; // Decode JSON and store $obj = json_decode(file_get_contents("php://input"), true); if ($obj === NULL) { // Decoding not valid try simple www-x-form-urlencoded $objTmp = file_get_contents("php://input"); parse_str($objTmp, $obj); if ($obj === NULL) { echo json_encode(['status' => 'failed', 'reason' => "wrong JSON"]); die(); } } if (!isset($obj['key']) || $this->api_model->authorize($obj['key']) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "missing api key"]); die(); } $userid = $this->api_model->key_userid($obj['key']); if (!isset($obj['station_profile_id']) || $this->stations->check_station_against_user($obj['station_profile_id'], $userid) == false) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "station id does not belong to the API key owner."]); die(); } if ($obj['type'] == "adif" && $obj['string'] != "") { // Load the logbook model for adding QSO records $this->load->model('logbook_model'); // Load ADIF Parser $this->load->library('adif_parser'); // Feed in the ADIF string $this->adif_parser->feed($obj['string']); // Create QSO Record while ($record = $this->adif_parser->get_record()) { if (count($record) == 0) { break; }; if (isset($obj['station_profile_id'])) { if (isset($record['station_callsign']) && $this->stations->check_station_against_callsign($obj['station_profile_id'], $record['station_callsign']) == false) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "station callsign does not match station callsign in station profile."]); die(); } if (!(isset($record['call'])) || (trim($record['call']) == '')) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "QSO Call is empty."]); die(); } $this->api_model->update_last_used($obj['key']); $msg = $this->logbook_model->import($record, $obj['station_profile_id'], NULL, NULL, NULL, NULL, NULL, NULL, false, false, true); if ($msg == "") { $return_count++; } else { $return_msg[] = $msg; } } }; http_response_code(201); echo json_encode(['status' => 'created', 'type' => $obj['type'], 'string' => $obj['string'], 'imported_count' => $return_count, 'messages' => $return_msg]); } } // API function to check if a callsign is in the logbook already function logbook_check_callsign() { header('Content-type: application/json'); $this->load->model('api_model'); // Decode JSON and store $obj = json_decode(file_get_contents("php://input"), true); if ($obj === NULL) { echo json_encode(['status' => 'failed', 'reason' => "wrong JSON"]); return; } if (!isset($obj['key']) || $this->api_model->authorize($obj['key']) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "missing api key"]); return; } if (!isset($obj['logbook_public_slug']) || !isset($obj['callsign'])) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "missing fields"]); return; } if ($obj['logbook_public_slug'] != "" && $obj['callsign'] != "") { $logbook_slug = $obj['logbook_public_slug']; $callsign = $obj['callsign']; // If $obj['band'] exists if (isset($obj['band'])) { $band = $obj['band']; } else { $band = null; } $this->load->model('logbooks_model'); if ($this->logbooks_model->public_slug_exists($logbook_slug)) { $logbook_id = $this->logbooks_model->public_slug_exists_logbook_id($logbook_slug); if ($logbook_id != false) { // Get associated station locations for mysql queries $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($logbook_id); if (!$logbooks_locations_array) { // Logbook not found http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => "Empty Logbook"]); die(); } } else { // Logbook not found http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => $logbook_slug . " has no associated station locations"]); die(); } // Search Logbook for callsign $this->load->model('logbook_model'); $result = $this->logbook_model->check_if_callsign_worked_in_logbook($callsign, $logbooks_locations_array, $band); http_response_code(201); if ($result > 0) { echo json_encode(['callsign' => $callsign, 'result' => 'Found']); } else { echo json_encode(['callsign' => $callsign, 'result' => 'Not Found']); } } else { // Logbook not found http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => "logbook not found"]); die(); } } } // API function to check if a grid is in the logbook already function logbook_check_grid() { header('Content-type: application/json'); $this->load->model('api_model'); // Decode JSON and store $obj = json_decode(file_get_contents("php://input"), true); if ($obj === NULL) { echo json_encode(['status' => 'failed', 'reason' => "wrong JSON"]); } if (!isset($obj['key']) || $this->api_model->authorize($obj['key']) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "missing api key"]); } if (!isset($obj['logbook_public_slug']) || !isset($obj['grid'])) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "missing fields"]); return; } if ($obj['logbook_public_slug'] != "" && $obj['grid'] != "") { $logbook_slug = $obj['logbook_public_slug']; $grid = $obj['grid']; // If $obj['band'] exists if (isset($obj['band'])) { $band = $obj['band']; } else { $band = null; } $this->load->model('logbooks_model'); if ($this->logbooks_model->public_slug_exists($logbook_slug)) { $logbook_id = $this->logbooks_model->public_slug_exists_logbook_id($logbook_slug); if ($logbook_id != false) { // Get associated station locations for mysql queries $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($logbook_id); if (!$logbooks_locations_array) { // Logbook not found http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => "Empty Logbook"]); die(); } } else { // Logbook not found http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => $logbook_slug . " has no associated station locations"]); die(); } // Search Logbook for callsign $this->load->model('logbook_model'); $result = $this->logbook_model->check_if_grid_worked_in_logbook($grid, $logbooks_locations_array, $band); http_response_code(201); if ($result > 0) { echo json_encode(['gridsquare' => strtoupper($grid), 'result' => 'Found']); } else { echo json_encode(['gridsquare' => strtoupper($grid), 'result' => 'Not Found']); } } else { // Logbook not found http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => "logbook not found"]); die(); } } } /** * Check if a country has been worked before and confirmed in various ways * * This API endpoint checks if a specific country (derived from callsign) has been * worked before in a given logbook, and whether it has been confirmed via different * confirmation methods (QSL, LoTW, eQSL, QRZ). * * @api POST /api/logbook_check_country * @header Content-Type application/json * * @param string key Required. API authentication key * @param string logbook_public_slug Required. Public slug identifier for the logbook * @param string callsign Required. Callsign to lookup country for * @param string type Optional. Type of contact ("sat" for satellite, empty for regular) * @param string band Optional. Amateur radio band (required for non-satellite contacts) * @param string mode Optional. Amateur radio mode (required for non-satellite contacts) * * @return json Returns JSON object with: * - workedBefore: boolean indicating if country was worked before * - confirmed: object with confirmation status for qsl, lotw, eqsl, qrz * * @throws 401 Unauthorized - Missing or invalid API key, missing required fields * @throws 404 Not Found - Logbook not found or empty logbook * @throws 400 Bad Request - Invalid JSON format * * @example * Request: * { * "key": "your-api-key", * "logbook_public_slug": "my-logbook", * "callsign": "W1AW", * "band": "20M", * "mode": "SSB" * } * * Response: * { * "workedBefore": true, * "confirmed": { * "qsl": true, * "lotw": false, * "eqsl": false, * "qrz": false * } * } */ function logbook_check_country() { header('Content-type: application/json'); $this->load->model('api_model'); // Decode JSON and store $obj = json_decode(file_get_contents("php://input"), true); if ($obj === NULL) { echo json_encode(['status' => 'failed', 'reason' => "wrong JSON"]); return; } if (!isset($obj['key']) || $this->api_model->authorize($obj['key']) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "missing api key"]); return; } if (!isset($obj['logbook_public_slug']) || !isset($obj['callsign'])) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "missing fields"]); return; } // Load models $this->load->model('logbook_model'); $this->load->model('logbooks_model'); $date = date("Y-m-d"); $callsign = $obj['callsign']; $logbook_slug = $obj['logbook_public_slug']; $type = isset($obj['type']) ? $obj['type'] : ''; $band = isset($obj['band']) ? $obj['band'] : ''; $mode = isset($obj['mode']) ? $obj['mode'] : ''; $callsign_dxcc_lookup = $this->logbook_model->dxcc_lookup($callsign, $date); $country = $callsign_dxcc_lookup['entity']; $return = [ "workedBefore" => false, "confirmed" => [ "qsl" => false, "lotw" => false, "eqsl" => false, "qrz" => false ] ]; if ($this->logbooks_model->public_slug_exists($logbook_slug)) { $logbook_id = $this->logbooks_model->public_slug_exists_logbook_id($logbook_slug); if ($logbook_id != false) { // Get associated station locations for mysql queries $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($logbook_id); if (!$logbooks_locations_array) { // Logbook not found http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => "Empty Logbook"]); die(); } } else { // Logbook not found http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => $logbook_slug . " has no associated station locations"]); die(); } if (!empty($logbooks_locations_array)) { if ($type == "sat") { $this->db->where('COL_PROP_MODE', 'SAT'); } else { $this->db->where('COL_MODE', $this->logbook_model->get_main_mode_from_mode($mode)); $this->db->where('COL_BAND', $band); $this->db->where('COL_PROP_MODE !=', 'SAT'); } $this->db->where_in('station_id', $logbooks_locations_array); $this->db->where('COL_COUNTRY', urldecode($country)); $query = $this->db->get($this->config->item('table_name'), 1, 0); foreach ($query->result() as $workedBeforeRow) { $return['workedBefore'] = true; } // Check each confirmation type separately $confirmation_types = [ 'qsl' => "COL_QSL_RCVD='Y'", 'lotw' => "COL_LOTW_QSL_RCVD='Y'", 'eqsl' => "COL_EQSL_QSL_RCVD='Y'", 'qrz' => "COL_QRZCOM_QSO_DOWNLOAD_STATUS='Y'" ]; foreach ($confirmation_types as $type_key => $where_clause) { if ($type == "SAT") { $this->db->where('COL_PROP_MODE', 'SAT'); } else { $this->db->where('COL_MODE', $this->logbook_model->get_main_mode_from_mode($mode)); $this->db->where('COL_BAND', $band); $this->db->where('COL_PROP_MODE !=', 'SAT'); } $this->db->where_in('station_id', $logbooks_locations_array); $this->db->where('COL_COUNTRY', urldecode($country)); $this->db->where($where_clause); $query = $this->db->get($this->config->item('table_name'), 1, 0); if ($query->num_rows() > 0) { $return['confirmed'][$type_key] = true; } } http_response_code(201); echo json_encode($return, JSON_PRETTY_PRINT); } else { http_response_code(201); echo json_encode($return, JSON_PRETTY_PRINT); } } else { // Logbook not found http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => "logbook not found"]); die(); } } /** * Check worked and confirmation status for callsign and country. * * @api POST /api/logbook_worked_status * @header Content-Type application/json * * Required fields: * - key * - logbook_public_slug * - callsign * * Optional fields: * - band * - mode * - gridsquare */ function logbook_worked_status() { header('Content-type: application/json'); $this->load->model('api_model'); $obj = json_decode(file_get_contents("php://input"), true); if ($obj === NULL) { http_response_code(400); echo json_encode(['status' => 'failed', 'reason' => "wrong JSON"]); return; } if (!isset($obj['key']) || $this->api_model->authorize($obj['key']) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "missing api key"]); return; } if (!isset($obj['logbook_public_slug']) || !isset($obj['callsign'])) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "missing fields"]); return; } $this->load->model('logbook_model'); $this->load->model('logbooks_model'); $logbook_slug = trim($obj['logbook_public_slug']); $callsign = strtoupper(trim($obj['callsign'])); $band = isset($obj['band']) && trim($obj['band']) !== '' ? strtoupper(trim($obj['band'])) : null; $mode = isset($obj['mode']) && trim($obj['mode']) !== '' ? strtoupper(trim($obj['mode'])) : null; $gridsquare = isset($obj['gridsquare']) && trim($obj['gridsquare']) !== '' ? strtoupper(trim($obj['gridsquare'])) : null; $date = date("Y-m-d"); $callsign_dxcc_lookup = $this->logbook_model->dxcc_lookup($callsign, $date); $country = isset($callsign_dxcc_lookup['entity']) ? $callsign_dxcc_lookup['entity'] : ''; if (!$this->logbooks_model->public_slug_exists($logbook_slug)) { http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => "logbook not found"]); return; } $logbook_id = $this->logbooks_model->public_slug_exists_logbook_id($logbook_slug); if ($logbook_id == false) { http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => $logbook_slug . " has no associated station locations"]); return; } $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($logbook_id); if (!$logbooks_locations_array) { http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => "Empty Logbook"]); return; } $return = [ 'callsign' => $callsign, 'country' => $country, 'band' => $band, 'mode' => $mode, 'gridsquare' => $gridsquare, 'worked' => [ 'callsign_overall' => false, 'callsign_band' => $band !== null ? false : null, 'country_overall' => false, 'country_band' => $band !== null ? false : null, 'grid_overall' => $gridsquare !== null ? false : null, 'grid_band' => ($gridsquare !== null && $band !== null) ? false : null, ], 'confirmed' => [ 'callsign' => [ 'lotw' => false, 'qsl' => false, 'any' => false, ], 'country' => [ 'lotw' => false, 'qsl' => false, 'any' => false, ], ], ]; $table_name = $this->config->item('table_name'); $main_mode = $mode !== null ? $this->logbook_model->get_main_mode_from_mode($mode) : null; $this->db->where_in('station_id', $logbooks_locations_array); $this->db->where('COL_CALL', $callsign); $callsign_overall_query = $this->db->get($table_name, 1, 0); $return['worked']['callsign_overall'] = $callsign_overall_query->num_rows() > 0; if ($band !== null) { $this->db->where_in('station_id', $logbooks_locations_array); $this->db->where('COL_CALL', $callsign); $this->db->where('COL_BAND', $band); if ($main_mode !== null) { $this->db->where('COL_MODE', $main_mode); } $callsign_band_query = $this->db->get($table_name, 1, 0); $return['worked']['callsign_band'] = $callsign_band_query->num_rows() > 0; } if ($country !== '') { $this->db->where_in('station_id', $logbooks_locations_array); $this->db->where('COL_COUNTRY', urldecode($country)); $country_overall_query = $this->db->get($table_name, 1, 0); $return['worked']['country_overall'] = $country_overall_query->num_rows() > 0; if ($band !== null) { $this->db->where_in('station_id', $logbooks_locations_array); $this->db->where('COL_COUNTRY', urldecode($country)); $this->db->where('COL_BAND', $band); if ($main_mode !== null) { $this->db->where('COL_MODE', $main_mode); } $country_band_query = $this->db->get($table_name, 1, 0); $return['worked']['country_band'] = $country_band_query->num_rows() > 0; } } if ($gridsquare !== null) { $grid_overall_result = $this->logbook_model->check_if_grid_worked_in_logbook($gridsquare, $logbooks_locations_array, null); $return['worked']['grid_overall'] = $grid_overall_result > 0; if ($band !== null) { $grid_band_result = $this->logbook_model->check_if_grid_worked_in_logbook($gridsquare, $logbooks_locations_array, $band); $return['worked']['grid_band'] = $grid_band_result > 0; } } $this->db->where_in('station_id', $logbooks_locations_array); $this->db->where('COL_CALL', $callsign); $this->db->where("COL_LOTW_QSL_RCVD='Y'"); $callsign_lotw_query = $this->db->get($table_name, 1, 0); $return['confirmed']['callsign']['lotw'] = $callsign_lotw_query->num_rows() > 0; $this->db->where_in('station_id', $logbooks_locations_array); $this->db->where('COL_CALL', $callsign); $this->db->where("COL_QSL_RCVD='Y'"); $callsign_qsl_query = $this->db->get($table_name, 1, 0); $return['confirmed']['callsign']['qsl'] = $callsign_qsl_query->num_rows() > 0; $return['confirmed']['callsign']['any'] = $return['confirmed']['callsign']['lotw'] || $return['confirmed']['callsign']['qsl']; if ($country !== '') { $this->db->where_in('station_id', $logbooks_locations_array); $this->db->where('COL_COUNTRY', urldecode($country)); $this->db->where("COL_LOTW_QSL_RCVD='Y'"); $country_lotw_query = $this->db->get($table_name, 1, 0); $return['confirmed']['country']['lotw'] = $country_lotw_query->num_rows() > 0; $this->db->where_in('station_id', $logbooks_locations_array); $this->db->where('COL_COUNTRY', urldecode($country)); $this->db->where("COL_QSL_RCVD='Y'"); $country_qsl_query = $this->db->get($table_name, 1, 0); $return['confirmed']['country']['qsl'] = $country_qsl_query->num_rows() > 0; $return['confirmed']['country']['any'] = $return['confirmed']['country']['lotw'] || $return['confirmed']['country']['qsl']; } http_response_code(200); echo json_encode($return, JSON_PRETTY_PRINT); } /* ENDPOINT for Rig Control */ function radio() { header('Content-type: application/json'); $this->load->model('api_model'); //$json = '{"radio":"FT-950","frequency":14075,"mode":"SSB","timestamp":"2012/04/07 16:47"}'; $this->load->model('cat'); //var_dump(file_get_contents("php://input"), true); // Decode JSON and store $obj = json_decode(file_get_contents("php://input"), true); if (!isset($obj['key']) || $this->api_model->authorize($obj['key']) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => "missing api key"]); die(); } $this->api_model->update_last_used($obj['key']); $user_id = $this->api_model->key_userid($obj['key']); // Store Result to Database $this->cat->update($obj, $user_id); // Return Message $arr = array('status' => 'success'); echo json_encode($arr); } /* * Radio Commands API Functions for Aurora Desktop App */ /** * Get pending radio commands * GET /api/radio_commands_pending/{key} * GET /api/radio_commands_pending_by_name/{key}/{radio_name} */ function radio_commands_pending($key, $radio_id = null) { header('Content-type: application/json'); $this->load->model('api_model'); $this->load->model('cat'); // Check API key if (!$key || $this->api_model->authorize($key) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => 'unauthorized']); return; } $this->api_model->update_last_used($key); $user_id = $this->api_model->key_userid($key); if ($radio_id) { // Get commands for specific radio by ID $commands = $this->cat->get_pending_commands($radio_id, $user_id); } else { // Get all pending commands for user $commands = $this->cat->get_all_pending_commands($user_id); } // Debug information $debug_info = array( 'user_id' => $user_id, 'radio_id' => $radio_id, 'current_time' => date('Y-m-d H:i:s'), 'total_commands_in_db' => $this->db->where('user_id', $user_id)->count_all_results('radio_commands'), 'pending_commands_in_db' => $this->db->where('user_id', $user_id)->where('status', 'PENDING')->count_all_results('radio_commands') ); echo json_encode([ 'status' => 'success', 'commands' => $commands, 'count' => count($commands), 'debug' => $debug_info ]); } /** * Get pending radio commands by radio name (for Aurora) * GET /api/radio_commands_pending_by_name/{key}/{radio_name} */ function radio_commands_pending_by_name($key, $radio_name = null) { header('Content-type: application/json'); $this->load->model('api_model'); $this->load->model('cat'); // Check API key if (!$key || $this->api_model->authorize($key) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => 'unauthorized']); return; } $this->api_model->update_last_used($key); $user_id = $this->api_model->key_userid($key); // Clean up expired commands for this user $cleaned = $this->cat->cleanup_expired_commands($user_id); if ($radio_name) { // URL decode the radio name in case it has spaces/special chars $decoded_radio_name = urldecode($radio_name); // Additional cleanup - remove any extra whitespace $decoded_radio_name = trim($decoded_radio_name); // Debug: Check if radio exists in cat table for this user $radio_check = $this->db->select('id, radio, user_id') ->where('radio', $decoded_radio_name) ->where('user_id', $user_id) ->get('cat'); // Debug: Check all radios for this user $all_user_radios = $this->db->select('id, radio') ->where('user_id', $user_id) ->get('cat'); // Debug: Check if any commands exist for this user $all_commands = $this->db->select('id, radio_id, radio_name, command_type, status, created_at, expires_at') ->where('user_id', $user_id) ->get('radio_commands'); // Get commands for specific radio by name $commands = $this->cat->get_pending_commands_by_name($decoded_radio_name, $user_id); $debug_info = array( 'user_id' => $user_id, 'decoded_radio_name' => $decoded_radio_name, 'radio_found_in_cat' => $radio_check->num_rows() > 0, 'radio_details' => $radio_check->num_rows() > 0 ? $radio_check->row_array() : null, 'all_user_radios' => $all_user_radios->result_array(), 'all_user_commands' => $all_commands->result_array(), 'current_time' => date('Y-m-d H:i:s') ); } else { // Get all pending commands for user $commands = $this->cat->get_all_pending_commands($user_id); $decoded_radio_name = null; $debug_info = array('user_id' => $user_id); } echo json_encode([ 'status' => 'success', 'commands' => $commands, 'count' => count($commands), 'radio_name' => $decoded_radio_name, 'original_param' => $radio_name, 'expired_cleaned' => $cleaned ]); } /** * Update radio command status * POST /api/radio_commands/update_status/{key} * Body: {"command_id": 123, "status": "COMPLETED", "error_message": "optional"} */ function radio_commands_update_status($key) { header('Content-type: application/json'); $this->load->model('api_model'); $this->load->model('cat'); // Check API key if (!$key || $this->api_model->authorize($key) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => 'unauthorized']); return; } $this->api_model->update_last_used($key); $user_id = $this->api_model->key_userid($key); // Get JSON input $input = json_decode(file_get_contents("php://input"), true); if (!isset($input['command_id']) || !isset($input['status'])) { http_response_code(400); echo json_encode(['status' => 'failed', 'reason' => 'missing command_id or status']); return; } $command_id = $input['command_id']; $status = $input['status']; $error_message = isset($input['error_message']) ? $input['error_message'] : null; // Validate status $valid_statuses = ['PROCESSING', 'COMPLETED', 'FAILED']; if (!in_array($status, $valid_statuses)) { http_response_code(400); echo json_encode(['status' => 'failed', 'reason' => 'invalid status']); return; } // Verify command belongs to user $command = $this->cat->get_command($command_id, $user_id); if (!$command) { http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => 'command not found']); return; } // Update command status $result = $this->cat->update_command_status($command_id, $status, $error_message); if ($result) { echo json_encode(['status' => 'success', 'updated' => true]); } else { http_response_code(500); echo json_encode(['status' => 'failed', 'reason' => 'update failed']); } } /** * Get radio command by ID * GET /api/radio_commands/get/{key}/{command_id} */ function radio_commands_get($key, $command_id) { header('Content-type: application/json'); $this->load->model('api_model'); $this->load->model('cat'); // Check API key if (!$key || $this->api_model->authorize($key) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => 'unauthorized']); return; } $this->api_model->update_last_used($key); $user_id = $this->api_model->key_userid($key); $command = $this->cat->get_command($command_id, $user_id); if ($command) { echo json_encode([ 'status' => 'success', 'command' => $command ]); } else { http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => 'command not found']); } } /** * Queue a new radio command (for testing or external apps) * POST /api/radio_commands_queue/{key} * Body: {"radio_id": 1, "command_type": "SET_FREQ", "frequency": 14074000, "mode": "USB"} * OR: {"radio_name": "FT-991A", "command_type": "SET_FREQ", "frequency": 14074000, "mode": "USB"} */ function radio_commands_queue($key) { header('Content-type: application/json'); $this->load->model('api_model'); $this->load->model('cat'); // Check API key if (!$key || $this->api_model->authorize($key) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => 'unauthorized']); return; } $this->api_model->update_last_used($key); $user_id = $this->api_model->key_userid($key); // Get JSON input $input = json_decode(file_get_contents("php://input"), true); if (!isset($input['command_type'])) { http_response_code(400); echo json_encode(['status' => 'failed', 'reason' => 'missing command_type']); return; } $radio_id = null; // Check if radio_name is provided instead of radio_id if (isset($input['radio_name'])) { // Get radio ID from radio name $radio_query = $this->db->select('id') ->where('radio', $input['radio_name']) ->where('user_id', $user_id) ->get('cat'); if ($radio_query->num_rows() == 0) { http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => 'radio not found']); return; } $radio_id = $radio_query->row()->id; } elseif (isset($input['radio_id'])) { $radio_id = $input['radio_id']; } else { http_response_code(400); echo json_encode(['status' => 'failed', 'reason' => 'missing radio_id or radio_name']); return; } // Prepare command data $command_data = array( 'radio_id' => $radio_id, 'user_id' => $user_id, 'command_type' => $input['command_type'], 'expires_at' => date('Y-m-d H:i:s', strtotime('+30 minutes')) ); // Add optional parameters if (isset($input['frequency'])) $command_data['frequency'] = $input['frequency']; if (isset($input['mode'])) $command_data['mode'] = $input['mode']; if (isset($input['vfo'])) $command_data['vfo'] = $input['vfo']; if (isset($input['power'])) $command_data['power'] = $input['power']; if (isset($input['station_id'])) $command_data['station_id'] = $input['station_id']; $command_id = $this->cat->queue_command($command_data); if ($command_id) { echo json_encode([ 'status' => 'success', 'command_id' => $command_id, 'message' => 'Command queued successfully' ]); } else { http_response_code(500); echo json_encode(['status' => 'failed', 'reason' => 'failed to queue command']); } } /** * Get all pending radio commands for all user's radios (optimized for Aurora) * GET /api/radio_commands_all_pending/{key} */ function radio_commands_all_pending($key) { header('Content-type: application/json'); $this->load->model('api_model'); $this->load->model('cat'); // Check API key if (!$key || $this->api_model->authorize($key) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => 'unauthorized']); return; } $this->api_model->update_last_used($key); $user_id = $this->api_model->key_userid($key); // Clean up expired commands for this user $cleaned = $this->cat->cleanup_expired_commands($user_id); // Get all pending commands for all user's radios $commands = $this->cat->get_all_pending_commands($user_id); // Group commands by radio for easier processing $commands_by_radio = array(); $radio_names = array(); foreach ($commands as $command) { $radio_name = $command['radio_name']; if (!isset($commands_by_radio[$radio_name])) { $commands_by_radio[$radio_name] = array(); $radio_names[] = $radio_name; } $commands_by_radio[$radio_name][] = $command; } echo json_encode([ 'status' => 'success', 'commands' => $commands, 'commands_by_radio' => $commands_by_radio, 'radio_names' => $radio_names, 'total_commands' => count($commands), 'radios_with_commands' => count($commands_by_radio), 'expired_cleaned' => $cleaned, 'timestamp' => date('Y-m-d H:i:s') ]); } /** * Cleanup expired radio commands (maintenance endpoint) * POST /api/radio_commands_cleanup/{key} * Body: {"cleanup_old": true, "days_old": 7} (optional parameters) */ function radio_commands_cleanup($key) { header('Content-type: application/json'); $this->load->model('api_model'); $this->load->model('cat'); // Check API key if (!$key || $this->api_model->authorize($key) == 0) { http_response_code(401); echo json_encode(['status' => 'failed', 'reason' => 'unauthorized']); return; } $this->api_model->update_last_used($key); $user_id = $this->api_model->key_userid($key); // Get optional JSON input $input = json_decode(file_get_contents("php://input"), true); $cleanup_old = isset($input['cleanup_old']) ? $input['cleanup_old'] : false; $days_old = isset($input['days_old']) ? intval($input['days_old']) : 7; // Clean up expired commands $expired_cleaned = $this->cat->cleanup_expired_commands($user_id); $old_cleaned = 0; if ($cleanup_old) { $old_cleaned = $this->cat->cleanup_old_commands($days_old, $user_id); } echo json_encode([ 'status' => 'success', 'expired_commands_cleaned' => $expired_cleaned, 'old_commands_cleaned' => $old_cleaned, 'cleanup_old_enabled' => $cleanup_old, 'days_old_threshold' => $days_old, 'timestamp' => date('Y-m-d H:i:s') ]); } /* * * Stats API function calls * */ function statistics($key = null) { header('Content-type: application/json'); $this->load->model('logbook_model'); $data['todays_qsos'] = $this->logbook_model->todays_qsos(null, $key); $data['total_qsos'] = $this->logbook_model->total_qsos(null, $key); $data['month_qsos'] = $this->logbook_model->month_qsos(null, $key); $data['year_qsos'] = $this->logbook_model->year_qsos(null, $key); http_response_code(201); echo json_encode(['Today' => $data['todays_qsos'], 'total_qsos' => $data['total_qsos'], 'month_qsos' => $data['month_qsos'], 'year_qsos' => $data['year_qsos']]); } function lookup() { // start benchmarking $this->output->enable_profiler(TRUE); /* * * Callsign lookup function for Cloudlogs logging page or thirdparty systems * which want to show previous QSO data on their system. * * TODO * - Local data make one database call ONLY * - Add eQSL status * - Add Callbook returned data * - Add QSO before data array * - Add options for checking based on band/mode/sat * */ // Make sure users logged in $this->load->model('user_model'); if (!$this->user_model->authorize($this->config->item('auth_mode'))) { return; } $this->load->model("logbook_model"); $date = date("Y-m-d"); // Return Array $return = [ "callsign" => "", "dxcc" => false, "dxcc_lat" => "", "dxcc_long" => "", "dxcc_cqz" => "", "name" => "", "gridsquare" => "", "location" => "", "iota_ref" => "", "state" => "", "us_county" => "", "qsl_manager" => "", "bearing" => "", "workedBefore" => false, "lotw_member" => false, "suffix_slash" => "", // Suffix Slash aka Portable ]; /* * * Handle POST data being sent to check lookups * */ $raw_input = json_decode(file_get_contents("php://input"), true); $lookup_callsign = strtoupper($raw_input['callsign']); /* * * Handle Callsign field * */ $return['callsign'] = $lookup_callsign; /* * * Lookup DXCC and Suffix information * */ $callsign_dxcc_lookup = $this->logbook_model->dxcc_lookup($lookup_callsign, $date); $last_slash_pos = strrpos($lookup_callsign, '/'); if (isset($last_slash_pos) && $last_slash_pos > 4) { $suffix_slash = $last_slash_pos === false ? $lookup_callsign : substr($lookup_callsign, $last_slash_pos + 1); switch ($suffix_slash) { case "P": $suffix_slash_item = "Portable"; break; case "M": $suffix_slash_item = "Mobile"; case "MM": $suffix_slash_item = "Maritime Mobile"; break; default: // If its not one of the above suffix slashes its likely dxcc $ans2 = $this->logbook_model->dxcc_lookup($suffix_slash, $date); $suffix_slash_item = null; } $return['suffix_slash'] = $suffix_slash_item; } // If the final slash is a DXCC then find it! if (isset($ans2['call'])) { $return['dxcc'] = $ans2['entity']; $return['dxcc_lat'] = $ans2['lat']; $return['dxcc_long'] = $ans2['long']; $return['dxcc_cqz'] = $ans2['cqz']; } else { $return['dxcc'] = $callsign_dxcc_lookup['entity']; $return['dxcc_lat'] = $callsign_dxcc_lookup['lat']; $return['dxcc_long'] = $callsign_dxcc_lookup['long']; $return['dxcc_cqz'] = $callsign_dxcc_lookup['cqz']; } /* * * Pool any local data we have for a callsign * */ $call_lookup_results = $this->logbook_model->call_lookup_result($lookup_callsign); if ($call_lookup_results != null) { $return['name'] = $call_lookup_results->COL_NAME; $return['gridsquare'] = $call_lookup_results->COL_GRIDSQUARE; $return['location'] = $call_lookup_results->COL_QTH; $return['iota_ref'] = $call_lookup_results->COL_IOTA; $return['qsl_manager'] = $call_lookup_results->COL_QSL_VIA; $return['state'] = $call_lookup_results->COL_STATE; $return['us_county'] = $call_lookup_results->COL_CNTY; if ($return['gridsquare'] != "") { $return['latlng'] = $this->qralatlng($return['gridsquare']); } } /* * * Check if callsign is active on LoTW * */ /* * * Output Returned data * */ echo json_encode($return, JSON_PRETTY_PRINT); return; // End benchmarking $this->output->enable_profiler(FALSE); } function qralatlng($qra) { $this->load->library('Qra'); $latlng = $this->qra->qra2latlong($qra); return $latlng; } /** * API endpoint to get recent QSOs from a public logbook * * @api GET /api/recent_qsos/{public_slug}/{limit} * * @param string public_slug Required. Public slug identifier for the logbook * @param int limit Optional. Number of QSOs to return (default: 10, max: 50) * * @return json Returns JSON array with recent QSO data or error message * * @throws 404 Not Found - Logbook not found or empty logbook * @throws 400 Bad Request - Invalid limit parameter * * @example * Request: GET /api/recent_qsos/my-public-logbook/5 * * Response: * { * "qsos": [ * { * "date": "2024-01-15", * "time": "14:30", * "callsign": "W1AW", * "mode": "SSB", * "band": "20M", * "rst_sent": "59", * "rst_rcvd": "59" * } * ], * "count": 1, * "logbook_slug": "my-public-logbook" * } */ function recent_qsos($public_slug = null, $limit = 10) { header('Content-type: application/json'); // Validate and sanitize $limit if (!is_numeric($limit)) { $limit = 10; // Default to 10 if not numeric } else { $limit = intval($limit); if ($limit < 1) { $limit = 1; // Minimum limit of 1 } elseif ($limit > 50) { $limit = 50; // Maximum limit of 50 } } if ($public_slug == null) { http_response_code(400); echo json_encode(['status' => 'failed', 'reason' => 'missing public_slug parameter']); return; } $this->load->model('logbooks_model'); $this->load->model('logbook_model'); if ($this->logbooks_model->public_slug_exists($public_slug)) { $logbook_id = $this->logbooks_model->public_slug_exists_logbook_id($public_slug); if ($logbook_id != false) { // Get associated station locations for mysql queries $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($logbook_id); if (!$logbooks_locations_array) { http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => 'Empty Logbook']); return; } // Get recent QSOs using existing method $recent_qsos_query = $this->logbook_model->get_last_qsos($limit, $logbooks_locations_array); if ($recent_qsos_query == null) { http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => 'No QSOs found']); return; } // Format the data for JSON response $qsos = array(); foreach ($recent_qsos_query->result() as $row) { $qso = array( 'date' => date('Y-m-d', strtotime($row->COL_TIME_ON)), 'time' => date('H:i', strtotime($row->COL_TIME_ON)), 'callsign' => strtoupper($row->COL_CALL), 'mode' => $row->COL_SUBMODE ? $row->COL_SUBMODE : $row->COL_MODE, 'band' => $row->COL_SAT_NAME ? $row->COL_SAT_NAME : $row->COL_BAND, 'rst_sent' => $row->COL_RST_SENT, 'rst_rcvd' => $row->COL_RST_RCVD ); // Add optional fields if they exist if ($row->COL_STX_STRING) { $qso['stx_string'] = $row->COL_STX_STRING; } if ($row->COL_SRX_STRING) { $qso['srx_string'] = $row->COL_SRX_STRING; } if ($row->COL_VUCC_GRIDS) { $qso['gridsquare'] = $row->COL_VUCC_GRIDS; } elseif ($row->COL_GRIDSQUARE) { $qso['gridsquare'] = $row->COL_GRIDSQUARE; } if ($row->COL_QTH) { $qso['qth'] = $row->COL_QTH; } if ($row->COL_NAME) { $qso['name'] = $row->COL_NAME; } // Prefer grid-derived coordinates for plotting, including VUCC multi-grid line/corner records. $plot_latlng = false; if (!empty($row->COL_VUCC_GRIDS)) { $plot_latlng = $this->qralatlng($row->COL_VUCC_GRIDS); } elseif (!empty($row->COL_GRIDSQUARE)) { $plot_latlng = $this->qralatlng($row->COL_GRIDSQUARE); } if (is_array($plot_latlng) && count($plot_latlng) === 2) { $qso['lat'] = $plot_latlng[0]; $qso['long'] = $plot_latlng[1]; } $dxcc = $this->logbook_model->check_dxcc_table(strtoupper(trim(strtoupper($row->COL_CALL))), $row->COL_TIME_ON); if (empty($dxcc[0])) { $dxcc_id = null; } else { $qso['country'] = $dxcc[1]; if (!isset($qso['lat']) || !isset($qso['long'])) { $qso['lat'] = $dxcc[4]; $qso['long'] = $dxcc[5]; } } $qsos[] = $qso; } http_response_code(200); echo json_encode([ 'qsos' => $qsos, 'count' => count($qsos), 'logbook_slug' => $public_slug ], JSON_PRETTY_PRINT); } else { http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => $public_slug . ' has no associated station locations']); } } else { http_response_code(404); echo json_encode(['status' => 'failed', 'reason' => 'logbook not found']); } } }