diff --git a/modules/structs/notification.go b/modules/structs/notification.go index 06cfb7c..5b2f1a3 100644 --- a/modules/structs/notification.go +++ b/modules/structs/notification.go @@ -1,7 +1,8 @@ package structs +// swagger:model NotificationBody type NotificationBody struct { - UsersIds []string - Type uint8 - Message string + UserIds []string + Type uint8 + Message string } diff --git a/modules/structs/verifycode.go b/modules/structs/verifycode.go index 70968a3..ae73349 100644 --- a/modules/structs/verifycode.go +++ b/modules/structs/verifycode.go @@ -12,7 +12,7 @@ type VerifyCodeParams struct { UserId string } -// swagger:response VerifyCodeResponse +// swagger:model VerifyCodeResponse type VerifyCodeResponse struct { Code string } diff --git a/modules/telegram/telegram.go b/modules/telegram/telegram.go index 68a8eac..208161d 100644 --- a/modules/telegram/telegram.go +++ b/modules/telegram/telegram.go @@ -87,12 +87,7 @@ func IncomingMessagesHandler() { var foundVerifiedUser structs.VerifiedUser - res := database.DB.Where("chat_id = ?", update.Message.From.ID).First(&foundVerifiedUser) - - if res.Error != nil { - logger.AddSystemLog(rslogger.LogTypeError, "Failed to get verified user from database, err: %v", res.Error.Error()) - continue - } + database.DB.Where("chat_id = ?", update.Message.From.ID).First(&foundVerifiedUser) if foundVerifiedUser.UserId != "" { // user already verified @@ -115,15 +110,7 @@ func IncomingMessagesHandler() { } } - // reply to user - reply := tgbotapi.NewMessage(chatID, replyMessage) - - _, err := bot.Send(reply) - - if err != nil { - log.Error().Msgf("Failed to send subscribed reply to user, err: %v", err.Error()) - logger.AddSystemLog(rslogger.LogTypeError, "Failed to send reply to user, err: %v", err.Error()) - } + SendNotification(chatID, replyMessage) } else if regUnsubscribe.MatchString(text) { res := database.DB.Delete(&structs.VerifiedUser{}, "chat_id = ?", update.Message.From.ID) @@ -140,16 +127,17 @@ func IncomingMessagesHandler() { replyMessage = "You have unsubscribed from receiving notifications. Type /verify to start receiving notifications again. The code can be found in the user profile on the dashboard." } - reply := tgbotapi.NewMessage(chatID, replyMessage) - - _, err := bot.Send(reply) - - if err != nil { - logger.AddSystemLog(rslogger.LogTypeError, "Failed to send reply to user, err: %v", err.Error()) - log.Error().Msgf("Failed to send unsubscribed reply to user, err: %v", err.Error()) - } + SendNotification(chatID, replyMessage) } else { logger.AddSystemLog(rslogger.LogTypeWarning, "Received unknown message: %s from user: %s %s", text, update.Message.From.FirstName, update.Message.From.LastName) } } } + +func SendNotification(chatId int64, message string) { + msg := tgbotapi.NewMessage(chatId, message) + + if _, err := bot.Send(msg); err != nil { + logger.AddSystemLog(rslogger.LogTypeError, "Failed to send notification to chatId %v message: %s, err: %v", chatId, message, err.Error()) + } +} diff --git a/modules/utils/globals.go b/modules/utils/globals.go index 59b6799..067a097 100644 --- a/modules/utils/globals.go +++ b/modules/utils/globals.go @@ -5,4 +5,9 @@ import "time" const ( VerifyCodeLength = 6 TempVerifyCodeExpirationTime = 5 * time.Minute + + NotificationIconSuccess = "🟢" + NotificationSymbolInfo = "🔵" + NotificationSymbolWarnung = "🟠" + NotificationSymbolError = "🔴" ) diff --git a/modules/utils/utils.go b/modules/utils/utils.go new file mode 100644 index 0000000..4f67272 --- /dev/null +++ b/modules/utils/utils.go @@ -0,0 +1,28 @@ +package utils + +import "jannex/telegram-bot-manager/modules/structs" + +func GetNotificationIconByType(notificationType uint8) string { + switch notificationType { + case 1: + return NotificationIconSuccess + case 2: + return NotificationSymbolInfo + case 3: + return NotificationSymbolWarnung + case 4: + return NotificationSymbolError + default: + return "X" + } +} + +func GetUserIds(list []structs.VerifiedUser) []string { + var userIds []string + + for _, v := range list { + userIds = append(userIds, v.UserId) + } + + return userIds +} diff --git a/public/swagger/swagger.json b/public/swagger/swagger.json index 5742b2f..4b167cb 100644 --- a/public/swagger/swagger.json +++ b/public/swagger/swagger.json @@ -1,22 +1,41 @@ { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "schemes": [ - "https" - ], "swagger": "2.0", - "info": { - "title": "JNX Robot Control Manager API Documentation.", - "version": "1.0.0" - }, - "host": "jannex", - "basePath": "/v1", "paths": { - "/api/v1/robots": { + "/v1/notification": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "notification" + ], + "summary": "Send notification to users", + "operationId": "sendNotification", + "parameters": [ + { + "description": "Notification body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/NotificationBody" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad request" + } + } + } + }, + "/v1/verifycode/{userId}": { "get": { "consumes": [ "application/json" @@ -25,576 +44,64 @@ "application/json" ], "tags": [ - "robots" + "verifycode" ], - "summary": "Get all robots", - "operationId": "getRobots", + "summary": "Get verify code for user", + "operationId": "getVerifyCode", "parameters": [ { - "description": "Page number", - "name": "page", - "in": "query" - } - ], - "responses": { - "200": { - "description": "", - "schema": { - "$ref": "#/definitions/RobotsResponse" - } - } - } - } - }, - "/api/v1/urobots": { - "get": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "robots" - ], - "summary": "Get all unauthorized robots", - "operationId": "getUnauthorizedRobots", - "parameters": [ - { - "description": "Page number", - "name": "page", - "in": "query" - } - ], - "responses": { - "200": { - "description": "", - "schema": { - "$ref": "#/definitions/UnauthorizedRobotsResponse" - } - } - } - } - }, - "/control/1": { - "post": { - "description": "This is used to control Rex.\n", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "control" - ], - "summary": "Control Rex.", - "operationId": "controlRex", - "parameters": [ - { - "description": "Control Rex body.", - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/ControlBody" - } - } - ], - "responses": { - "200": { - "description": "Control Rex" - }, - "400": { - "description": "Invalid request body" - }, - "422": { - "description": "Robot not found" - } - } - } - }, - "/control/1/finish": { - "post": { - "description": "This is used to finish control Rex.\n", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "control" - ], - "summary": "Finish control Rex.", - "operationId": "finishControlRex", - "responses": { - "200": { - "description": "Finish control Rex" - }, - "400": { - "description": "Invalid robot name" - }, - "422": { - "description": "Robot not found" - } - } - } - }, - "/permitjoin": { - "get": { - "description": "This is used to get the current permit join status.\n", - "produces": [ - "application/json" - ], - "tags": [ - "permitjoin" - ], - "summary": "Get permit join.", - "operationId": "getPermitJoin", - "responses": { - "200": { - "description": "Permit join status", - "schema": { - "$ref": "#/definitions/PermitJoinResponse" - } - } - } - } - }, - "/permitjoin/{enabled}": { - "post": { - "description": "This is used to enable or disable permit join.\n", - "tags": [ - "permitjoin" - ], - "summary": "Set permit join.", - "operationId": "setPermitJoin", - "parameters": [ - { - "description": "Enable or disable permit join. 0 = disable, 1 = enable.", - "name": "enabled", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "enum": [ - 0, - 1 - ] - } - } - ], - "responses": { - "200": { - "description": "Permit join set" - }, - "400": { - "description": "Invalid request body" - } - } - } - }, - "/robot": { - "post": { - "description": "This is the first request from the robot. It will be used to identify the robot.\n", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "robot" - ], - "summary": "First request from robot.", - "operationId": "robotFirstRequest", - "parameters": [ - { - "description": "First request body.", - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/FirstRequestBody" - } - } - ], - "responses": { - "200": { - "description": "Robot identified", - "schema": { - "$ref": "#/definitions/StatusResponse" - } - }, - "400": { - "description": "Invalid request body" - }, - "403": { - "description": "Permit join is enabled" - } - } - }, - "patch": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "robot" - ], - "summary": "Update robot.", - "operationId": "robotUpdate", - "parameters": [ - { - "description": "Update robot body.", - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/Robot" - } - } - ], - "responses": { - "200": { - "description": "Robot updated" - }, - "400": { - "description": "Invalid request body" - }, - "422": { - "description": "Robot not found" - } - } - } - }, - "/robot/authorize/{robotId}": { - "post": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "robot" - ], - "summary": "Authorize robot.", - "operationId": "robotAuthorize", - "parameters": [ - { - "description": "Robot id.", - "name": "robotId", + "type": "string", + "description": "User ID", + "name": "userId", "in": "params", - "required": true, - "schema": { - "type": "string" - } + "required": true } ], "responses": { "200": { - "description": "Robot authorized" - }, - "400": { - "description": "Invalid robot id" - }, - "422": { - "description": "Robot not found" - } - } - } - }, - "/robot/deny/{robotId}": { - "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "robot" - ], - "summary": "Deny unauthorized robot.", - "operationId": "robotDenyUnauthorizedRobot", - "parameters": [ - { - "description": "Robot id.", - "name": "robotId", - "in": "params", - "required": true, + "description": "OK", "schema": { - "type": "string" + "$ref": "#/definitions/VerifyCodeResponse" } - } - ], - "responses": { - "200": { - "description": "Robot denied" }, "400": { - "description": "Invalid robot id" - } - } - } - }, - "/robot/fuj/{robotId}": { - "patch": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "robot" - ], - "summary": "Free up job from robot.", - "operationId": "robotFreeUpJob", - "parameters": [ - { - "description": "Robot id.", - "name": "robotId", - "in": "params", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Job freed up" + "description": "Bad request" }, - "400": { - "description": "Invalid robot id" - } - } - } - }, - "/robot/{robotId}": { - "delete": { - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "robot" - ], - "summary": "Delete robot.", - "operationId": "robotDelete", - "parameters": [ - { - "description": "Robot id.", - "name": "robotId", - "in": "params", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Robot deleted" - }, - "400": { - "description": "Invalid robot id" + "500": { + "description": "Internal server error" } } } } }, "definitions": { - "APIRobot": { + "NotificationBody": { "type": "object", "properties": { - "Address": { + "Message": { "type": "string" }, - "ConnectedAt": { - "type": "string", - "format": "date-time" - }, - "CreatedAt": { - "type": "string", - "format": "date-time" - }, - "CurrentJobName": { - "type": "string" - }, - "FirmwareVersion": { - "type": "string" - }, - "Id": { - "type": "string" - }, - "JobsWaitingCount": { + "Type": { "type": "integer", - "format": "int64" + "format": "uint8" }, - "JobsWaitingNameList": { + "UserIds": { "type": "array", "items": { "type": "string" } - }, - "Name": { - "type": "string" - }, - "Status": { - "type": "integer", - "format": "uint8" - }, - "Type": { - "type": "integer", - "format": "uint8" } }, - "x-go-package": "jannex/robot-control-manager/modules/structs" + "x-go-package": "jannex/telegram-bot-manager/modules/structs" }, - "ControlBody": { + "VerifyCodeResponse": { "type": "object", "properties": { - "JobId": { + "Code": { "type": "string" - }, - "JobName": { - "type": "string" - }, - "RobotName": { - "type": "string" - }, - "Task": { - "$ref": "#/definitions/ControlTask" } }, - "x-go-package": "jannex/robot-control-manager/modules/structs" - }, - "ControlTask": { - "type": "object", - "properties": { - "ConnectedModule": { - "type": "integer", - "format": "int64" - }, - "X": { - "type": "integer", - "format": "int64" - }, - "Y": { - "type": "integer", - "format": "int64" - }, - "Z": { - "type": "integer", - "format": "int64" - } - }, - "x-go-package": "jannex/robot-control-manager/modules/structs" - }, - "FirstRequestBody": { - "type": "object", - "properties": { - "FirmwareVersion": { - "description": "used firmware version of the robot", - "type": "string" - }, - "Id": { - "description": "robot id", - "type": "string" - }, - "Type": { - "description": "robot type", - "type": "integer", - "format": "uint8" - } - }, - "x-go-package": "jannex/robot-control-manager/modules/structs" - }, - "PermitJoinResponse": { - "type": "object", - "properties": { - "Enabled": { - "type": "boolean" - } - }, - "x-go-package": "jannex/robot-control-manager/modules/structs" - }, - "RobotsResponse": { - "type": "object", - "properties": { - "Robots": { - "type": "array", - "items": { - "$ref": "#/definitions/APIRobot" - } - }, - "TotalPages": { - "type": "integer", - "format": "int64" - } - }, - "x-go-package": "jannex/robot-control-manager/modules/structs" - }, - "StatusResponse": { - "type": "object", - "properties": { - "Status": { - "type": "integer", - "format": "int64" - } - }, - "x-go-package": "jannex/robot-control-manager/modules/structs" - }, - "UnauthorizedRobot": { - "type": "object", - "properties": { - "Address": { - "type": "string" - }, - "ConnectedAt": { - "type": "string", - "format": "date-time" - }, - "CreatedAt": { - "type": "string", - "format": "date-time" - }, - "FirmwareVersion": { - "type": "string" - }, - "Id": { - "type": "string" - }, - "Type": { - "type": "integer", - "format": "uint8" - } - }, - "x-go-package": "jannex/robot-control-manager/modules/structs" - }, - "UnauthorizedRobotsResponse": { - "type": "object", - "properties": { - "TotalPages": { - "type": "integer", - "format": "int64" - }, - "UnauthorizedRobots": { - "type": "array", - "items": { - "$ref": "#/definitions/UnauthorizedRobot" - } - } - }, - "x-go-package": "jannex/robot-control-manager/modules/structs" + "x-go-package": "jannex/telegram-bot-manager/modules/structs" } } } \ No newline at end of file diff --git a/routers/api/v1/notification/notification.go b/routers/api/v1/notification/notification.go index 170ebae..c17dff2 100644 --- a/routers/api/v1/notification/notification.go +++ b/routers/api/v1/notification/notification.go @@ -1,18 +1,57 @@ package notification import ( + "jannex/telegram-bot-manager/modules/database" + "jannex/telegram-bot-manager/modules/logger" "jannex/telegram-bot-manager/modules/structs" + "jannex/telegram-bot-manager/modules/telegram" + "jannex/telegram-bot-manager/modules/utils" + "git.ex.umbach.dev/Alex/roese-utils/rslogger" "git.ex.umbach.dev/Alex/roese-utils/rsutils" "github.com/gofiber/fiber/v2" ) func SendNotification(c *fiber.Ctx) error { + // swagger:operation POST /v1/notification notification sendNotification + // --- + // summary: Send notification to users + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: body + // in: body + // description: Notification body + // required: true + // schema: + // "$ref": "#/definitions/NotificationBody" + // responses: + // '200': + // description: OK + // '400': + // description: Bad request + var body structs.NotificationBody if err := rsutils.BodyParserHelper(c, &body); err != nil { return c.SendStatus(fiber.StatusBadRequest) } + var foundVerifiedUsers []structs.VerifiedUser + + // get all verified users by body userIds + database.DB.Where("user_id IN ?", body.UserIds).Find(&foundVerifiedUsers) + + formattedMessage := utils.GetNotificationIconByType(body.Type) + " " + body.Message + + // send message to all verified users + for _, user := range foundVerifiedUsers { + telegram.SendNotification(int64(user.ChatId), formattedMessage) + } + + logger.AddSystemLog(rslogger.LogTypeInfo, "Sent notification: %s to %d users: %v", formattedMessage, len(foundVerifiedUsers), utils.GetUserIds(foundVerifiedUsers)) + return c.SendStatus(fiber.StatusOK) } diff --git a/routers/router/router.go b/routers/router/router.go index 3bc5b67..81248cf 100644 --- a/routers/router/router.go +++ b/routers/router/router.go @@ -1,6 +1,7 @@ package router import ( + "jannex/telegram-bot-manager/routers/api/v1/notification" "jannex/telegram-bot-manager/routers/api/v1/verifycode" "github.com/gofiber/fiber/v2" @@ -13,6 +14,7 @@ func SetupRoutes(app *fiber.App) { vc.Get("/:userId", verifycode.GetVerifyCode) n := v1.Group("/notification") + n.Post("/", notification.SendNotification) app.Static("/", "./public/") }