package socketclients import ( "encoding/base64" "janex/admin-dashboard-backend/modules/cache" "janex/admin-dashboard-backend/modules/database" "janex/admin-dashboard-backend/modules/logger" "janex/admin-dashboard-backend/modules/structs" "janex/admin-dashboard-backend/modules/systempermissions" "janex/admin-dashboard-backend/modules/utils" "os" "strconv" "time" "github.com/gofiber/websocket/v2" "github.com/google/uuid" "github.com/rs/zerolog/log" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) func BroadcastMessage(sendSocketMessage structs.SendSocketMessage) { for _, client := range cache.GetSocketClients() { client.SendMessage(sendSocketMessage) } } func BroadcastMessageExceptUserSessionId(ignoreUserSessionId string, sendSocketMessage structs.SendSocketMessage) { for _, client := range cache.GetSocketClients() { if client.SessionId != ignoreUserSessionId { client.SendMessage(sendSocketMessage) } } } func BroadcastMessageExceptUserId(ignoreUserId string, sendSocketMessage structs.SendSocketMessage) { for _, client := range cache.GetSocketClients() { if client.UserId != ignoreUserId { client.SendMessage(sendSocketMessage) } } } func BroadcastMessageToUsersWithPermission(neededPermission string, sendSocketMessage structs.SendSocketMessage) { var rolePermissions []structs.RolePermission database.DB.Model(&structs.RolePermission{}).Where("permission_id = ?", neededPermission).Find(&rolePermissions) var foundUsers []structs.User for _, rolePermission := range rolePermissions { var users []structs.User database.DB.Where("role_id = ?", rolePermission.RoleId).Select("id").Find(&users) foundUsers = append(foundUsers, users...) } for _, foundUser := range foundUsers { SendMessageToUser(foundUser.Id, "", sendSocketMessage) } } func SendMessageToUser(userId string, ignoreUserSessionId string, sendSocketMessage structs.SendSocketMessage) { for _, client := range cache.GetSocketClients() { if client.UserId == userId && client.SessionId != ignoreUserSessionId { client.SendMessage(sendSocketMessage) } } } func SendMessageOnlyToSessionId(sessionId string, sendSocketMessage structs.SendSocketMessage) { for _, client := range cache.GetSocketClients() { if client.SessionId == sessionId { client.SendMessage(sendSocketMessage) } } } func SendMessageToUsersByRoleId(roleId string, sendSocketMessage structs.SendSocketMessage) { for _, client := range cache.GetSocketClients() { var user structs.User database.DB.Where("id = ?", client.UserId).First(&user) if user.RoleId == roleId { client.SendMessage(sendSocketMessage) } } } // This close all connections that are connected with one session id. // For example when a user has two browser tabs opened func CloseAllUserSessionConnections(sessionId string) { for _, client := range cache.GetSocketClients() { if client.SessionId == sessionId { client.SendSessionClosedMessage() } } } // Used to close all user connections // For example when a user changed his password func CloseAndDeleteAllUserConnections(userId string) { for _, client := range cache.GetSocketClients() { if client.UserId == userId { client.SendSessionClosedMessage() } } database.DB.Where("user_id = ?", userId).Delete(&structs.UserSession{}) } func UpdateConnectedUsers(userId string) { var user structs.User database.DB.First(&user, "id = ?", userId) BroadcastMessage(structs.SendSocketMessage{ Cmd: utils.SentCmdUpdateConnectedUsers, Body: struct { WebSocketUsersCount int UserId string ConnectionStatus uint8 LastOnline time.Time }{ WebSocketUsersCount: len(cache.GetSocketClients()), UserId: userId, ConnectionStatus: isUserGenerallyConnected(userId), LastOnline: user.LastOnline, }, }) } func GetUserSessions(userId string) []structs.UserSessionSocket { var userSessions []structs.UserSession database.DB.Where("user_id = ?", userId).Find(&userSessions) var userSessionsSocket []structs.UserSessionSocket socketClients := cache.GetSocketClients() for _, userSession := range userSessions { userSessionsSocket = append(userSessionsSocket, structs.UserSessionSocket{ IdForDeletion: userSession.IdForDeletion, UserAgent: userSession.UserAgent, ConnectionStatus: isUserSessionConnected(userSession.Id, socketClients), LastUsed: userSession.LastUsed, ExpiresAt: userSession.ExpiresAt, }) } return userSessionsSocket } func UpdateUserSessionsForUser(userId string, ignoreUserSessionId string) { GetUserSessions(userId) SendMessageToUser(userId, ignoreUserSessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdUpdateUserSessions, Body: GetUserSessions(userId), }) } func isUserSessionConnected(userSessionId string, socketClients []*structs.SocketClient) uint8 { for _, socketClient := range socketClients { if socketClient.SessionId == userSessionId { return 1 } } return 0 } // Used to determine if a user is connected regardless of the session used func isUserGenerallyConnected(userId string) uint8 { for _, socketClient := range cache.GetSocketClients() { if socketClient.UserId == userId { return utils.ConnectionStateOnline } } return utils.ConnectionStateOffline } // Get all users from database. // This is used in the UI to display all users. func GetAllUsers() []structs.AllUsers { var users []structs.User var allUsers []structs.AllUsers database.DB.Find(&users) for _, user := range users { allUsers = append(allUsers, structs.AllUsers{ Id: user.Id, RoleId: user.RoleId, Avatar: user.Avatar, Username: user.Username, ConnectionStatus: isUserGenerallyConnected(user.Id), Deactivated: user.Deactivated, LastOnline: user.LastOnline, }) } return allUsers } func GetAllScanners() []structs.Scanner { var scanners []structs.Scanner var allScanners []structs.Scanner database.DB.Find(&scanners) for _, scanner := range scanners { // clear session to prevent leaking and sending to ui scanner.Session = "" allScanners = append(allScanners, scanner) } return allScanners } func isUsernameAvailable(username string) bool { var user structs.User database.DB.Select("username").Where("username = ?", username).Find(&user) return user.Username == "" } func isEmailAvailable(email string) bool { var user structs.User database.DB.Select("email").Where("email = ?", email).Find(&user) return user.Email == "" } func UpdateUserProfile(conn *websocket.Conn, changes map[string]interface{}) { sessionId := conn.Locals("sessionId").(string) userId := conn.Locals("userId").(string) var user structs.User var updates = make(map[string]interface{}) if changes["username"] != nil { username := changes["username"].(string) if isValueLenValid(username, utils.MinUsername, utils.MaxUsername) { // only affected if username was manipulated as min and max is provided in web ui if isUsernameAvailable(username) { user.Username = username updates["Username"] = username } else { SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdUserProfileUpdated, Body: struct { UserId string Result uint8 }{ UserId: userId, Result: 0, }, }) return } } } if changes["email"] != nil { email := changes["email"].(string) if isEmailAvailable(email) { user.Email = email updates["Email"] = email } else { SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdUserProfileUpdated, Body: struct { UserId string Result uint8 }{ UserId: userId, Result: 1, }, }) return } } if changes["oldPassword"] != nil && changes["newPassword"] != nil { oldPassword := changes["oldPassword"].(string) newPassword := changes["newPassword"].(string) decodedOldPassword, err := base64.StdEncoding.DecodeString(oldPassword) decodedNewPassword, err1 := base64.StdEncoding.DecodeString(newPassword) if err != nil || err1 != nil { log.Error().Msgf("Error decoding old or new password %s %s", err.Error(), err1.Error()) SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdUserProfileUpdated, Body: struct { UserId string Result uint8 }{ UserId: userId, Result: 2, }, }) return } if utils.IsPasswordLengthValid(string(decodedOldPassword)) { // only affected if username was manipulated as min and max is provided in web ui database.DB.Select("password").First(&user, "id = ?", userId) if err := bcrypt.CompareHashAndPassword([]byte(user.Password), decodedOldPassword); err == nil { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(decodedNewPassword), bcrypt.DefaultCost) if err == nil { user.Password = string(hashedPassword) } else { log.Error().Msgf("Failed to generate hash password %s", err.Error()) } } else { log.Error().Msg("Incorrect password") SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdUserProfileUpdated, Body: struct { UserId string Result uint8 }{ UserId: userId, Result: 2, }, }) return } } } if len(changes) > 0 { user.UpdatedAt = time.Now() database.DB.Model(&structs.User{}).Where("id = ?", userId).Updates(user) if changes["username"] != nil || changes["email"] != nil || changes["oldPassword"] != nil && changes["newPassword"] != nil { if changes["oldPassword"] != nil && changes["newPassword"] != nil { // user has changed password - logout all his sessions CloseAndDeleteAllUserConnections(userId) logger.AddSystemLog(structs.LogMessage{ Id: 12, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "userId", Value: userId}, }, }) return } SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdUserProfileUpdated, Body: struct { UserId string Changes map[string]interface{} }{ UserId: userId, Changes: updates, }, }) BroadcastMessageExceptUserSessionId(sessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdUserProfileUpdated, Body: struct { UserId string Changes map[string]interface{} }{ UserId: userId, Changes: updates, }, }) logger.AddSystemLog(structs.LogMessage{ Id: 13, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "userId", Value: userId}, {Type: "changes", Value: utils.MarshalJson(updates)}, }, }) } } } func isValueLenValid(value string, min int, max int) bool { l := len(value) return l >= min && l <= max } func GetAllRoles() []structs.Role { var roles []structs.Role database.DB.Order("sorting_order").Find(&roles) return roles } func GetPermissionsByRoleId(roleId string) []string { var rolePermissions []structs.RolePermission database.DB.Where("role_id = ?", roleId).Find(&rolePermissions) var permissions []string for _, rolePermission := range rolePermissions { permissions = append(permissions, rolePermission.PermissionId) } return permissions } // Retrieve all roles with a list of all permissions for each role func GetAdminAreaRolesPermissions() []structs.RolePermissions { roles := GetAllRoles() var rolesPermissions []structs.RolePermission database.DB.Find(&rolesPermissions) var rolePermissions []structs.RolePermissions for _, role := range roles { var permissions []string for _, rolePermission := range rolesPermissions { if rolePermission.RoleId == role.Id { permissions = append(permissions, rolePermission.PermissionId) } } rolePermissions = append(rolePermissions, structs.RolePermissions{ RoleId: role.Id, Permissions: permissions, }) } return rolePermissions } func isRoleDisplayNameAvailable(roleDisplayName string) bool { var role structs.Role database.DB.Select("display_name").Where("display_name = ?", roleDisplayName).Find(&role) return role.DisplayName == "" } func AdminAreaUpdateRole(conn *websocket.Conn, body map[string]interface{}) { if body["RoleId"] == nil { log.Error().Msgf("No role id specified in update role %v", body) return } roleId := body["RoleId"].(string) changes := body["Changes"].(map[string]interface{}) // user has nothing changed if changes["DisplayName"] == nil && changes["Description"] == nil && changes["AddedPermissions"] == nil && changes["RemovedPermissions"] == nil { log.Error().Msgf("User has not specified anything to update the role %v", body) return } updatedRole := structs.Role{ Id: roleId, } var changesResult = make(map[string]uint8) var updates = make(map[string]interface{}) if changes["DisplayName"] != nil && isValueLenValid(changes["DisplayName"].(string), utils.MinRoleDisplayName, utils.MaxRoleDisplayName) { if isRoleDisplayNameAvailable(changes["DisplayName"].(string)) { updatedRole.DisplayName = changes["DisplayName"].(string) updates["DisplayName"] = changes["DisplayName"].(string) } else { changesResult["DisplayName"] = 1 } } if changes["Description"] != nil { updatedRole.Description = changes["Description"].(string) updates["Description"] = changes["Description"].(string) } if changes["AddedPermissions"] != nil { addedPermissions := changes["AddedPermissions"].([]interface{}) updates["AddedPermissions"] = addedPermissions var dbAddedPermissions []structs.RolePermission for _, addedPermission := range addedPermissions { dbAddedPermissions = append(dbAddedPermissions, structs.RolePermission{ RoleId: roleId, PermissionId: addedPermission.(string), }) } database.DB.Create(dbAddedPermissions) } if changes["RemovedPermissions"] != nil { removedPermissions := changes["RemovedPermissions"].([]interface{}) updates["RemovedPermissions"] = removedPermissions for _, removedPermission := range removedPermissions { database.DB.Where("role_id = ?", roleId).Where("permission_id = ?", removedPermission.(string)).Delete(&structs.RolePermission{}) } } database.DB.Model(&structs.Role{}).Where("id = ?", roleId).Updates(&updatedRole) sessionId := conn.Locals("sessionId").(string) SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdAdminAreaRoleUpdated, Body: struct { RoleId string Changes map[string]interface{} Result map[string]uint8 }{ RoleId: roleId, Changes: updates, Result: changesResult, }, }) BroadcastMessageExceptUserSessionId(sessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdAdminAreaRoleUpdated, Body: struct { RoleId string Changes map[string]interface{} }{ RoleId: roleId, Changes: updates, }, }) SendMessageToUsersByRoleId(roleId, structs.SendSocketMessage{ Cmd: utils.SentCmdRolePermissionsUpdated, Body: changes, }) logger.AddSystemLog(structs.LogMessage{ Id: 2, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "userId", Value: conn.Locals("userId").(string)}, {Type: "roleId", Value: roleId}, {Type: "changes", Value: utils.MarshalJson(updates)}, }, }) } func AdminAreaMoveRoleToSortingOrder(conn *websocket.Conn, body map[string]interface{}) { roleId := body["RoleId"].(string) direction := int(body["Direction"].(float64)) var role structs.Role if err := database.DB.First(&role, "id = ?", roleId).Error; err != nil { log.Error().Msgf("Failed to get role %s", err.Error()) return } currentSortingOrder := role.SortingOrder var newSortingOrder int if direction == 1 { newSortingOrder = currentSortingOrder + 1 if newSortingOrder > systempermissions.GetRoleSortingOrder()-1 { return } } else { newSortingOrder = currentSortingOrder - 1 if newSortingOrder < 0 { return } } // If the new sorting order number is smaller than the current sorting order number, // increase the sorting order numbers of all roles between the new and current sorting order by 1 if newSortingOrder < currentSortingOrder { if err := database.DB.Model(&structs.Role{}).Where("sorting_order >= ? AND sorting_order < ?", newSortingOrder, currentSortingOrder).Update("sorting_order", gorm.Expr("sorting_order + 1")).Error; err != nil { log.Error().Msgf("Failed to update sorting order upwards %s", err.Error()) return } } else if newSortingOrder > currentSortingOrder { // If the new sorting order number is larger than the current sorting order number, // decrease the sorting order numbers of all roles between the current and new sorting order by 1. if err := database.DB.Model(&structs.Role{}).Where("sorting_order > ? AND sorting_order <= ?", currentSortingOrder, newSortingOrder).Update("sorting_order", gorm.Expr("sorting_order - 1")).Error; err != nil { log.Error().Msgf("Failed to update sorting order downwards %s", err.Error()) return } } // Update the sorting order number of the moved role if err := database.DB.Model(&role).Update("sorting_order", newSortingOrder).Error; err != nil { log.Debug().Msgf("Failed to update sorting order %s", err.Error()) return } BroadcastMessage(structs.SendSocketMessage{ Cmd: utils.SentCmdAdminAreaUpdateRoleSortingOrder, Body: body, }) logger.AddSystemLog(structs.LogMessage{ Id: 3, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "userId", Value: conn.Locals("userId").(string)}, {Type: "roleId", Value: role.Id}, {Type: "sortingOrder", Value: strconv.Itoa(newSortingOrder)}, }, }) } func AdminAreaDeleteRole(conn *websocket.Conn, body map[string]interface{}) { roleId := body["RoleId"].(string) var role structs.Role database.DB.Where("id = ?", roleId).First(&role) if err := database.DB.Model(&structs.Role{}).Where("sorting_order > ?", role.SortingOrder).Update("sorting_order", gorm.Expr("sorting_order - 1")).Error; err != nil { log.Error().Msgf("Failed to update role sorting order after delete role %s", err.Error()) return } database.DB.Where("id = ?", roleId).Delete(&structs.Role{}) database.DB.Where("role_id = ?", roleId).Delete(&structs.RolePermission{}) BroadcastMessage(structs.SendSocketMessage{ Cmd: utils.SentCmdAdminAreaRoleDeleted, Body: body, }) logger.AddSystemLog(structs.LogMessage{ Id: 4, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "userId", Value: conn.Locals("userId").(string)}, {Type: "roleId", Value: role.Id}, }, }) } func UpdateUserRole(conn *websocket.Conn, userId string, roleId string) { database.DB.Model(&structs.User{}).Where("id = ?", userId).Updates(structs.User{ RoleId: roleId, }) SendMessageToUser(userId, "", structs.SendSocketMessage{ Cmd: utils.SentCmdAllUsersUserRoleUpdated, Body: struct { UserId string RoleId string Permissions []string }{ UserId: userId, RoleId: roleId, Permissions: GetPermissionsByRoleId(roleId), }, }) BroadcastMessageExceptUserId(userId, structs.SendSocketMessage{ Cmd: utils.SentCmdAllUsersUserRoleUpdated, Body: struct { UserId string RoleId string }{ UserId: userId, RoleId: roleId, }, }) logger.AddSystemLog(structs.LogMessage{ Id: 5, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "userId", Value: conn.Locals("userId").(string)}, {Type: "roleId", Value: roleId}, {Type: "targetUserId", Value: userId}, }, }) } func HasPermission(userId string, permission string) bool { var user structs.User database.DB.Where("id = ?", userId).First(&user) var rolePermission structs.RolePermission database.DB.Where("role_id = ?", user.RoleId).Where("permission_id = ?", permission).Find(&rolePermission) return rolePermission.PermissionId == permission } func HasXYPermission(userId string, permission string, category string) bool { return HasPermission(userId, systempermissions.ConvertXYPermission(permission, category)) } func SendErrorMessageNoPermissions(sessionId string) { SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdErrorNoPermissions, }) } func AllUsersCreateNewUser(conn *websocket.Conn, body map[string]interface{}) { if body["Username"] == nil || body["Email"] == nil || body["Password"] == nil || body["RoleId"] == nil { log.Error().Msgf("Invalid body provided for user creation: %v", body) return } username := body["Username"].(string) email := body["Email"].(string) password := body["Password"].(string) roleId := body["RoleId"].(string) if !isValueLenValid(username, utils.MinUsername, utils.MaxUsername) { log.Error().Msgf("Invalid username length: %s", username) return } sessionId := conn.Locals("sessionId").(string) if !isUsernameAvailable(username) { SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdAllUsersNewUserCreated, Body: struct { Result uint8 }{ Result: 0, }, }) return } if !isEmailAvailable(email) { SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{ Cmd: utils.SentCmdAllUsersNewUserCreated, Body: struct { Result uint8 }{ Result: 1, }, }) return } decodedPassword, err := base64.StdEncoding.DecodeString(password) if err != nil { log.Error().Msgf("Failed to decode base64 password, err: %s", err.Error()) return } hashedPassword, err := bcrypt.GenerateFromPassword(decodedPassword, bcrypt.DefaultCost) if err != nil { log.Error().Msgf("Failed to generate password: %s", err.Error()) return } newUser := structs.User{ Id: uuid.New().String(), RoleId: roleId, Username: username, Email: email, Password: string(hashedPassword), CreatedAt: time.Now(), } database.DB.Create(&newUser) BroadcastMessage(structs.SendSocketMessage{ Cmd: utils.SentCmdAllUsersNewUserCreated, Body: struct { Id string Username string RoleId string ConnectionStatus uint8 Deactivated bool }{ Id: newUser.Id, Username: username, RoleId: roleId, ConnectionStatus: utils.ConnectionStateOffline, Deactivated: false, }, }) logger.AddSystemLog(structs.LogMessage{ Id: 6, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "userId", Value: conn.Locals("userId").(string)}, {Type: "targetUserId", Value: newUser.Id}, {Type: "roleId", Value: roleId}, }, }) } func AllUsersDeleteUser(conn *websocket.Conn, userId string) { var user structs.User database.DB.Select("avatar").Where("id = ?", userId).Find(&user) if user.Avatar != "" { os.Remove("./public/avatars/" + user.Avatar) } database.DB.Where("id = ?", userId).Delete(&structs.User{}) database.DB.Where("user_id = ?", userId).Delete(&structs.UserSession{}) var scannerInUsage structs.Scanner database.DB.Select("id").Where("used_by_user_id = ?", userId).Find(&scannerInUsage) if scannerInUsage.Id != "" { database.DB.Model(&structs.Scanner{}).Where("id = ?", scannerInUsage.Id).Updates(structs.Scanner{ UsedByUserId: "", }) } CloseAndDeleteAllUserConnections(userId) BroadcastMessage(structs.SendSocketMessage{ Cmd: utils.SentCmdAllUsersUserDeleted, Body: struct { UserId string ScannerId string }{ UserId: userId, ScannerId: scannerInUsage.Id, }, }) logger.AddSystemLog(structs.LogMessage{ Id: 7, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "userId", Value: conn.Locals("userId").(string)}, {Type: "targetUserId", Value: userId}, }, }) } func AllUsersUserDeactivation(conn *websocket.Conn, userId string, deactivate bool) { database.DB.Model(&structs.User{}).Select("deactivated").Where("id = ?", userId).Updates(structs.User{ Deactivated: deactivate, }) if deactivate { CloseAndDeleteAllUserConnections(userId) } BroadcastMessage(structs.SendSocketMessage{ Cmd: utils.SentCmdAllUsersUserDeactivation, Body: struct { UserId string Deactivated bool }{ UserId: userId, Deactivated: deactivate, }, }) logger.AddSystemLog(structs.LogMessage{ Id: 9, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "userId", Value: conn.Locals("userId").(string)}, {Type: "targetUserId", Value: userId}, {Type: "deactivated", Value: strconv.FormatBool(deactivate)}, }, }) } func ScannersUpdateScannerUsedByUserId(userId string, scannerId string) { database.DB.Model(&structs.Scanner{}).Where("id = ?", scannerId).Updates(structs.Scanner{UsedByUserId: userId}) BroadcastMessage(structs.SendSocketMessage{ Cmd: utils.SentCmdUpdateScannerUsedBy, Body: struct { ScannerId string UsedByUserId string }{ ScannerId: scannerId, UsedByUserId: userId, }, }) if userId != "" { logger.AddSystemLog(structs.LogMessage{ Id: 10, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "scannerId", Value: scannerId}, {Type: "userId", Value: userId}, }, }) } else { logger.AddSystemLog(structs.LogMessage{ Id: 11, Type: utils.LogTypeInfo, Messages: []structs.LogData{ {Type: "scannerId", Value: scannerId}, }, }) } } func GetUserApiKeys(userId string) []structs.UserApiKey { var apiKeys []structs.UserApiKey database.DB.Where("user_id = ?", userId).Find(&apiKeys) return apiKeys } func CreateNewUserApiKey(userId string, apiName string) { token, err := utils.GenerateSession() if err != nil { return } newApiKey := structs.UserApiKey{ Id: uuid.New().String(), Token: token, UserId: userId, Name: apiName, UsageCount: 0, CreatedAt: time.Now(), } database.DB.Create(&newApiKey) SendMessageToUser(userId, "", structs.SendSocketMessage{ Cmd: utils.SentCmdNewUserApiKeyCreated, Body: newApiKey, }) }