feat: finish project basic structure and core functions
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"nukumizu-backend/internal/controller"
|
||||
"nukumizu-backend/postLog"
|
||||
"nukumizu-backend/utils"
|
||||
)
|
||||
|
||||
// OneBotMessage represents a OneBot 11 message event forwarded from napcat-bridge.
|
||||
type OneBotMessage struct {
|
||||
PostType string `json:"post_type"`
|
||||
MessageType string `json:"message_type"`
|
||||
GroupID int64 `json:"group_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
RawMessage string `json:"raw_message"`
|
||||
Message string `json:"message"`
|
||||
Sender struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
Nickname string `json:"nickname"`
|
||||
} `json:"sender"`
|
||||
SelfID int64 `json:"self_id"`
|
||||
SubType string `json:"sub_type"`
|
||||
}
|
||||
|
||||
// BotMessageHandler handles POST /api/bot/msg/recv.
|
||||
// Receives OneBot 11 events forwarded from napcat-bridge and
|
||||
// routes them to the appropriate bot controller.
|
||||
func BotMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Auth(w, r, "POST", "bot") {
|
||||
return
|
||||
}
|
||||
|
||||
var event OneBotMessage
|
||||
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
|
||||
postLog.Debug("Failed to parse bot message: " + err.Error())
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "invalid message format")
|
||||
return
|
||||
}
|
||||
|
||||
// Only handle message events.
|
||||
if event.PostType != "message" {
|
||||
utils.SendSuccessResponse(w, "event ignored", map[string]interface{}{
|
||||
"post_type": event.PostType,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Route to the Napcat/QQ controller.
|
||||
ctrl := controller.GetController("qq(napcat)")
|
||||
if ctrl == nil {
|
||||
postLog.Debug("QQ controller not available, ignoring message")
|
||||
utils.SendSuccessResponse(w, "controller not available", nil)
|
||||
return
|
||||
}
|
||||
|
||||
cmdCtrl, ok := ctrl.(controller.CommandController)
|
||||
if !ok {
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "controller does not support commands")
|
||||
return
|
||||
}
|
||||
|
||||
// Determine chat information.
|
||||
chatID := event.GroupID
|
||||
chatType := event.MessageType
|
||||
if chatType == "private" {
|
||||
chatID = event.UserID
|
||||
}
|
||||
|
||||
// Build a command from the raw message.
|
||||
cmd := controller.Command{
|
||||
RawText: event.RawMessage,
|
||||
ChatID: chatID,
|
||||
ChatType: chatType,
|
||||
SenderID: event.UserID,
|
||||
}
|
||||
|
||||
response, err := cmdCtrl.HandleCommand(cmd)
|
||||
if err != nil {
|
||||
postLog.Error("Bot command handling failed: " + err.Error())
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "command handling failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if response != "" {
|
||||
utils.SendSuccessResponse(w, "", map[string]interface{}{
|
||||
"response": response,
|
||||
"chatID": chatID,
|
||||
"chatType": chatType,
|
||||
})
|
||||
} else {
|
||||
utils.SendSuccessResponse(w, "no response", nil)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"nukumizu-backend/database"
|
||||
"nukumizu-backend/utils"
|
||||
)
|
||||
|
||||
// HealthHandler handles GET /health.
|
||||
func HealthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
utils.SendErrorResponse(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
dbStatus := "ok"
|
||||
if database.UserDB == nil {
|
||||
dbStatus = "not initialized"
|
||||
} else if err := database.UserDB.Ping(); err != nil {
|
||||
dbStatus = "error: " + err.Error()
|
||||
}
|
||||
|
||||
utils.SendSuccessResponse(w, "", map[string]interface{}{
|
||||
"status": "ok",
|
||||
"database": dbStatus,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"nukumizu-backend/internal/komari"
|
||||
"nukumizu-backend/internal/node"
|
||||
"nukumizu-backend/internal/template"
|
||||
"nukumizu-backend/postLog"
|
||||
"nukumizu-backend/utils"
|
||||
)
|
||||
|
||||
// ServerExecRequest represents the request body for POST /api/server/exec.
|
||||
type ServerExecRequest struct {
|
||||
UUID []string `json:"uuid"`
|
||||
Command string `json:"command"`
|
||||
}
|
||||
|
||||
// ServerListHandler handles GET /api/server/list.
|
||||
func ServerListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Auth(w, r, "GET", "bot") {
|
||||
return
|
||||
}
|
||||
|
||||
tracker := node.GetTracker()
|
||||
if tracker == nil {
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "node tracker not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
params := template.BuildParamsFromServerList()
|
||||
result := template.Render("", params)
|
||||
|
||||
utils.SendSuccessResponse(w, "", map[string]interface{}{
|
||||
"list": result,
|
||||
})
|
||||
}
|
||||
|
||||
// ServerGetStatusHandler handles GET /api/server/getStatus?uuid=xxx.
|
||||
func ServerGetStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Auth(w, r, "GET", "bot") {
|
||||
return
|
||||
}
|
||||
|
||||
uuid := r.URL.Query().Get("uuid")
|
||||
if uuid == "" {
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "missing uuid parameter")
|
||||
return
|
||||
}
|
||||
|
||||
// First try to get data from the local tracker.
|
||||
tracker := node.GetTracker()
|
||||
if tracker != nil {
|
||||
if n, exists := tracker.GetNode(uuid); exists && n.LatestReport != nil {
|
||||
utils.SendSuccessResponse(w, "", map[string]interface{}{
|
||||
"uuid": uuid,
|
||||
"report": n.LatestReport,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
utils.SendErrorResponse(w, http.StatusNotFound, fmt.Sprintf("no recent data for uuid: %s", uuid))
|
||||
}
|
||||
|
||||
// ServerExecHandler handles POST /api/server/exec.
|
||||
func ServerExecHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Auth(w, r, "POST", "bot") {
|
||||
return
|
||||
}
|
||||
|
||||
var req ServerExecRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.UUID) == 0 {
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "uuid array is required")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Command == "" {
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "command is required")
|
||||
return
|
||||
}
|
||||
|
||||
// Get the Komari client from the global state.
|
||||
komariClient := komari.GetClient()
|
||||
if komariClient == nil {
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "komari client not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
taskID, err := komariClient.ExecTask(req.UUID, req.Command)
|
||||
if err != nil {
|
||||
postLog.Error("Komari exec task failed: " + err.Error())
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "failed to execute command: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
results, err := komariClient.PollTaskResult(taskID)
|
||||
if err != nil {
|
||||
postLog.Error("Komari task polling failed: " + err.Error())
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "failed to get task result: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendSuccessResponse(w, "", map[string]interface{}{
|
||||
"taskID": taskID,
|
||||
"results": results,
|
||||
})
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
db "nukumizu-backend/database"
|
||||
"nukumizu-backend/postLog"
|
||||
"nukumizu-backend/utils"
|
||||
)
|
||||
|
||||
// UserLoginRequest represents the login request body.
|
||||
type UserLoginRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// UserLoginHandler handles POST /api/user/login.
|
||||
func UserLoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Auth(w, r, "POST", "None") {
|
||||
return
|
||||
}
|
||||
|
||||
var req UserLoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Username == "" || req.Password == "" {
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "username and password are required")
|
||||
return
|
||||
}
|
||||
|
||||
userID, username, level, registerDate, err := db.GetUserByUsername(req.Username, req.Password)
|
||||
if err != nil {
|
||||
postLog.Warning("Login failed for user " + req.Username + ": " + err.Error())
|
||||
utils.SendErrorResponse(w, http.StatusUnauthorized, "invalid username or password")
|
||||
return
|
||||
}
|
||||
|
||||
token, err := utils.GenerateToken()
|
||||
if err != nil {
|
||||
postLog.Error("Failed to generate token: " + err.Error())
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "failed to generate token")
|
||||
return
|
||||
}
|
||||
|
||||
utils.AddToken(token, userID, level, username)
|
||||
postLog.Info("User logged in: " + username)
|
||||
|
||||
utils.SendSuccessResponse(w, "login successful", map[string]interface{}{
|
||||
"token": token,
|
||||
"userID": userID,
|
||||
"username": username,
|
||||
"level": level,
|
||||
"registerDate": registerDate,
|
||||
})
|
||||
}
|
||||
|
||||
// UserRegisterHandler handles POST /api/user/register.
|
||||
// Only allowed when there are no existing users in the database.
|
||||
func UserRegisterHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.Auth(w, r, "POST", "None") {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if any user already exists.
|
||||
count, err := db.GetUserCount()
|
||||
if err != nil {
|
||||
postLog.Error("Failed to check user count: " + err.Error())
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "database error")
|
||||
return
|
||||
}
|
||||
if count > 0 {
|
||||
utils.SendErrorResponse(w, http.StatusForbidden, "registration is closed: users already exist")
|
||||
return
|
||||
}
|
||||
|
||||
var req UserLoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Username == "" || req.Password == "" {
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "username and password are required")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Username) > 50 {
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "username too long (max 50 characters)")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Password) < 6 || len(req.Password) > 100 {
|
||||
utils.SendErrorResponse(w, http.StatusBadRequest, "password must be between 6 and 100 characters")
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := db.CreateUser(req.Username, req.Password, "admin")
|
||||
if err != nil {
|
||||
postLog.Error("Failed to register user: " + err.Error())
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "failed to register user, username may already exist")
|
||||
return
|
||||
}
|
||||
|
||||
token, err := utils.GenerateToken()
|
||||
if err != nil {
|
||||
postLog.Error("Failed to generate token: " + err.Error())
|
||||
utils.SendErrorResponse(w, http.StatusInternalServerError, "failed to generate token")
|
||||
return
|
||||
}
|
||||
|
||||
utils.AddToken(token, userID, "admin", req.Username)
|
||||
postLog.Info("User registered: " + req.Username)
|
||||
|
||||
utils.SendSuccessResponse(w, "user registered successfully", map[string]interface{}{
|
||||
"token": token,
|
||||
"userID": userID,
|
||||
"username": req.Username,
|
||||
"level": "admin",
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user