diff options
| -rw-r--r-- | app/cmd/main.go | 68 | ||||
| -rw-r--r-- | app/go.mod | 1 | ||||
| -rw-r--r-- | app/go.sum | 2 | ||||
| -rw-r--r-- | app/internal/middleware.go | 67 | ||||
| -rw-r--r-- | app/internal/utils/json.go | 22 | ||||
| -rw-r--r-- | devenv.lock | 4 |
6 files changed, 129 insertions, 35 deletions
diff --git a/app/cmd/main.go b/app/cmd/main.go index 3c1e9e2..95dab38 100644 --- a/app/cmd/main.go +++ b/app/cmd/main.go @@ -13,6 +13,7 @@ import ( "github.com/arangodb/go-driver/v2/arangodb" "github.com/arangodb/go-driver/v2/connection" "github.com/ketsuna-org/bot-creator-api/internal" + "github.com/ketsuna-org/bot-creator-api/internal/utils" ) var botList = make(map[string]*internal.Bot) @@ -61,6 +62,7 @@ func main() { }) // Route POST /create/{bot_token} + //![TODO] : Every bot_token will be replaced by the bot id. to be compliant with Discord TOS, and Token will be send as a secured Header. mux.HandleFunc("POST /create/{bot_token}", func(w http.ResponseWriter, r *http.Request) { // Extraire le token du bot de l'URL botToken := r.PathValue("bot_token") @@ -76,7 +78,7 @@ func main() { col, err := database.GetCollection(context.Background(), "bots", nil) if err != nil { log.Printf("[SERVER] Error getting collection: %v", err) - http.Error(w, "Error getting collection", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error getting collection") return } // let's check if the bot already exists @@ -84,7 +86,7 @@ func main() { // Let's check if this discord bot exists if _, ok := botList[botToken]; ok { log.Printf("[SERVER] Bot already running: %s", botToken) - http.Error(w, "Bot already running", http.StatusConflict) + utils.RespondWithError(w, http.StatusConflict, "Bot already running") return } @@ -93,27 +95,27 @@ func main() { req, err := http.NewRequest("GET", "https://discord.com/api/v10/users/@me", nil) if err != nil { log.Printf("[SERVER] Error creating request: %v", err) - http.Error(w, "Error creating request", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error creating request") return } req.Header.Set("Authorization", "Bot "+botToken) resp, err := httpClient.Do(req) if err != nil { log.Printf("[SERVER] Error sending request: %v", err) - http.Error(w, "Error sending request", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error sending request") return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Printf("[SERVER] Bot not found: %s", botToken) - http.Error(w, "Bot not found", http.StatusNotFound) + utils.RespondWithError(w, http.StatusNotFound, "Bot not found") return } var botData map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&botData); err != nil { log.Printf("[SERVER] Error decoding JSON: %v", err) - http.Error(w, "Error decoding JSON", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusBadRequest, "Invalid JSON") return } @@ -123,7 +125,7 @@ func main() { exist, err := col.DocumentExists(context.Background(), id) if err != nil { log.Printf("[SERVER] Error checking document: %v", err) - http.Error(w, "Error checking document", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error checking document") return } @@ -131,7 +133,7 @@ func main() { _, err = col.CreateDocument(context.Background(), botData) if err != nil { log.Printf("[SERVER] Error creating bot: %v", err) - http.Error(w, "Error creating document", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error creating bot") return } log.Printf("[SERVER] Bot created: %s", botToken) @@ -140,7 +142,7 @@ func main() { _, err = col.UpdateDocument(context.Background(), botData["id"].(string), botData) if err != nil { log.Printf("[SERVER] Error updating document: %v", err) - http.Error(w, "Error updating document", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error updating document") return } log.Printf("[SERVER] Bot updated: %s", botToken) @@ -151,7 +153,7 @@ func main() { if err := json.NewDecoder(r.Body).Decode(&body); err != nil { log.Printf("[SERVER] Error decoding JSON: %v", err) - http.Error(w, "Invalid JSON", http.StatusBadRequest) + utils.RespondWithError(w, http.StatusBadRequest, "Invalid JSON") return } @@ -169,7 +171,7 @@ func main() { dataToUse, ok := body["data"].(map[string]interface{}) if !ok { log.Printf("[SERVER] Data is not a map") - http.Error(w, "Data is not a map", http.StatusBadRequest) + utils.RespondWithError(w, http.StatusBadRequest, "Data is not a map") return } // let's check if the data is a map @@ -179,7 +181,7 @@ func main() { valueToUse, ok := value.(map[string]interface{}) if !ok { log.Printf("[SERVER] Value is not a map") - http.Error(w, "Value is not a map", http.StatusBadRequest) + utils.RespondWithError(w, http.StatusBadRequest, "Value is not a map") return } @@ -189,7 +191,7 @@ func main() { // let's check if the name is set if err != nil { log.Printf("[SERVER] Error getting collection: %v", err) - http.Error(w, "Error getting collection", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error getting collection") return } @@ -197,7 +199,7 @@ func main() { exist, err := col.DocumentExists(context.Background(), key) if err != nil { log.Printf("[SERVER] Error checking document: %v", err) - http.Error(w, "Error checking document", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error checking document") return } if !exist { @@ -206,7 +208,7 @@ func main() { _, err = col.CreateDocument(context.Background(), valueToUse) if err != nil { log.Printf("[SERVER] Error creating document: %v", err) - http.Error(w, "Error creating document", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error creating document") return } log.Printf("[SERVER] Command created: %s", key) @@ -215,7 +217,7 @@ func main() { _, err = col.UpdateDocument(context.Background(), key, valueToUse) if err != nil { log.Printf("[SERVER] Error updating document: %v", err) - http.Error(w, "Error updating document", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error updating document") return } log.Printf("[SERVER] Command updated: %s", key) @@ -225,7 +227,7 @@ func main() { edgeCol, err := database.GetCollection(context.Background(), "bots_commands", nil) if err != nil { log.Printf("[SERVER] Error getting collection: %v", err) - http.Error(w, "Error getting collection", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error getting collection") return } // let's check if the edge already exists @@ -246,7 +248,7 @@ func main() { _, err = edgeCol.CreateDocument(context.Background(), edge) if err != nil { log.Printf("[SERVER] Error creating document: %v", err) - http.Error(w, "Error creating document", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error creating document") return } log.Printf("[SERVER] Edge created: %s", edgeID) @@ -259,7 +261,7 @@ func main() { _, err = edgeCol.UpdateDocument(context.Background(), edgeID, edge) if err != nil { log.Printf("[SERVER] Error updating document: %v", err) - http.Error(w, "Error updating document", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error updating document") return } log.Printf("[SERVER] Edge updated: %s", edgeID) @@ -271,7 +273,7 @@ func main() { data, err := json.Marshal(body["data"]) if err != nil { log.Printf("[SERVER] Error marshaling JSON: %v", err) - http.Error(w, "Error marshaling JSON", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error marshaling JSON") return } @@ -280,65 +282,65 @@ func main() { bot, err = internal.Start(bot, string(data), fmt.Sprint(body["intents"])) if err != nil { log.Printf("[SERVER] Error starting bot: %v", err) - http.Error(w, "Error starting bot", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error starting bot") return } botList[botToken] = bot log.Printf("[SERVER] Bot started successfully") - w.WriteHeader(http.StatusOK) - w.Write([]byte("Bot started successfully")) + utils.RespondWithJSON(w, bot, http.StatusOK) }) // Route POST /stop/{bot_token} + //![TODO] : Every bot_token will be replaced by the bot id. to be compliant with Discord TOS, and Token will be send as a secured Header. mux.HandleFunc("POST /stop/{bot_token}", func(w http.ResponseWriter, r *http.Request) { // Extraire le token du bot de l'URL botToken := r.PathValue("bot_token") bot, ok := botList[botToken] if !ok { - http.Error(w, "Bot not found", http.StatusNotFound) + log.Printf("[SERVER] Bot not found: %s", botToken) + utils.RespondWithError(w, http.StatusNotFound, "Bot not found") return } if err := bot.Stop(); err != nil { log.Printf("[SERVER] Error stopping bot: %v", err) - http.Error(w, "Error stopping bot", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error stopping bot") return } delete(botList, botToken) log.Printf("[SERVER] Bot stopped successfully") - w.WriteHeader(http.StatusOK) - w.Write([]byte("Bot stopped successfully")) + utils.RespondWithJSON(w, bot, http.StatusOK) }) // Route POST /update/{bot_token} + //![TODO] : Every bot_token will be replaced by the bot id. to be compliant with Discord TOS, and Token will be send as a secured Header. mux.HandleFunc("POST /update/{bot_token}", func(w http.ResponseWriter, r *http.Request) { // Extraire le token du bot de l'URL botToken := r.PathValue("bot_token") bot, ok := botList[botToken] if !ok { - http.Error(w, "Bot not found", http.StatusNotFound) + utils.RespondWithError(w, http.StatusNotFound, "Bot not found") return } body := make(map[string]interface{}) if err := json.NewDecoder(r.Body).Decode(&body); err != nil { log.Printf("[SERVER] Error decoding JSON: %v", err) - http.Error(w, "Invalid JSON", http.StatusBadRequest) + utils.RespondWithError(w, http.StatusBadRequest, "Invalid JSON") return } data, err := json.Marshal(body) if err != nil { log.Printf("[SERVER] Error marshaling JSON: %v", err) - http.Error(w, "Error marshaling JSON", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error marshaling JSON") return } if err := bot.SendMessage(string(data)); err != nil { log.Printf("[SERVER] Error sending message: %v", err) - http.Error(w, "Error sending message", http.StatusInternalServerError) + utils.RespondWithError(w, http.StatusInternalServerError, "Error sending message") return } log.Printf("[SERVER] Bot updated successfully") - w.WriteHeader(http.StatusOK) - w.Write([]byte("Bot updated successfully")) + utils.RespondWithJSON(w, bot, http.StatusOK) }) // Gestion des signaux pour l'arrĂȘt propre @@ -6,6 +6,7 @@ require ( github.com/arangodb/go-driver/v2 v2.1.3 // indirect github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e // indirect github.com/dchest/siphash v1.2.3 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/kkdai/maglev v0.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -8,6 +8,8 @@ github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBl github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kkdai/maglev v0.2.0 h1:w6DCW0kAA6fstZqXkrBrlgIC3jeIRXkjOYea/m6EK/Y= diff --git a/app/internal/middleware.go b/app/internal/middleware.go new file mode 100644 index 0000000..2af90c8 --- /dev/null +++ b/app/internal/middleware.go @@ -0,0 +1,67 @@ +package internal + +import ( + "log" + "net/http" + "os" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/ketsuna-org/bot-creator-api/internal/utils" +) + +func LogMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + next.ServeHTTP(w, r) + duration := time.Since(start) + + log.Printf("Request: %s %s took %f seconds", r.Method, r.URL.Path, duration.Seconds()) + }) +} + +/** + * AuthMiddleware is a placeholder for authentication middleware. + * In a real application, this would check for valid authentication tokens or sessions. + * For now, it just calls the next handler. + */ +func AuthMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + utils.RespondWithError(w, http.StatusUnauthorized, "Missing authorization header") + return + } + tokenString := authHeader[len("Bearer "):] + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Validate the algorithm + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, http.ErrNotSupported + } + // Return the secret key for validation + return []byte(os.Getenv("JWT_SECRET")), nil + }) + if err != nil { + utils.RespondWithError(w, http.StatusUnauthorized, "Invalid token") + return + } + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + // You can access claims here + log.Printf("User ID: %v", claims["user_id"]) + } else { + utils.RespondWithError(w, http.StatusUnauthorized, "Invalid token claims") + return + } + + next.ServeHTTP(w, r) + }) +} + +func Chain(middlewares ...func(http.Handler) http.Handler) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + for _, middleware := range middlewares { + next = middleware(next) + } + return next + } +} diff --git a/app/internal/utils/json.go b/app/internal/utils/json.go new file mode 100644 index 0000000..d3c6b71 --- /dev/null +++ b/app/internal/utils/json.go @@ -0,0 +1,22 @@ +package utils + +import ( + "encoding/json" + "net/http" +) + +func RespondWithError(w http.ResponseWriter, code int, message string) { + RespondWithJSON(w, map[string]string{"error": message}, code) +} + +func RespondWithJSON(w http.ResponseWriter, payload interface{}, code ...int) { + var c int = http.StatusOK + if len(code) > 0 { + c = code[0] + } + response, _ := json.Marshal(payload) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(c) + w.Write(response) +} diff --git a/devenv.lock b/devenv.lock index f92d9c0..b772a34 100644 --- a/devenv.lock +++ b/devenv.lock @@ -3,10 +3,10 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1745750098, + "lastModified": 1746276326, "owner": "cachix", "repo": "devenv", - "rev": "312f0eb723d9b2a1539f175bca9f11f1e4f0a673", + "rev": "0133867ecb206f1d9cedc3c5872d2a4eb2d8ff29", "type": "github" }, "original": { |
