feat(api): add GET endpoint to look up clients by Telegram ID (#5945)

* feat(api): add GET endpoint to look up clients by Telegram ID

GET /panel/api/clients/getByTgId/:tgId returns all clients matching the given Telegram user ID. tgId is not unique, so the response is an array of {client, inboundIds, externalLinks, usedTraffic} objects.

* fix: guard tgId=0 sentinel, index tg_id, deduplicate enrichment in getByTgId

Three issues from the code review on the new GET /panel/api/clients/getByTgId/:tgId
endpoint: the lookup did not short-circuit tgId <= 0 (this codebase's sentinel
for 'no Telegram ID'), had no index on clients.tg_id causing a full table scan
on every call, and duplicated the per-record enrichment (inbound IDs, external
links, effective flow, traffic) identically between get and getByTgId.

- Reject tgId <= 0 in GetRecordsByTgId with a clear error, matching the
  '0 = none' convention used elsewhere in the codebase.
- Add index:idx_clients_tg_id to ClientRecord.TgID (struct tag + idempotent
  startup migration for existing databases).
- Extract buildClientPayload helper used by both get and getByTgId.
- Update client_lookup_test.go to verify sentinel rejection instead of
  expecting tgId=0 to be a valid lookup.

* refactor(api): move Telegram client lookup under /get/tgId/:tgId

Nest the Telegram-ID lookup beside the email lookup as /get/tgId/:tgId
instead of the flat /getByTgId/:tgId, so both client fetch routes share the
/get prefix. Gin resolves the static tgId segment ahead of the :email
wildcard, so /get/:email keeps matching plain email lookups, including a
literal 'tgId' email. The endpoint is unreleased, so no compatibility
concern.
This commit is contained in:
Kim Fom
2026-07-28 21:38:44 +01:00
committed by GitHub
parent 041476a317
commit 6af2995930
7 changed files with 200 additions and 12 deletions
+14
View File
@@ -131,6 +131,9 @@ func initModels() error {
if err := migrateVmessRemovedSecurities(); err != nil {
return err
}
if err := migrateTgIDIndex(); err != nil {
return err
}
if IsPostgres() {
if err := resyncPostgresSequences(db, models); err != nil {
log.Printf("Error resyncing postgres sequences: %v", err)
@@ -884,6 +887,17 @@ func migrateVmessRemovedSecurities() error {
return nil
}
// migrateTgIDIndex creates an index on the clients.tg_id column so that
// lookups by Telegram ID do not require a full table scan. The index tag
// on the struct field already causes AutoMigrate to create it on new
// installations; the explicit migration ensures existing databases get it.
func migrateTgIDIndex() error {
if db.Migrator().HasIndex(&model.ClientRecord{}, "idx_clients_tg_id") {
return nil
}
return db.Migrator().CreateIndex(&model.ClientRecord{}, "TgID")
}
// normalizeInboundSubSortIndex lifts sub_sort_index values below the 1-based
// minimum (rows written by builds that defaulted the column to 0, or by nodes
// predating the field) so they cannot sort ahead of explicitly ranked inbounds.
+1 -1
View File
@@ -904,7 +904,7 @@ type ClientRecord struct {
TotalGB int64 `json:"totalGB" gorm:"column:total_gb"`
ExpiryTime int64 `json:"expiryTime" gorm:"column:expiry_time"`
Enable bool `json:"enable" gorm:"default:true"`
TgID int64 `json:"tgId" gorm:"column:tg_id"`
TgID int64 `json:"tgId" gorm:"column:tg_id;index:idx_clients_tg_id"`
Group string `json:"group" gorm:"column:group_name;default:'';index:idx_client_record_group"`
Comment string `json:"comment"`
Reset int `json:"reset" gorm:"default:0"`