Skip to content

Diagnostic API ​

/api/diag/* exposes admin-only diagnostic endpoints used to debug production data inconsistencies (typical scenario: user_plans.plan_id always shows 0). All endpoints require Admin.

Endpoint index ​

EndpointMethodAuthDescription
/api/diag/subscriptionsGETAdminCompare user_plans against orders to locate dirty rows

1. Subscription Diagnostics ​

Endpoint: GET /api/diag/subscriptions

Auth: Admin

Description: Scans the first 100 user_plans rows and joins each with its orders row (when order_id > 0) and a brief users profile, so you can triage anomalies like plan_id <= 0. The endpoint is read-only and never mutates data.

Response:

json
{
  "success": true,
  "message": "",
  "data": {
    "total_user_plans": 100,
    "missing_plan_id": 2,
    "rows": [
      {
        "user_plan": {
          "id": 42,
          "user_id": 7,
          "plan_id": 1,
          "order_id": 88,
          "start_time": 1718000000,
          "end_time": 1720592000,
          "status": 1,
          "billing_type": "token",
          "created_time": 1718000000,
          "updated_time": 1718000000
        },
        "order_no": "TB20250912153000123456",
        "order_plan_id": 1,
        "order_type": 1,
        "user": {
          "id": 7,
          "username": "alice",
          "display_name": "Alice",
          "email": "alice@example.com",
          "role": 1,
          "status": 1
        }
      },
      {
        "user_plan": {
          "id": 43,
          "user_id": 9,
          "plan_id": 0,
          "order_id": 0,
          "start_time": 0,
          "end_time": 0,
          "status": 1,
          "billing_type": "token",
          "created_time": 1718000000,
          "updated_time": 1718000000
        },
        "order_no": "",
        "order_plan_id": 0,
        "order_type": 0,
        "user": {
          "id": 9,
          "username": "bob",
          "display_name": "Bob",
          "email": "bob@example.com",
          "role": 1,
          "status": 1
        }
      }
    ]
  }
}

Fields:

FieldTypeDescription
total_user_plansintNumber of user_plans rows returned (capped at 100)
missing_plan_idintCount of rows with plan_id &lt;= 0 (suspected dirty data)
rowsarrayOne row per scanned user_plan, joined with order + user brief

Per-row fields:

FieldTypeDescription
user_planobjectRaw UserPlan row
order_nostringSource order number; empty when order_id &lt;= 0
order_plan_idintplan_id on the source order
order_typeintOrder type (1=plan subscription, 2=topup)
userobjectUserBrief: id / username / display_name / email / role / status

The order row is only loaded when user_plan.order_id > 0; otherwise the order fields are zero / empty.

Typical usage:

bash
# 1. List the first 100 user_plans to spot dirty rows
curl -s 'http://localhost:3000/api/diag/subscriptions' -b cookies.txt | jq

# 2. When missing_plan_id > 0, look up the trigger path by user_plan.id:
#    - plan_id = 0   : order.plan_id was missing/overwritten at activation time
#    - order_id = 0  : orphan user_plan (order hard-deleted without cascade cleanup)