Files
step3/scripts/autoload/ccs_service.gd
T
2026-07-15 10:17:53 -05:00

139 lines
4.6 KiB
GDScript

extends Node
const EndpointConfig := preload("res://scripts/autoload/api_endpoint_config.gd")
const DEFAULT_API_URL := "https://s3q.ranaze.com/api/ccs"
signal cases_changed
signal attempt_changed
signal orders_changed
signal history_changed
signal prediction_changed
signal request_failed(message: String)
var api_url := DEFAULT_API_URL
var request: HTTPRequest
var cases: Array = []
var attempt := {}
var orders: Array = []
var history: Array = []
var prediction := {}
var is_loading := false
var pending_action := ""
var queue: Array = []
func _ready() -> void:
api_url = EndpointConfig.resolve(str(ProjectSettings.get_setting("step3/ccs_api_url", DEFAULT_API_URL)), "/api/ccs")
request = HTTPRequest.new()
request.request_completed.connect(_on_request_completed)
add_child(request)
func load_cases() -> void:
_send("cases", HTTPClient.METHOD_GET, "/cases")
func create_attempt(case_external_id: String, mode: String) -> void:
_send("attempt", HTTPClient.METHOD_POST, "/attempts", {"caseExternalId": case_external_id, "mode": mode})
func load_attempt(attempt_id: String) -> void:
_send("attempt", HTTPClient.METHOD_GET, "/attempts/%s" % attempt_id.uri_encode())
func send_command(type: String, code: String = "", minutes: int = 0) -> void:
if attempt.is_empty():
return
_send("attempt", HTTPClient.METHOD_POST, "/attempts/%s/commands" % str(attempt.get("id", "")).uri_encode(), {
"commandId": "%d-%d" % [Time.get_ticks_usec(), randi()],
"expectedRevision": int(attempt.get("revision", 0)),
"type": type,
"code": code,
"minutes": minutes
})
func search_orders(query: String) -> void:
if query.strip_edges().length() < 2:
orders = []
orders_changed.emit()
return
_send("orders", HTTPClient.METHOD_GET, "/orders/search?q=%s&limit=20" % query.strip_edges().uri_encode())
func load_history() -> void:
_send("history", HTTPClient.METHOD_GET, "/history?limit=50")
func load_prediction() -> void:
_send("prediction", HTTPClient.METHOD_GET, "/prediction")
func abandon() -> void:
if attempt.is_empty():
return
_send("abandon", HTTPClient.METHOD_POST, "/attempts/%s/abandon" % str(attempt.get("id", "")).uri_encode())
func deadline_unix() -> float:
var value := str(attempt.get("finalOrdersDeadlineUtc", attempt.get("deadlineUtc", "")))
return 0.0 if value.is_empty() or value == "<null>" else Time.get_unix_time_from_datetime_string(value)
func _send(action: String, method: int, suffix: String, payload: Dictionary = {}) -> void:
var url := "%s%s" % [api_url.rstrip("/"), suffix]
if is_loading:
queue.append({"action": action, "method": method, "url": url, "payload": payload.duplicate(true)})
return
if AuthState.access_token.is_empty():
return
is_loading = true
pending_action = action
var headers := PackedStringArray(["Authorization: Bearer %s" % AuthState.access_token])
var body := ""
if not payload.is_empty():
headers.append("Content-Type: application/json")
body = JSON.stringify(payload)
var error := request.request(url, headers, method, body)
if error != OK:
is_loading = false
request_failed.emit("The CCS request could not be started.")
func _on_request_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
var action := pending_action
pending_action = ""
is_loading = false
call_deferred("_process_queue")
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
var detail := body.get_string_from_utf8().strip_edges().left(240)
request_failed.emit("CCS request failed (%d). %s" % [response_code, detail])
return
if response_code == 204:
attempt = {}
attempt_changed.emit()
return
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
if action in ["cases", "orders", "history"] and typeof(parsed) != TYPE_ARRAY:
request_failed.emit("The CCS API returned an invalid list.")
return
if action not in ["cases", "orders", "history"] and typeof(parsed) != TYPE_DICTIONARY:
request_failed.emit("The CCS API returned an invalid response.")
return
match action:
"cases": cases = parsed; cases_changed.emit()
"orders": orders = parsed; orders_changed.emit()
"history": history = parsed; history_changed.emit()
"prediction": prediction = parsed; prediction_changed.emit()
_:
attempt = parsed
attempt_changed.emit()
func _process_queue() -> void:
if is_loading or queue.is_empty():
return
var queued: Dictionary = queue.pop_front()
var suffix := str(queued.get("url", "")).trim_prefix(api_url.rstrip("/"))
_send(str(queued.get("action", "")), int(queued.get("method", HTTPClient.METHOD_GET)), suffix, queued.get("payload", {}))