mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-12 02:06:37 +08:00
feat(message-gateway): add user bind and unbind APIs
This commit is contained in:
+254
@@ -4574,6 +4574,205 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/message-gateway/bindings": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns the current user's bound messaging channels",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message-gateway"
|
||||||
|
],
|
||||||
|
"summary": "List message gateway bindings",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/message_gateway.BindingDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Binds the current user to a platform identity using a one-time pairing code",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message-gateway"
|
||||||
|
],
|
||||||
|
"summary": "Bind a messaging channel",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "bind body",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/message_gateway.BindRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/message_gateway.BindingDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"409": {
|
||||||
|
"description": "Conflict",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/message-gateway/bindings/{id}": {
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Removes a binding owned by the current user",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message-gateway"
|
||||||
|
],
|
||||||
|
"summary": "Unbind a messaging channel",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "binding id",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/message-gateway/channels": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns enabled system bots the current user can pair with",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message-gateway"
|
||||||
|
],
|
||||||
|
"summary": "List enabled messaging channels",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/message_gateway.PublicChannelDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/oauth/callback": {
|
"/api/v1/oauth/callback": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "接收前端传回的 state 和 code,完成 OAuth/OIDC 认证并建立会话。支持登录(login)和账号绑定(bind)两种场景。",
|
"description": "接收前端传回的 state 和 code,完成 OAuth/OIDC 认证并建立会话。支持登录(login)和账号绑定(bind)两种场景。",
|
||||||
@@ -6432,6 +6631,46 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"message_gateway.BindRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"channel_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"message_gateway.BindingDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"channel_id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "0"
|
||||||
|
},
|
||||||
|
"channel_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"channel_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "0"
|
||||||
|
},
|
||||||
|
"platform_user_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"message_gateway.ChannelDTO": {
|
"message_gateway.ChannelDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -6537,6 +6776,21 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"message_gateway.PublicChannelDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "0"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"message_gateway.UpdateChannelRequest": {
|
"message_gateway.UpdateChannelRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -4567,6 +4567,205 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/message-gateway/bindings": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns the current user's bound messaging channels",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message-gateway"
|
||||||
|
],
|
||||||
|
"summary": "List message gateway bindings",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/message_gateway.BindingDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Binds the current user to a platform identity using a one-time pairing code",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message-gateway"
|
||||||
|
],
|
||||||
|
"summary": "Bind a messaging channel",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "bind body",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/message_gateway.BindRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/message_gateway.BindingDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"409": {
|
||||||
|
"description": "Conflict",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/message-gateway/bindings/{id}": {
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Removes a binding owned by the current user",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message-gateway"
|
||||||
|
],
|
||||||
|
"summary": "Unbind a messaging channel",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "binding id",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/message-gateway/channels": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"SessionCookie": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns enabled system bots the current user can pair with",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"message-gateway"
|
||||||
|
],
|
||||||
|
"summary": "List enabled messaging channels",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/message_gateway.PublicChannelDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/oauth/callback": {
|
"/api/v1/oauth/callback": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "接收前端传回的 state 和 code,完成 OAuth/OIDC 认证并建立会话。支持登录(login)和账号绑定(bind)两种场景。",
|
"description": "接收前端传回的 state 和 code,完成 OAuth/OIDC 认证并建立会话。支持登录(login)和账号绑定(bind)两种场景。",
|
||||||
@@ -6425,6 +6624,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"message_gateway.BindRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"channel_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"message_gateway.BindingDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"channel_id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "0"
|
||||||
|
},
|
||||||
|
"channel_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"channel_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "0"
|
||||||
|
},
|
||||||
|
"platform_user_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"message_gateway.ChannelDTO": {
|
"message_gateway.ChannelDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -6530,6 +6769,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"message_gateway.PublicChannelDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "0"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"message_gateway.UpdateChannelRequest": {
|
"message_gateway.UpdateChannelRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -327,6 +327,33 @@ definitions:
|
|||||||
date:
|
date:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
message_gateway.BindRequest:
|
||||||
|
properties:
|
||||||
|
channel_id:
|
||||||
|
type: string
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
message_gateway.BindingDTO:
|
||||||
|
properties:
|
||||||
|
channel_id:
|
||||||
|
example: "0"
|
||||||
|
type: string
|
||||||
|
channel_name:
|
||||||
|
type: string
|
||||||
|
channel_type:
|
||||||
|
type: string
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
example: "0"
|
||||||
|
type: string
|
||||||
|
platform_user_id:
|
||||||
|
type: string
|
||||||
|
user_id:
|
||||||
|
example: "0"
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
message_gateway.ChannelDTO:
|
message_gateway.ChannelDTO:
|
||||||
properties:
|
properties:
|
||||||
app_id:
|
app_id:
|
||||||
@@ -396,6 +423,16 @@ definitions:
|
|||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
message_gateway.PublicChannelDTO:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
example: "0"
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
message_gateway.UpdateChannelRequest:
|
message_gateway.UpdateChannelRequest:
|
||||||
properties:
|
properties:
|
||||||
app_id:
|
app_id:
|
||||||
@@ -4252,6 +4289,124 @@ paths:
|
|||||||
summary: Sample Hello API
|
summary: Sample Hello API
|
||||||
tags:
|
tags:
|
||||||
- custom
|
- custom
|
||||||
|
/api/v1/message-gateway/bindings:
|
||||||
|
get:
|
||||||
|
description: Returns the current user's bound messaging channels
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.Any'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/message_gateway.BindingDTO'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Any'
|
||||||
|
security:
|
||||||
|
- SessionCookie: []
|
||||||
|
summary: List message gateway bindings
|
||||||
|
tags:
|
||||||
|
- message-gateway
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Binds the current user to a platform identity using a one-time
|
||||||
|
pairing code
|
||||||
|
parameters:
|
||||||
|
- description: bind body
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/message_gateway.BindRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.Any'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/message_gateway.BindingDTO'
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Any'
|
||||||
|
"409":
|
||||||
|
description: Conflict
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Any'
|
||||||
|
security:
|
||||||
|
- SessionCookie: []
|
||||||
|
summary: Bind a messaging channel
|
||||||
|
tags:
|
||||||
|
- message-gateway
|
||||||
|
/api/v1/message-gateway/bindings/{id}:
|
||||||
|
delete:
|
||||||
|
description: Removes a binding owned by the current user
|
||||||
|
parameters:
|
||||||
|
- description: binding id
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Any'
|
||||||
|
"403":
|
||||||
|
description: Forbidden
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Any'
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Any'
|
||||||
|
security:
|
||||||
|
- SessionCookie: []
|
||||||
|
summary: Unbind a messaging channel
|
||||||
|
tags:
|
||||||
|
- message-gateway
|
||||||
|
/api/v1/message-gateway/channels:
|
||||||
|
get:
|
||||||
|
description: Returns enabled system bots the current user can pair with
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/response.Any'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/message_gateway.PublicChannelDTO'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Any'
|
||||||
|
security:
|
||||||
|
- SessionCookie: []
|
||||||
|
summary: List enabled messaging channels
|
||||||
|
tags:
|
||||||
|
- message-gateway
|
||||||
/api/v1/oauth/{source}/authorize:
|
/api/v1/oauth/{source}/authorize:
|
||||||
get:
|
get:
|
||||||
description: 根据指定认证源名称发起 OAuth 授权,支持 purpose 参数用于区分登录和账号绑定场景。认证源必须已启用。
|
description: 根据指定认证源名称发起 OAuth 授权,支持 purpose 参数用于区分登录和账号绑定场景。认证源必须已启用。
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package message_gateway
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
errCodeInvalid = errors.New("invalid or expired pairing code")
|
||||||
|
errChannelMismatch = errors.New("pairing code does not match channel")
|
||||||
|
errPlatformAlreadyBound = errors.New("this platform account is already bound")
|
||||||
|
errBindingNotFound = errors.New("binding not found")
|
||||||
|
errBindingForbidden = errors.New("cannot unbind another user's binding")
|
||||||
|
errChannelIDRequired = errors.New("channel_id is required")
|
||||||
|
errChannelDisabled = errors.New("channel is not enabled")
|
||||||
|
)
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package message_gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/apps/oauth"
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/shared/response"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func currentUser(c *gin.Context) (*model.User, bool) {
|
||||||
|
return oauth.GetFromContext[*model.User](c, oauth.UserObjKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListChannels lists enabled channels a user can bind.
|
||||||
|
// @Summary List enabled messaging channels
|
||||||
|
// @Description Returns enabled system bots the current user can pair with
|
||||||
|
// @Tags message-gateway
|
||||||
|
// @Produce json
|
||||||
|
// @Security SessionCookie
|
||||||
|
// @Success 200 {object} response.Any{data=[]PublicChannelDTO}
|
||||||
|
// @Failure 401 {object} response.Any
|
||||||
|
// @Router /api/v1/message-gateway/channels [get]
|
||||||
|
func ListChannels(c *gin.Context) {
|
||||||
|
if user, ok := currentUser(c); !ok || user == nil {
|
||||||
|
response.AbortUnauthorized(c, "login required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rows, err := listEnabledPublicChannels(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
response.AbortInternal(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, response.OK(rows))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListBindings lists the current user's bot bindings.
|
||||||
|
// @Summary List message gateway bindings
|
||||||
|
// @Description Returns the current user's bound messaging channels
|
||||||
|
// @Tags message-gateway
|
||||||
|
// @Produce json
|
||||||
|
// @Security SessionCookie
|
||||||
|
// @Success 200 {object} response.Any{data=[]BindingDTO}
|
||||||
|
// @Failure 401 {object} response.Any
|
||||||
|
// @Router /api/v1/message-gateway/bindings [get]
|
||||||
|
func ListBindings(c *gin.Context) {
|
||||||
|
user, ok := currentUser(c)
|
||||||
|
if !ok || user == nil {
|
||||||
|
response.AbortUnauthorized(c, "login required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rows, err := listUserBindings(c.Request.Context(), user.ID)
|
||||||
|
if err != nil {
|
||||||
|
response.AbortInternal(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, response.OK(rows))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindBinding consumes a pairing code and binds the platform identity.
|
||||||
|
// @Summary Bind a messaging channel
|
||||||
|
// @Description Binds the current user to a platform identity using a one-time pairing code
|
||||||
|
// @Tags message-gateway
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security SessionCookie
|
||||||
|
// @Param request body BindRequest true "bind body"
|
||||||
|
// @Success 200 {object} response.Any{data=BindingDTO}
|
||||||
|
// @Failure 400 {object} response.Any
|
||||||
|
// @Failure 409 {object} response.Any
|
||||||
|
// @Router /api/v1/message-gateway/bindings [post]
|
||||||
|
func BindBinding(c *gin.Context) {
|
||||||
|
user, ok := currentUser(c)
|
||||||
|
if !ok || user == nil {
|
||||||
|
response.AbortUnauthorized(c, "login required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req BindRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.AbortBadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dto, err := bindChannel(c.Request.Context(), user.ID, req)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, errPlatformAlreadyBound) {
|
||||||
|
response.AbortConflict(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.AbortBadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, response.OK(dto))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnbindBinding removes the current user's binding.
|
||||||
|
// @Summary Unbind a messaging channel
|
||||||
|
// @Description Removes a binding owned by the current user
|
||||||
|
// @Tags message-gateway
|
||||||
|
// @Produce json
|
||||||
|
// @Security SessionCookie
|
||||||
|
// @Param id path int true "binding id"
|
||||||
|
// @Success 200 {object} response.Any
|
||||||
|
// @Failure 403 {object} response.Any
|
||||||
|
// @Failure 404 {object} response.Any
|
||||||
|
// @Router /api/v1/message-gateway/bindings/{id} [delete]
|
||||||
|
func UnbindBinding(c *gin.Context) {
|
||||||
|
user, ok := currentUser(c)
|
||||||
|
if !ok || user == nil {
|
||||||
|
response.AbortUnauthorized(c, "login required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.AbortBadRequest(c, "invalid binding id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := unbindChannel(c.Request.Context(), user.ID, id); err != nil {
|
||||||
|
if errors.Is(err, errBindingNotFound) {
|
||||||
|
response.AbortNotFound(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.Is(err, errBindingForbidden) {
|
||||||
|
response.AbortForbidden(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.AbortInternal(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, response.OKNil())
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package message_gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||||
|
pkgmg "github.com/Rain-kl/Wavelet/pkg/message_gateway"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BindRequest is the user bind body.
|
||||||
|
type BindRequest struct {
|
||||||
|
ChannelID string `json:"channel_id"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingDTO is a user-facing binding row.
|
||||||
|
type BindingDTO struct {
|
||||||
|
ID uint64 `json:"id,string"`
|
||||||
|
UserID uint64 `json:"user_id,string"`
|
||||||
|
ChannelID uint64 `json:"channel_id,string"`
|
||||||
|
ChannelName string `json:"channel_name"`
|
||||||
|
ChannelType string `json:"channel_type"`
|
||||||
|
PlatformUserID string `json:"platform_user_id"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindChannel(ctx context.Context, userID uint64, req BindRequest) (BindingDTO, error) {
|
||||||
|
channelID, err := strconv.ParseUint(strings.TrimSpace(req.ChannelID), 10, 64)
|
||||||
|
if err != nil || channelID == 0 {
|
||||||
|
return BindingDTO{}, errChannelIDRequired
|
||||||
|
}
|
||||||
|
code := pkgmg.NormalizeCode(req.Code)
|
||||||
|
if code == "" {
|
||||||
|
return BindingDTO{}, errCodeInvalid
|
||||||
|
}
|
||||||
|
pairing, err := repository.GetPairingCode(ctx, code)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return BindingDTO{}, errCodeInvalid
|
||||||
|
}
|
||||||
|
return BindingDTO{}, err
|
||||||
|
}
|
||||||
|
if !pairing.ExpiresAt.After(time.Now()) {
|
||||||
|
return BindingDTO{}, errCodeInvalid
|
||||||
|
}
|
||||||
|
if pairing.ChannelID != channelID {
|
||||||
|
return BindingDTO{}, errChannelMismatch
|
||||||
|
}
|
||||||
|
ch, err := repository.GetMessageChannel(ctx, channelID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return BindingDTO{}, errCodeInvalid
|
||||||
|
}
|
||||||
|
return BindingDTO{}, err
|
||||||
|
}
|
||||||
|
if !ch.Enabled {
|
||||||
|
return BindingDTO{}, errChannelDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
existing, err := repository.GetBindingByChannelPlatform(ctx, channelID, pairing.PlatformUserID)
|
||||||
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return BindingDTO{}, err
|
||||||
|
}
|
||||||
|
if err == nil && existing != nil {
|
||||||
|
if existing.UserID != userID {
|
||||||
|
return BindingDTO{}, errPlatformAlreadyBound
|
||||||
|
}
|
||||||
|
_ = repository.DeletePairingCode(ctx, pairing.Code)
|
||||||
|
return toBindingDTO(existing, ch), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
row := &model.MessageBinding{
|
||||||
|
UserID: userID,
|
||||||
|
ChannelID: channelID,
|
||||||
|
PlatformUserID: pairing.PlatformUserID,
|
||||||
|
}
|
||||||
|
if err := repository.CreateMessageBinding(ctx, row); err != nil {
|
||||||
|
return BindingDTO{}, err
|
||||||
|
}
|
||||||
|
if err := repository.DeletePairingCode(ctx, pairing.Code); err != nil {
|
||||||
|
return BindingDTO{}, err
|
||||||
|
}
|
||||||
|
return toBindingDTO(row, ch), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicChannelDTO is an enabled channel a user can bind to.
|
||||||
|
type PublicChannelDTO struct {
|
||||||
|
ID uint64 `json:"id,string"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func listEnabledPublicChannels(ctx context.Context) ([]PublicChannelDTO, error) {
|
||||||
|
rows, err := repository.ListEnabledMessageChannels(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out := make([]PublicChannelDTO, 0, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
out = append(out, PublicChannelDTO{ID: row.ID, Name: row.Name, Type: row.Type})
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func listUserBindings(ctx context.Context, userID uint64) ([]BindingDTO, error) {
|
||||||
|
rows, err := repository.ListBindingsByUser(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out := make([]BindingDTO, 0, len(rows))
|
||||||
|
for i := range rows {
|
||||||
|
ch, err := repository.GetMessageChannel(ctx, rows[i].ChannelID)
|
||||||
|
if err != nil {
|
||||||
|
out = append(out, toBindingDTO(&rows[i], nil))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, toBindingDTO(&rows[i], ch))
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unbindChannel(ctx context.Context, userID, bindingID uint64) error {
|
||||||
|
row, err := repository.GetMessageBinding(ctx, bindingID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return errBindingNotFound
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if row.UserID != userID {
|
||||||
|
return errBindingForbidden
|
||||||
|
}
|
||||||
|
return repository.DeleteMessageBinding(ctx, bindingID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toBindingDTO(row *model.MessageBinding, ch *model.MessageChannel) BindingDTO {
|
||||||
|
dto := BindingDTO{
|
||||||
|
ID: row.ID,
|
||||||
|
UserID: row.UserID,
|
||||||
|
ChannelID: row.ChannelID,
|
||||||
|
PlatformUserID: row.PlatformUserID,
|
||||||
|
CreatedAt: row.CreatedAt,
|
||||||
|
}
|
||||||
|
if ch != nil {
|
||||||
|
dto.ChannelName = ch.Name
|
||||||
|
dto.ChannelType = ch.Type
|
||||||
|
}
|
||||||
|
return dto
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package message_gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func seedChannel(t *testing.T, ctx context.Context) *model.MessageChannel {
|
||||||
|
t.Helper()
|
||||||
|
ch := &model.MessageChannel{
|
||||||
|
Name: "tg",
|
||||||
|
Type: model.MessageChannelTypeTelegram,
|
||||||
|
OwnerScope: model.MessageOwnerScopeSystem,
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
if err := repository.CreateMessageChannel(ctx, ch); err != nil {
|
||||||
|
t.Fatalf("CreateMessageChannel() error = %v", err)
|
||||||
|
}
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBind_ExpiredCode(t *testing.T) {
|
||||||
|
_, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||||
|
defer cleanup()
|
||||||
|
ctx := context.Background()
|
||||||
|
ch := seedChannel(t, ctx)
|
||||||
|
if _, err := repository.UpsertPairingCode(ctx, ch.ID, "u1", "ABCD2345", time.Now().Add(-time.Minute)); err != nil {
|
||||||
|
t.Fatalf("UpsertPairingCode() error = %v", err)
|
||||||
|
}
|
||||||
|
_, err := bindChannel(ctx, 1, BindRequest{ChannelID: fmt.Sprint(ch.ID), Code: "ABCD-2345"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("bindChannel() error = nil, want expired code")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBind_HappyPathDeletesCode(t *testing.T) {
|
||||||
|
_, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||||
|
defer cleanup()
|
||||||
|
ctx := context.Background()
|
||||||
|
ch := seedChannel(t, ctx)
|
||||||
|
if _, err := repository.UpsertPairingCode(ctx, ch.ID, "plat-1", "ABCD2345", time.Now().Add(15*time.Minute)); err != nil {
|
||||||
|
t.Fatalf("UpsertPairingCode() error = %v", err)
|
||||||
|
}
|
||||||
|
dto, err := bindChannel(ctx, 42, BindRequest{ChannelID: fmt.Sprint(ch.ID), Code: "abcd-2345"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bindChannel() error = %v", err)
|
||||||
|
}
|
||||||
|
if dto.PlatformUserID != "plat-1" || dto.ChannelID != ch.ID || dto.UserID != 42 {
|
||||||
|
t.Fatalf("bindChannel() dto = %+v", dto)
|
||||||
|
}
|
||||||
|
_, err = repository.GetPairingCode(ctx, "ABCD2345")
|
||||||
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
t.Fatalf("GetPairingCode() error = %v, want not found", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBind_ConflictOtherUser(t *testing.T) {
|
||||||
|
_, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||||
|
defer cleanup()
|
||||||
|
ctx := context.Background()
|
||||||
|
ch := seedChannel(t, ctx)
|
||||||
|
if err := repository.CreateMessageBinding(ctx, &model.MessageBinding{
|
||||||
|
UserID: 7, ChannelID: ch.ID, PlatformUserID: "plat-1",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("CreateMessageBinding() error = %v", err)
|
||||||
|
}
|
||||||
|
if _, err := repository.UpsertPairingCode(ctx, ch.ID, "plat-1", "ABCD2345", time.Now().Add(15*time.Minute)); err != nil {
|
||||||
|
t.Fatalf("UpsertPairingCode() error = %v", err)
|
||||||
|
}
|
||||||
|
_, err := bindChannel(ctx, 42, BindRequest{ChannelID: fmt.Sprint(ch.ID), Code: "ABCD2345"})
|
||||||
|
if !errors.Is(err, errPlatformAlreadyBound) {
|
||||||
|
t.Fatalf("bindChannel() error = %v, want %v", err, errPlatformAlreadyBound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnbind_OnlyOwnBinding(t *testing.T) {
|
||||||
|
_, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||||
|
defer cleanup()
|
||||||
|
ctx := context.Background()
|
||||||
|
ch := seedChannel(t, ctx)
|
||||||
|
b := &model.MessageBinding{UserID: 7, ChannelID: ch.ID, PlatformUserID: "plat-1"}
|
||||||
|
if err := repository.CreateMessageBinding(ctx, b); err != nil {
|
||||||
|
t.Fatalf("CreateMessageBinding() error = %v", err)
|
||||||
|
}
|
||||||
|
if err := unbindChannel(ctx, 42, b.ID); err == nil {
|
||||||
|
t.Fatal("unbindChannel() error = nil, want forbidden")
|
||||||
|
}
|
||||||
|
if err := unbindChannel(ctx, 7, b.ID); err != nil {
|
||||||
|
t.Fatalf("unbindChannel() own binding error = %v", err)
|
||||||
|
}
|
||||||
|
_, err := repository.GetMessageBinding(ctx, b.ID)
|
||||||
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
t.Fatalf("GetMessageBinding() error = %v, want deleted", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Package message_gateway provides user bind/unbind APIs and credential helpers.
|
||||||
|
package message_gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Rain-kl/Wavelet/internal/apps/oauth"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterUserRoutes mounts login-required bind/unbind APIs.
|
||||||
|
func RegisterUserRoutes(apiV1Router *gin.RouterGroup) {
|
||||||
|
g := apiV1Router.Group("/message-gateway")
|
||||||
|
g.Use(oauth.LoginRequired())
|
||||||
|
{
|
||||||
|
g.GET("/channels", ListChannels)
|
||||||
|
g.GET("/bindings", ListBindings)
|
||||||
|
g.POST("/bindings", BindBinding)
|
||||||
|
g.DELETE("/bindings/:id", UnbindBinding)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
appgw "github.com/Rain-kl/Wavelet/internal/apps/message_gateway"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterMessageGatewayUserRoutes mounts user bind/unbind APIs.
|
||||||
|
func RegisterMessageGatewayUserRoutes(apiV1Router *gin.RouterGroup) {
|
||||||
|
appgw.RegisterUserRoutes(apiV1Router)
|
||||||
|
}
|
||||||
@@ -16,6 +16,9 @@ func RegisterV1Routes(apiV1Router *gin.RouterGroup, apiGroup *gin.RouterGroup) {
|
|||||||
// 2. Admin routes
|
// 2. Admin routes
|
||||||
RegisterAdminRoutes(apiV1Router)
|
RegisterAdminRoutes(apiV1Router)
|
||||||
|
|
||||||
|
// 3. Message gateway user bind/unbind
|
||||||
|
RegisterMessageGatewayUserRoutes(apiV1Router)
|
||||||
|
|
||||||
// 3. Product domain routes: RegisterXxxRoutes(apiV1Router) — see skill new-api
|
// 3. Product domain routes: RegisterXxxRoutes(apiV1Router) — see skill new-api
|
||||||
// 4. Scaffold sample only (optional demo under /api/v1/custom)
|
// 4. Scaffold sample only (optional demo under /api/v1/custom)
|
||||||
RegisterCustomRoutes(apiV1Router)
|
RegisterCustomRoutes(apiV1Router)
|
||||||
|
|||||||
Reference in New Issue
Block a user