When access_token_id is null, join access_tokens on locked_mac so
token_code, expires_at, remaining_seconds and plan_name still populate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- app.js: comment out per-request HTTP logger (floods logs with webhook bodies)
- omada.js: strip verbose request/response interceptors; keep only error logging
with method+url+status+body for failed requests
- pollSessions.job.js: warn when a new session has no matching token so MAC
format mismatches show up clearly in logs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add missing portal customization properties (backgroundPictureId, logoSize, etc.)
- Always set httpsRedirectEnable to true for portal endpoints
- Include pageType and landingPage in portal updates
- Fix server URL construction to always end with '?' for external portal
- Remove redundant portalCustomize merge in updatePortalUrl since full object is now provided
fix(omada): ensure portalCustomize fields are included in PATCH requestsβ
When updating Omada portal SSID or URL, the API requires a complete portalCustomize object. Previously, missing fields could cause validation errors. Now we merge current portalCustomize with mandatory defaults to ensure successful synchronization.
Also sync portal URL updates to Omada when device is linked, maintaining external portal activation.
- Omada: log request body and full (untruncated) response per call
- Express: log every incoming request with method, URL, body, status
code, and response time
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(ssid): update portal serverUrl to our server on every SSID setβ
When reusing an existing portal (or re-linking an SSID), the portal's
externalPortal.serverUrl was left pointing to whatever was set before.
Now patches the URL to mbonea-wifibiz.hf.space/portal/{siteId} every
time an SSID is created or updated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(ssid): reuse existing Omada portal instead of creating duplicateβ
Creating a new portal failed with -34337 when the SSID was already
bound to an existing portal. Now fetches existing portals for the site
first β reuses one if found, creates a new one only if none exist.
Also adds getPortals() to the omada service.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(omada): align createPortal with actual API shapeβ
- Set httpsRedirectEnable based on PORTAL_SCHEME env var
- Use device name (not id) for lazy portal creation
- Minor portalCustomize field alignment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(omada): extract ssidId from createSSID response; create portal lazily with SSIDβ
- createSSID returned [object Object] β Omada uses ssidId not id field
- createPortal fails on newer Omada with empty ssidList; now created
lazily in PATCH /ssid once we have the SSID id to attach
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(omada): extract siteId string from createSite response objectβ
Omada returns result as {siteId: "..."} not a plain string β passing
the object to MySQL as a VARCHAR caused Incorrect arguments to
mysqld_stmt_execute on device registration.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(omada): correct rateLimits path and handle direct-array result shapeβ
- Correct path is /profiles/rateLimits (camelCase), not ratelimit
- Omada returns result as a plain array, not result.data β fix extraction
so the profile ID is actually found and included in createSSID payload
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Omada rejects SSID creation with "rateLimit should not be null".
Now fetches the site's rate limit profiles first and includes the
first profile's ID in the rateLimit and ssidRateLimit fields.
Also corrects band from 3 (2.4+5GHz) to 1 (2.4GHz only) for
EAP110-Outdoor compatibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(devices): browser-triggered polling, full SSID update payload, and SSID deleteβ
- GET /api/devices now fires pollDevices.throttled() so device status
syncs from Omada on every dashboard load, not just the 15-min cron
- Move pollDevices throttle state into the job module so /api/devices
and /api/dashboard/summary share one 60s window
- Fix updateSSID to fetch the existing SSID and PATCH with the full
object β Omada requires a complete payload, not a sparse merge
- Add deleteSSID to omada service and expose it via DELETE /api/devices/:id/ssid
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(omada): align createSSID/updateSSID payload with actual Omada API shapeβ
- POST /api/devices/:id/reboot β sends reboot command to AP via Omada
- PATCH /api/devices/:id/ssid β updates ssid_name/password in DB and Omada
- POST /api/devices/:id/remove β revokes tokens, forgets AP from Omada,
marks device cancelled/removed in DB
- omada.js: add forgetDevice and updateSSID functions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix SSID security mode: use mode=3 (WPA2-Personal) not mode=2 (WPA-Personal)β
In Omada 5.x: mode 2=WPA-Personal(TKIP), mode 3=WPA2-Personal(AES).
Also restore band=3 and wlanScheduleEnable=false which are required fields,
and encryptAlg=2 (AES) to match WPA2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Log all Omada responses; reuse existing SSID instead of creating duplicateβ
- Log every omadaRequest response (errorCode + result truncated to 300 chars)
- getSSIDs: new function to list SSIDs in a WLAN group
- createSSID: check for existing SSIDs first and reuse rather than POST,
which avoids 'invalid request parameters' when SSID already exists
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove band/enabled/encryptAlg fields that cause 'invalid request
parameters' on Omada 5.14
- Guard against null wlanId before calling createSSID
- Handle multiple possible ID field names from getWlans response
- Log full payloads and raw responses to surface Omada API mismatches
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add SSID/WLAN management: create WiFi network during device adoptionβ
- omada.js: add getWlans, getOrCreateWlan, createSSID, updatePortalSSID
- adopt endpoint: accept ssid_name + ssid_password, create WLAN group and
SSID on Omada, link SSID to portal (creates or updates existing portal),
persist wlan/ssid IDs and credentials on the device record
- device registration: accept and store ssid_name + ssid_password upfront
(defaults ssid_name to device name if not provided)
- schema.sql: add ssid_name, ssid_password, omada_wlan_id, omada_ssid_id
columns to devices table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Surface Omada error details to client; auto-reinit Omada if not connectedβ
- Return actual Omada error message on site creation failure instead of
generic message, so the caller can see what went wrong
- Guard omadaRequest against null controllerId (boot failure) by calling
initOmada() on demand before the first request
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Node.js/Express backend with Omada SDN integration, M-Pesa payments via
Snippe, SMS notifications, captive portal, and tenant dashboard API.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>