In shared mode the Xray API returns InboundId=0 for client traffic.
Collect() now looks up the real InboundId from the client_traffics table
by email, and skips unknown emails with a warning. Also computes and
sets online clients in XrayTrafficJob since addClientTraffic is bypassed.
- inject lightweight hooks in CheckDeviceLimitJob for deterministic tests
- add tests for run re-entrancy, disabled enforcement unban, and stale ban cleanup
- add inbound deviceLimit model/frontend fields and translations
- add CheckDeviceLimitJob with observation window and xray API ban/unban
- prevent job re-entrancy and restore users when limit is disabled
- reduce lock scope via snapshots to avoid blocking log parsing
- Prevent DBPassword from leaking to frontend (json:"-")
- Make migration direction explicit via --direction flag, set dbType only after success
- Use driver-appropriate DROP INDEX IF EXISTS for SQLite vs MariaDB
- Build DSN with mysql.Config.FormatDSN() to prevent injection with special chars
- Close DB before re-initialization in InitDB
- Add migration tests (5 tests using SQLite in-memory DBs)
- Parse JSON once in GetDBConfigFromJSON instead of 7 times
- Use Go binary for dbType in shell script instead of fragile grep
- Add rollback on failure in db_switch_to_sqlite
- Validate DB settings in CheckValid
- Close migration DB connections with defer to prevent leaks
- Truncate destination tables before migration to avoid duplicates
- Wrap migration in transaction for atomicity
- Pass DB password via env var instead of CLI args to avoid process list exposure
- Improve error messages for MariaDB export/import with alternatives
- Update package doc to reflect dual DB support
- DRY migration logic with shared migrateAllTables function
- Add tests for config, database, model, util/common, util/crypto,
util/random, web/middleware, web/service, and xray packages
- Fix redirect middleware using slice instead of map to guarantee
deterministic longest-prefix-first matching order