mirror of
https://github.com/orris-inc/orris.git
synced 2026-06-03 01:41:41 +08:00
- Remove NodeGroup domain entity, repository, and all related usecases - Remove NodeGroup HTTP handlers and routes - Update SubscriptionPlanRepository to PlanRepository across codebase - Rename SubscriptionTrafficItem to SubscriptionUsageItem in DTOs - Fix JSON column default value issue in migration (MySQL compatibility) - Remove unused migration files (002, 003) - Update NODE_API.md to remove node group documentation - Add entitlement mapper and repository implementations
864 lines
18 KiB
Markdown
864 lines
18 KiB
Markdown
# Node API Documentation
|
|
|
|
Node management API for proxy node configuration and subscription generation.
|
|
|
|
## Base URL
|
|
|
|
```
|
|
/nodes - Node management
|
|
/s - Subscription endpoints
|
|
```
|
|
|
|
## Authentication
|
|
|
|
### Admin API (Node Management)
|
|
|
|
All management endpoints require JWT Bearer token authentication with admin role.
|
|
|
|
**Request Header**:
|
|
```
|
|
Authorization: Bearer <jwt_token>
|
|
```
|
|
|
|
### Subscription API
|
|
|
|
Subscription endpoints use token-based authentication via URL path parameter.
|
|
|
|
**Format**: `GET /s/{subscription_uuid}`
|
|
|
|
---
|
|
|
|
## 1. Node Management
|
|
|
|
### 1.1 Create Node
|
|
|
|
Create a new proxy node.
|
|
|
|
**Request**
|
|
|
|
```
|
|
POST /nodes
|
|
Authorization: Bearer <jwt_token>
|
|
Content-Type: application/json
|
|
```
|
|
|
|
**Request Body**
|
|
|
|
```json
|
|
{
|
|
"name": "US-Node-01",
|
|
"server_address": "proxy.example.com",
|
|
"server_port": 8388,
|
|
"protocol": "shadowsocks",
|
|
"encryption_method": "aes-256-gcm",
|
|
"plugin": "obfs-local",
|
|
"plugin_opts": {
|
|
"obfs": "http",
|
|
"obfs-host": "example.com"
|
|
},
|
|
"region": "us-west",
|
|
"tags": ["premium", "fast"],
|
|
"description": "High-speed US server",
|
|
"sort_order": 1
|
|
}
|
|
```
|
|
|
|
**Request Fields**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `name` | string | Yes | Node display name (unique) |
|
|
| `server_address` | string | Yes | Server hostname or IP address |
|
|
| `server_port` | uint16 | Yes | Server port (1-65535) |
|
|
| `protocol` | string | Yes | Protocol type: `shadowsocks`, `trojan` |
|
|
| `encryption_method` | string | Yes | Encryption method |
|
|
| `plugin` | string | No | Plugin name (e.g., `obfs-local`, `v2ray-plugin`) |
|
|
| `plugin_opts` | object | No | Plugin configuration options |
|
|
| `region` | string | No | Geographic region identifier |
|
|
| `tags` | array | No | Custom tags for categorization |
|
|
| `description` | string | No | Node description |
|
|
| `sort_order` | int | No | Display order for sorting |
|
|
|
|
**Supported Encryption Methods**
|
|
|
|
| Protocol | Methods |
|
|
|----------|---------|
|
|
| shadowsocks | `aes-256-gcm`, `aes-128-gcm`, `chacha20-ietf-poly1305` |
|
|
| trojan | N/A (uses TLS) |
|
|
|
|
**Response**
|
|
|
|
**Success (201)**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Node created successfully",
|
|
"data": {
|
|
"id": 1,
|
|
"name": "US-Node-01",
|
|
"server_address": "proxy.example.com",
|
|
"server_port": 8388,
|
|
"protocol": "shadowsocks",
|
|
"encryption_method": "aes-256-gcm",
|
|
"plugin": "obfs-local",
|
|
"plugin_opts": {"obfs": "http", "obfs-host": "example.com"},
|
|
"status": "inactive",
|
|
"region": "us-west",
|
|
"tags": ["premium", "fast"],
|
|
"sort_order": 1,
|
|
"is_available": false,
|
|
"version": 1,
|
|
"created_at": "2024-01-15T10:30:00Z",
|
|
"updated_at": "2024-01-15T10:30:00Z",
|
|
"api_token": "node_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
}
|
|
}
|
|
```
|
|
|
|
> **Important**: The `api_token` is only returned once during creation. Store it securely for node status reporting.
|
|
|
|
---
|
|
|
|
### 1.2 List Nodes
|
|
|
|
Get a paginated list of nodes.
|
|
|
|
**Request**
|
|
|
|
```
|
|
GET /nodes?page=1&page_size=20&status=active
|
|
Authorization: Bearer <jwt_token>
|
|
```
|
|
|
|
**Query Parameters**
|
|
|
|
| Parameter | Type | Default | Description |
|
|
|-----------|------|---------|-------------|
|
|
| `page` | int | 1 | Page number |
|
|
| `page_size` | int | 20 | Items per page (max: 100) |
|
|
| `status` | string | - | Filter: `active`, `inactive`, `maintenance` |
|
|
| `region` | string | - | Filter by region |
|
|
| `tags` | string | - | Filter by tags (comma-separated) |
|
|
| `order_by` | string | sort_order | Sort field |
|
|
| `order` | string | asc | Sort direction: `asc`, `desc` |
|
|
|
|
**Response**
|
|
|
|
**Success (200)**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"items": [
|
|
{
|
|
"id": 1,
|
|
"name": "US-Node-01",
|
|
"server_address": "proxy.example.com",
|
|
"server_port": 8388,
|
|
"protocol": "shadowsocks",
|
|
"encryption_method": "aes-256-gcm",
|
|
"status": "active",
|
|
"region": "us-west",
|
|
"tags": ["premium", "fast"],
|
|
"sort_order": 1,
|
|
"is_available": true,
|
|
"version": 1,
|
|
"created_at": "2024-01-15T10:30:00Z",
|
|
"updated_at": "2024-01-15T14:20:00Z",
|
|
"system_status": {
|
|
"cpu": "45.50",
|
|
"memory": "65.30",
|
|
"disk": "80.20",
|
|
"uptime": 86400,
|
|
"updated_at": 1705324800
|
|
}
|
|
}
|
|
],
|
|
"total": 50,
|
|
"page": 1,
|
|
"page_size": 20,
|
|
"total_pages": 3
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 1.3 Get Node
|
|
|
|
Get details of a specific node.
|
|
|
|
**Request**
|
|
|
|
```
|
|
GET /nodes/{id}
|
|
Authorization: Bearer <jwt_token>
|
|
```
|
|
|
|
**Response**
|
|
|
|
**Success (200)**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"id": 1,
|
|
"name": "US-Node-01",
|
|
"server_address": "proxy.example.com",
|
|
"server_port": 8388,
|
|
"protocol": "shadowsocks",
|
|
"encryption_method": "aes-256-gcm",
|
|
"plugin": "obfs-local",
|
|
"plugin_opts": {"obfs": "http"},
|
|
"status": "active",
|
|
"region": "us-west",
|
|
"tags": ["premium", "fast"],
|
|
"sort_order": 1,
|
|
"is_available": true,
|
|
"version": 1,
|
|
"created_at": "2024-01-15T10:30:00Z",
|
|
"updated_at": "2024-01-15T14:20:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Not Found (404)**
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "Node not found",
|
|
"error": {
|
|
"code": "NOT_FOUND",
|
|
"message": "Node with ID 999 not found"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 1.4 Update Node
|
|
|
|
Update node information.
|
|
|
|
**Request**
|
|
|
|
```
|
|
PUT /nodes/{id}
|
|
Authorization: Bearer <jwt_token>
|
|
Content-Type: application/json
|
|
```
|
|
|
|
**Request Body** (all fields optional)
|
|
|
|
```json
|
|
{
|
|
"name": "US-Node-01-Updated",
|
|
"server_address": "new-proxy.example.com",
|
|
"server_port": 8389,
|
|
"encryption_method": "chacha20-ietf-poly1305",
|
|
"plugin": "v2ray-plugin",
|
|
"plugin_opts": {"mode": "websocket"},
|
|
"region": "us-east",
|
|
"tags": ["premium", "low-latency"],
|
|
"description": "Updated description",
|
|
"sort_order": 2
|
|
}
|
|
```
|
|
|
|
**Response**
|
|
|
|
**Success (200)**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Node updated successfully",
|
|
"data": {
|
|
"id": 1,
|
|
"name": "US-Node-01-Updated",
|
|
"server_address": "new-proxy.example.com",
|
|
"server_port": 8389,
|
|
"status": "active",
|
|
"version": 2,
|
|
"updated_at": "2024-01-15T16:00:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 1.5 Update Node Status
|
|
|
|
Update node operational status.
|
|
|
|
**Request**
|
|
|
|
```
|
|
PATCH /nodes/{id}/status
|
|
Authorization: Bearer <jwt_token>
|
|
Content-Type: application/json
|
|
```
|
|
|
|
**Request Body**
|
|
|
|
```json
|
|
{
|
|
"status": "active"
|
|
}
|
|
```
|
|
|
|
**Status Values**
|
|
|
|
| Status | Description |
|
|
|--------|-------------|
|
|
| `active` | Node is active and available for use |
|
|
| `inactive` | Node is disabled |
|
|
| `maintenance` | Node is under maintenance |
|
|
|
|
**Status Transition Rules**
|
|
|
|
```
|
|
inactive → active
|
|
active → inactive, maintenance
|
|
maintenance → active, inactive
|
|
```
|
|
|
|
**Response**
|
|
|
|
**Success (200)**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Node status updated successfully",
|
|
"data": {
|
|
"id": 1,
|
|
"status": "active",
|
|
"is_available": true
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 1.6 Generate Node Token
|
|
|
|
Generate a new API token for node authentication.
|
|
|
|
**Request**
|
|
|
|
```
|
|
POST /nodes/{id}/tokens
|
|
Authorization: Bearer <jwt_token>
|
|
```
|
|
|
|
**Response**
|
|
|
|
**Success (200)**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Token generated successfully",
|
|
"data": {
|
|
"node_id": 1,
|
|
"token": "node_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
}
|
|
}
|
|
```
|
|
|
|
> **Important**: The previous token will be invalidated. Store the new token securely.
|
|
|
|
**Token Format**: `node_<base64_encoded_random_bytes>`
|
|
|
|
---
|
|
|
|
### 1.7 Delete Node
|
|
|
|
Delete a node permanently.
|
|
|
|
**Request**
|
|
|
|
```
|
|
DELETE /nodes/{id}
|
|
Authorization: Bearer <jwt_token>
|
|
```
|
|
|
|
**Response**
|
|
|
|
**Success (204)**: No content
|
|
|
|
---
|
|
|
|
## 2. Subscription Endpoints
|
|
|
|
Public endpoints for fetching subscription configurations in various formats.
|
|
|
|
### 2.1 Base64 Subscription (Default)
|
|
|
|
Get subscription in Base64-encoded format.
|
|
|
|
**Request**
|
|
|
|
```
|
|
GET /s/{token}
|
|
```
|
|
|
|
**Response**
|
|
|
|
**Success (200)**
|
|
|
|
```
|
|
Content-Type: text/plain
|
|
|
|
c3M6Ly9ZV1Z6TFRJMU5pMW5ZMjBLYUc1aGNIQjVMbVY0WVcxd2JHVXVZMjl0T2pnek9EZz0=
|
|
```
|
|
|
|
The Base64 content decodes to Shadowsocks/Trojan URIs, one per line:
|
|
```
|
|
ss://YWVzLTI1Ni1nY20KaG5hcHB5LmV4YW1wbGUuY29tOjgzODg=
|
|
ss://YWVzLTI1Ni1nY20KaG5hcHB5Mi5leGFtcGxlLmNvbTo4Mzg5
|
|
```
|
|
|
|
---
|
|
|
|
### 2.2 Clash Subscription
|
|
|
|
Get subscription in Clash YAML format.
|
|
|
|
**Request**
|
|
|
|
```
|
|
GET /s/{token}/clash
|
|
```
|
|
|
|
**Response**
|
|
|
|
**Success (200)**
|
|
|
|
```yaml
|
|
Content-Type: text/yaml
|
|
|
|
proxies:
|
|
- name: "US-Node-01"
|
|
type: ss
|
|
server: proxy.example.com
|
|
port: 8388
|
|
cipher: aes-256-gcm
|
|
password: "subscription_uuid"
|
|
plugin: obfs
|
|
plugin-opts:
|
|
mode: http
|
|
host: example.com
|
|
|
|
proxy-groups:
|
|
- name: "Proxy"
|
|
type: select
|
|
proxies:
|
|
- "US-Node-01"
|
|
```
|
|
|
|
---
|
|
|
|
### 2.3 V2Ray Subscription
|
|
|
|
Get subscription in V2Ray JSON format.
|
|
|
|
**Request**
|
|
|
|
```
|
|
GET /s/{token}/v2ray
|
|
```
|
|
|
|
**Response**
|
|
|
|
**Success (200)**
|
|
|
|
```json
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"outbounds": [
|
|
{
|
|
"protocol": "shadowsocks",
|
|
"settings": {
|
|
"servers": [
|
|
{
|
|
"address": "proxy.example.com",
|
|
"port": 8388,
|
|
"method": "aes-256-gcm",
|
|
"password": "subscription_uuid"
|
|
}
|
|
]
|
|
},
|
|
"tag": "US-Node-01"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2.4 SIP008 Subscription
|
|
|
|
Get subscription in Shadowsocks SIP008 format.
|
|
|
|
**Request**
|
|
|
|
```
|
|
GET /s/{token}/sip008
|
|
```
|
|
|
|
**Response**
|
|
|
|
**Success (200)**
|
|
|
|
```json
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"version": 1,
|
|
"servers": [
|
|
{
|
|
"id": "uuid-1",
|
|
"remarks": "US-Node-01",
|
|
"server": "proxy.example.com",
|
|
"server_port": 8388,
|
|
"method": "aes-256-gcm",
|
|
"password": "subscription_uuid",
|
|
"plugin": "obfs-local",
|
|
"plugin_opts": "obfs=http;obfs-host=example.com"
|
|
}
|
|
],
|
|
"bytes_used": 1073741824,
|
|
"bytes_remaining": 9663676416
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 2.5 Surge Subscription
|
|
|
|
Get subscription in Surge configuration format.
|
|
|
|
**Request**
|
|
|
|
```
|
|
GET /s/{token}/surge
|
|
```
|
|
|
|
**Response**
|
|
|
|
**Success (200)**
|
|
|
|
```ini
|
|
Content-Type: text/plain
|
|
|
|
[Proxy]
|
|
US-Node-01 = ss, proxy.example.com, 8388, encrypt-method=aes-256-gcm, password=subscription_uuid, obfs=http, obfs-host=example.com
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Response Data Structures
|
|
|
|
### NodeDTO
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `id` | uint | Unique node identifier |
|
|
| `name` | string | Node display name |
|
|
| `server_address` | string | Server hostname or IP |
|
|
| `server_port` | uint16 | Server port number |
|
|
| `protocol` | string | Protocol: `shadowsocks`, `trojan` |
|
|
| `encryption_method` | string | Encryption method |
|
|
| `plugin` | string | Plugin name (optional) |
|
|
| `plugin_opts` | object | Plugin options (optional) |
|
|
| `status` | string | Status: `active`, `inactive`, `maintenance` |
|
|
| `region` | string | Geographic region |
|
|
| `tags` | array | Custom tags |
|
|
| `sort_order` | int | Display order |
|
|
| `maintenance_reason` | string | Maintenance reason (if status is maintenance) |
|
|
| `is_available` | bool | Current availability |
|
|
| `version` | int | Version for optimistic locking |
|
|
| `created_at` | string | Creation timestamp (ISO 8601) |
|
|
| `updated_at` | string | Last update timestamp (ISO 8601) |
|
|
| `system_status` | object | Real-time system metrics (optional) |
|
|
|
|
### NodeSystemStatusDTO
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `cpu` | string | CPU usage percentage |
|
|
| `memory` | string | Memory usage percentage |
|
|
| `disk` | string | Disk usage percentage |
|
|
| `uptime` | int | Uptime in seconds |
|
|
| `updated_at` | int64 | Last update timestamp (Unix) |
|
|
|
|
---
|
|
|
|
## 4. Error Codes
|
|
|
|
| HTTP Status | Code | Description |
|
|
|-------------|------|-------------|
|
|
| 400 | VALIDATION_ERROR | Invalid request body or parameters |
|
|
| 401 | UNAUTHORIZED | Missing or invalid authentication |
|
|
| 403 | FORBIDDEN | Insufficient permissions (admin required) |
|
|
| 404 | NOT_FOUND | Resource not found |
|
|
| 409 | CONFLICT | Resource conflict (e.g., duplicate name) |
|
|
| 500 | INTERNAL_ERROR | Server-side error |
|
|
|
|
**Error Response Format**
|
|
|
|
```json
|
|
{
|
|
"success": false,
|
|
"message": "Error description",
|
|
"error": {
|
|
"code": "ERROR_CODE",
|
|
"message": "Detailed error message"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Node Token Authentication
|
|
|
|
For node status reporting and heartbeat, nodes authenticate using API tokens.
|
|
|
|
### Token Format
|
|
|
|
```
|
|
node_<base64_encoded_random_bytes>
|
|
```
|
|
|
|
### Authentication Methods
|
|
|
|
**1. Authorization Header (Recommended)**
|
|
```
|
|
Authorization: Bearer node_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
```
|
|
|
|
**2. Query Parameter**
|
|
```
|
|
GET /endpoint?token=node_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
```
|
|
|
|
**3. X-Node-Token Header (RESTful)**
|
|
```
|
|
X-Node-Token: node_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
```
|
|
|
|
### Token Lifecycle
|
|
|
|
1. Token is generated when creating a node
|
|
2. Token can be regenerated via `POST /nodes/{id}/tokens`
|
|
3. Only the hash is stored; plaintext is returned once
|
|
4. Old token is invalidated when regenerating
|
|
|
|
---
|
|
|
|
## 6. Client Implementation Example
|
|
|
|
### Go Client
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
baseURL = "https://api.example.com"
|
|
)
|
|
|
|
type NodeClient struct {
|
|
client *http.Client
|
|
token string
|
|
}
|
|
|
|
func NewNodeClient(token string) *NodeClient {
|
|
return &NodeClient{
|
|
client: &http.Client{Timeout: 10 * time.Second},
|
|
token: token,
|
|
}
|
|
}
|
|
|
|
// CreateNode creates a new proxy node
|
|
func (c *NodeClient) CreateNode(req CreateNodeRequest) (*NodeResponse, error) {
|
|
body, err := json.Marshal(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
httpReq, err := http.NewRequest("POST", baseURL+"/nodes", bytes.NewReader(body))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
httpReq.Header.Set("Authorization", "Bearer "+c.token)
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.client.Do(httpReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result NodeResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// ListNodes retrieves paginated node list
|
|
func (c *NodeClient) ListNodes(page, pageSize int, status string) (*ListNodesResponse, error) {
|
|
url := fmt.Sprintf("%s/nodes?page=%d&page_size=%d", baseURL, page, pageSize)
|
|
if status != "" {
|
|
url += "&status=" + status
|
|
}
|
|
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+c.token)
|
|
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result ListNodesResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// UpdateNodeStatus updates node operational status
|
|
func (c *NodeClient) UpdateNodeStatus(nodeID uint, status string) error {
|
|
body, _ := json.Marshal(map[string]string{"status": status})
|
|
|
|
req, err := http.NewRequest("PATCH",
|
|
fmt.Sprintf("%s/nodes/%d/status", baseURL, nodeID),
|
|
bytes.NewReader(body))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+c.token)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("failed to update status: %d", resp.StatusCode)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Types
|
|
type CreateNodeRequest struct {
|
|
Name string `json:"name"`
|
|
ServerAddress string `json:"server_address"`
|
|
ServerPort uint16 `json:"server_port"`
|
|
Protocol string `json:"protocol"`
|
|
EncryptionMethod string `json:"encryption_method"`
|
|
Plugin string `json:"plugin,omitempty"`
|
|
PluginOpts map[string]string `json:"plugin_opts,omitempty"`
|
|
Region string `json:"region,omitempty"`
|
|
Tags []string `json:"tags,omitempty"`
|
|
SortOrder int `json:"sort_order,omitempty"`
|
|
}
|
|
|
|
type NodeResponse struct {
|
|
Success bool `json:"success"`
|
|
Message string `json:"message"`
|
|
Data Node `json:"data"`
|
|
}
|
|
|
|
type Node struct {
|
|
ID uint `json:"id"`
|
|
Name string `json:"name"`
|
|
ServerAddress string `json:"server_address"`
|
|
ServerPort uint16 `json:"server_port"`
|
|
Protocol string `json:"protocol"`
|
|
EncryptionMethod string `json:"encryption_method"`
|
|
Status string `json:"status"`
|
|
APIToken string `json:"api_token,omitempty"`
|
|
}
|
|
|
|
type ListNodesResponse struct {
|
|
Success bool `json:"success"`
|
|
Data struct {
|
|
Items []Node `json:"items"`
|
|
Total int `json:"total"`
|
|
Page int `json:"page"`
|
|
PageSize int `json:"page_size"`
|
|
TotalPages int `json:"total_pages"`
|
|
} `json:"data"`
|
|
}
|
|
|
|
func main() {
|
|
client := NewNodeClient("your-jwt-token")
|
|
|
|
// Create a new node
|
|
node, err := client.CreateNode(CreateNodeRequest{
|
|
Name: "US-Node-01",
|
|
ServerAddress: "proxy.example.com",
|
|
ServerPort: 8388,
|
|
Protocol: "shadowsocks",
|
|
EncryptionMethod: "aes-256-gcm",
|
|
Region: "us-west",
|
|
Tags: []string{"premium"},
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Printf("Created node: %s (ID: %d)\n", node.Data.Name, node.Data.ID)
|
|
fmt.Printf("API Token: %s\n", node.Data.APIToken)
|
|
|
|
// Activate the node
|
|
if err := client.UpdateNodeStatus(node.Data.ID, "active"); err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println("Node activated")
|
|
|
|
// List active nodes
|
|
nodes, err := client.ListNodes(1, 20, "active")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Printf("Found %d active nodes\n", nodes.Data.Total)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Notes
|
|
|
|
1. **Password Handling**: For Shadowsocks nodes, an HMAC-SHA256 signed password (derived from subscription UUID) is used when generating subscription URIs. The original UUID is never exposed to agents.
|
|
2. **Rate Limiting**: Subscription endpoints have rate limiting enabled to prevent abuse
|
|
3. **Optimistic Locking**: Use `version` parameter when updating to prevent concurrent modification conflicts
|
|
4. **Token Security**: Node API tokens should be treated like passwords and stored securely
|
|
5. **Status Transitions**: Follow the state machine rules when changing node status
|
|
6. **Batch Operations**: Maximum 100 items per batch request for adding/removing nodes from groups
|