diff --git a/README.md b/README.md index 77be53b..46dd51f 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,8 @@ what works: * displaying new node notifications and chat messages in default channel in chat box * displaying active node count and filtering nodes by name * exposing nodes and messages to api endpoints - -what does not work _(yet):_ - -* posting nodes and messages to the api endpoints _(wip)_ - +* posting nodes and messages to the api endpoints with authentication + ## requirements requires a meshtastic node connected (via serial) to gather mesh data and the meshtastic cli. @@ -78,7 +75,7 @@ Puma starting in single mode... * Listening on http://127.0.0.1:41447 ``` -set `API_TOKEN` required for authorizations on the api post-endpoints (wip). +set `API_TOKEN` required for authorizations on the api post-endpoints. the web app can be configured with environment variables (defaults shown): @@ -100,8 +97,10 @@ the web app contains an api: * GET `/api/nodes?limit=1000` - returns the latest 1000 nodes reported to the app * GET `/api/messages?limit=1000` - returns the latest 1000 messages +* POST `/api/nodes` - upserts nodes provided as JSON object mapping node ids to node data (requires `Authorization: Bearer `) +* POST `/api/messages` - appends messages provided as a JSON object or array (requires `Authorization: Bearer `) -the `POST` apis are _currently being worked on (tm)._ +the `API_TOKEN` environment variable must be set to a non-empty value and match the token supplied in the `Authorization` header for `POST` requests. ## license diff --git a/web/app.rb b/web/app.rb index e045ff3..ec15e27 100644 --- a/web/app.rb +++ b/web/app.rb @@ -141,7 +141,29 @@ end def require_token! token = ENV["API_TOKEN"] provided = request.env["HTTP_AUTHORIZATION"].to_s.sub(/^Bearer\s+/i, "") - halt 403, { error: "Forbidden" }.to_json unless token && provided == token + halt 403, { error: "Forbidden" }.to_json unless token && !token.empty? && provided == token +end + +def insert_message(db, m) + rx_time = m["rx_time"]&.to_i || Time.now.to_i + rx_iso = m["rx_iso"] || Time.at(rx_time).utc.iso8601 + row = [ + rx_time, + rx_iso, + m["from_id"], + m["to_id"], + m["channel"], + m["portnum"], + m["text"], + m["snr"], + m["rssi"], + m["hop_limit"], + m["raw_json"], + ] + db.execute <<~SQL, row + INSERT INTO messages(rx_time,rx_iso,from_id,to_id,channel,portnum,text,snr,rssi,hop_limit,raw_json) + VALUES (?,?,?,?,?,?,?,?,?,?,?) + SQL end post "/api/nodes" do @@ -162,6 +184,25 @@ ensure db&.close end +post "/api/messages" do + require_token! + content_type :json + begin + data = JSON.parse(request.body.read) + rescue JSON::ParserError + halt 400, { error: "invalid JSON" }.to_json + end + messages = data.is_a?(Array) ? data : [data] + halt 400, { error: "too many messages" }.to_json if messages.size > 1000 + db = SQLite3::Database.new(DB_PATH) + messages.each do |msg| + insert_message(db, msg) + end + { status: "ok" }.to_json +ensure + db&.close +end + get "/" do erb :index, locals: { site_name: SITE_NAME,