chore: change handlers to work as methods and seperate db and app logic Seperated DB and app logic, this should help in writing tests later. Also edited code to use Methods, might change this back to plain functions as this might be unneccessary.
Brijesh ops@brijesh.dev
Thu, 11 Jul 2024 19:21:37 +0530
15 files changed,
466 insertions(+),
249 deletions(-)
A
database/database.go
@@ -0,0 +1,108 @@
+package database + +import ( + "context" + "database/sql" + "fmt" + "log" + "strconv" + "time" + "watchman/schema" + + _ "github.com/mattn/go-sqlite3" +) + +type DBService interface { + Health() map[string]string + Close() error + CreateProject(project schema.Project) error + ListAllProjects() ([]schema.Project, error) + GetProjectByID(projectID string) (schema.Project, error) + UpdateProjectByID(projectID string, project schema.Project) error + DeleteProjectByID(projectID string) error + + BatchInsertLogs(logs []schema.Log) error + GetLogs(projectID string, userID string, startTime string, endTime string, level string) ([]schema.Log, error) + DeleteLogs(projectID string, userID string, startTime string, endTime string, level string) error +} + +type service struct { + db *sql.DB +} + +var ( + dburl = "watchman.db" + dbInstance *service +) + +func New() DBService { + if dbInstance != nil { + return dbInstance + } + + db, err := sql.Open("sqlite3", dburl) + if err != nil { + log.Fatal(err) + } + + dbInstance = &service{ + db: db, + } + return dbInstance +} + +// Health checks the health of the database connection by pinging the database. +// It returns a map with keys indicating various health statistics. +func (s *service) Health() map[string]string { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + stats := make(map[string]string) + + // Ping the database + err := s.db.PingContext(ctx) + if err != nil { + stats["status"] = "down" + stats["error"] = fmt.Sprintf("db down: %v", err) + log.Fatalf(fmt.Sprintf("db down: %v", err)) // Log the error and terminate the program + return stats + } + + // Database is up, add more statistics + stats["status"] = "up" + stats["message"] = "It's healthy" + + // Get database stats (like open connections, in use, idle, etc.) + dbStats := s.db.Stats() + stats["open_connections"] = strconv.Itoa(dbStats.OpenConnections) + stats["in_use"] = strconv.Itoa(dbStats.InUse) + stats["idle"] = strconv.Itoa(dbStats.Idle) + stats["wait_count"] = strconv.FormatInt(dbStats.WaitCount, 10) + stats["wait_duration"] = dbStats.WaitDuration.String() + stats["max_idle_closed"] = strconv.FormatInt(dbStats.MaxIdleClosed, 10) + stats["max_lifetime_closed"] = strconv.FormatInt(dbStats.MaxLifetimeClosed, 10) + + // Evaluate stats to provide a health message + if dbStats.OpenConnections > 40 { // Assuming 50 is the max for this example + stats["message"] = "The database is experiencing heavy load." + } + + if dbStats.WaitCount > 1000 { + stats["message"] = "The database has a high number of wait events, indicating potential bottlenecks." + } + + if dbStats.MaxIdleClosed > int64(dbStats.OpenConnections)/2 { + stats["message"] = "Many idle connections are being closed, consider revising the connection pool settings." + } + + if dbStats.MaxLifetimeClosed > int64(dbStats.OpenConnections)/2 { + stats["message"] = "Many connections are being closed due to max lifetime, consider increasing max lifetime or revising the connection usage pattern." + } + + return stats +} + +func (s *service) Close() error { + log.Printf("Disconnected from database: %s", dburl) + return s.db.Close() +}
A
database/logs.go
@@ -0,0 +1,96 @@
+package database + +import ( + "fmt" + "time" + "watchman/schema" +) + +func (s *service) BatchInsertLogs(logs []schema.Log) error { + stmt, err := s.db.Prepare("INSERT INTO Logs (Time, Level, Message, Subject, UserID, ProjectID) VALUES (?, ?, ?, ?, ?, ?)") + if err != nil { + return fmt.Errorf("error preparing statement: %v", err) + } + defer stmt.Close() + for _, log := range logs { + log.Time = int32(time.Now().Unix()) + _, err = stmt.Exec(log.Time, log.Level, log.Message, log.Subject, log.UserID, log.ProjectID) + if err != nil { + return fmt.Errorf("error inserting into database: %v", err) + } + } + return nil +} + +func (s *service) GetLogs(projectID string, userID string, startTime string, endTime string, level string) ([]schema.Log, error) { + query := "SELECT * FROM Logs WHERE " + if projectID != "" { + query += "ProjectID = '" + projectID + "' AND " + } + if userID != "" { + query += "UserID = '" + userID + "' AND " + } + if startTime != "" { + query += "Time > '" + startTime + "' AND " + } + if endTime != "" { + query += "Time < '" + endTime + "' AND " + } + if level != "" { + query += "Level = '" + level + "' AND " + } + if projectID != "" || userID != "" || startTime != "" || endTime != "" || level != "" { + query = query[:len(query)-5] + } else { + query = query[:len(query)-7] + } + rows, err := s.db.Query(query) + if err != nil { + return nil, fmt.Errorf("error querying database: %v", err) + } + defer rows.Close() + var logs []schema.Log + for rows.Next() { + var log schema.Log + err := rows.Scan(&log.Time, &log.Level, &log.Message, &log.Subject, &log.UserID, &log.ProjectID) + if err != nil { + return nil, fmt.Errorf("error scanning row: %v", err) + } + logs = append(logs, log) + } + return logs, nil +} + +func (s *service) DeleteLogs(projectID string, userID string, startTime string, endTime string, level string) error { + query := "DELETE FROM Logs WHERE " + if projectID != "" { + query += "ProjectID = '" + projectID + "' AND " + } + if userID != "" { + query += "UserID = '" + userID + "' AND " + } + if startTime != "" { + query += "Time > '" + startTime + "' AND " + } + if endTime != "" { + query += "Time < '" + endTime + "' AND " + } + if level != "" { + query += "Level = '" + level + "' AND " + } + if projectID != "" || userID != "" || startTime != "" || endTime != "" || level != "" { + query = query[:len(query)-5] + } else { + query = query[:len(query)-7] + } + stmt, err := s.db.Prepare(query) + if err != nil { + return fmt.Errorf("error preparing statement: %v", err) + } + defer stmt.Close() + _, err = stmt.Exec() + if err != nil { + return fmt.Errorf("error executing statement: %v", err) + } + return nil +}
A
database/projects.go
@@ -0,0 +1,77 @@
+package database + +import ( + "fmt" + "watchman/schema" +) + +func (s *service) CreateProject(project schema.Project) error { + stmt, err := s.db.Prepare("INSERT INTO Projects (ID, Name) VALUES (?, ?)") + if err != nil { + return fmt.Errorf("error preparing statement: %v", err) + } + defer stmt.Close() + + _, err = stmt.Exec(project.ID, project.Name) + if err != nil { + return fmt.Errorf("error executing statement: %v", err) + } + + return nil +} + +func (s *service) ListAllProjects() ([]schema.Project, error) { + rows, err := s.db.Query("SELECT * FROM Projects") + if err != nil { + return nil, fmt.Errorf("error querying database: %v", err) + } + defer rows.Close() + var projects []schema.Project + for rows.Next() { + var project schema.Project + err := rows.Scan(&project.ID, &project.Name) + if err != nil { + return nil, fmt.Errorf("error scanning row: %v", err) + } + projects = append(projects, project) + } + return projects, nil +} + +// 8263059922 + +func (s *service) GetProjectByID(projectID string) (schema.Project, error) { + row := s.db.QueryRow("SELECT * FROM Projects WHERE ID = ?", projectID) + var project schema.Project + err := row.Scan(&project.ID, &project.Name) + if err != nil { + return schema.Project{}, fmt.Errorf("error scanning row: %v", err) + } + return project, nil +} + +func (s *service) UpdateProjectByID(projectID string, project schema.Project) error { + stmt, err := s.db.Prepare("UPDATE Projects SET Name = ?, ID = ? WHERE ID = ?") + if err != nil { + return fmt.Errorf("error preparing statement: %v", err) + } + defer stmt.Close() + _, err = stmt.Exec(project.Name, project.ID, projectID) + if err != nil { + return fmt.Errorf("error executing statement: %v", err) + } + return nil +} + +func (s *service) DeleteProjectByID(projectID string) error { + stmt, err := s.db.Prepare("DELETE FROM Projects WHERE ID = ?") + if err != nil { + return fmt.Errorf("error preparing statement: %v", err) + } + defer stmt.Close() + _, err = stmt.Exec(projectID) + if err != nil { + return fmt.Errorf("error executing statement: %v", err) + } + return nil +}
A
internal/app_server.go
@@ -0,0 +1,98 @@
+package internal + +import ( + "fmt" + "net/http" + "strings" + "time" + "watchman/database" + "watchman/middleware" + "watchman/schema" + "watchman/utils" +) + +type Server struct { + db database.DBService + port int +} + +func NewServer() *http.Server { + NewServer := &Server{ + port: utils.ReadConfig().Port, + + db: database.New(), + } + + server := &http.Server{ + Addr: fmt.Sprintf(":%d", NewServer.port), + Handler: middleware.ApplyMiddleware(NewServer.RegisterRoutes()), + IdleTimeout: time.Minute, + ReadTimeout: 10 * time.Second, + WriteTimeout: 30 * time.Second, + } + + return server +} + +func (s *Server) RegisterRoutes() http.Handler { + multiplexer := http.NewServeMux() + + multiplexer.HandleFunc("/health", s.healthHandler) + + multiplexer.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodPost: + s.CreateProjectHandler(w, r) + case http.MethodGet: + s.ListAllProjectsHandler(w, r) + default: + utils.MethodNotAllowedHandler(w, r) + } + }) + + multiplexer.HandleFunc("/projects/", func(w http.ResponseWriter, r *http.Request) { + url := strings.TrimPrefix(r.URL.Path, "/projects/") + projectID := url + + if projectID == "" { + utils.HandleError(w, r, http.StatusBadRequest, "Project ID not provided", fmt.Errorf("project ID not provided")) + } + + switch r.Method { + case http.MethodGet: + s.GetProjectByIDHandler(w, r, projectID) + case http.MethodPut: + s.UpdateProjectByIDHandler(w, r, projectID) + case http.MethodDelete: + s.DeleteProjectByIDHandler(w, r, projectID) + default: + utils.MethodNotAllowedHandler(w, r) + } + }) + + multiplexer.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodPost: + s.BatchInsertLogsHandler(w, r) + case http.MethodGet: + s.GetLogsHandler(w, r) + case http.MethodDelete: + s.DeleteLogsHandler(w, r) + default: + utils.MethodNotAllowedHandler(w, r) + } + }) + + multiplexer.HandleFunc("/login", AdminLogin) + + return multiplexer +} + +func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { + utils.SendResponse(w, r, schema.ResponseType{ + Status: "OK", + Message: "Health check successful", + RequestID: r.Context().Value(schema.RequestIDKey{}).(string), + Data: s.db.Health(), + }) +}
M
internal/auth.go
→
internal/auth.go
@@ -42,16 +42,21 @@ },
} token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - tokenString, err := token.SignedString(config.JwtKey) + tokenString, err := token.SignedString([]byte(config.JwtKey)) if err != nil { utils.HandleError(w, r, http.StatusInternalServerError, "Error signing token: ", err) return } http.SetCookie(w, &http.Cookie{ - Name: "token", - Value: tokenString, - Expires: expirationTime, + Name: "token", + Value: tokenString, + Path: "/", + Expires: expirationTime, + MaxAge: 1800, + Secure: true, + HttpOnly: true, + SameSite: http.SameSiteNoneMode, }) response := schema.ResponseType{
M
internal/logs.go
→
internal/logs.go
@@ -1,16 +1,13 @@
package internal import ( - "database/sql" "encoding/json" - "fmt" "net/http" - "time" "watchman/schema" "watchman/utils" ) -func BatchInsertLogs(w http.ResponseWriter, r *http.Request, db *sql.DB) { +func (s *Server) BatchInsertLogsHandler(w http.ResponseWriter, r *http.Request) { utils.HandleMethodNotAllowed(w, r, http.MethodPost) var logs []schema.Log@@ -18,17 +15,8 @@ decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&logs) utils.HandleError(w, r, http.StatusBadRequest, "Error decoding JSON: ", err) - fmt.Println("Logs: ", logs) - - stmt, err := db.Prepare("INSERT INTO Logs (Time, Level, Message, Subject, UserID, ProjectID) VALUES (?, ?, ?, ?, ?, ?)") - utils.HandleError(w, r, http.StatusInternalServerError, "Error preparing statement: ", err) - defer stmt.Close() - - for _, log := range logs { - log.Time = int32(time.Now().Unix()) - _, err = stmt.Exec(log.Time, log.Level, log.Message, log.Subject, log.UserID, log.ProjectID) - utils.HandleError(w, r, http.StatusInternalServerError, "Error inserting into database: ", err) - } + err = s.db.BatchInsertLogs(logs) + utils.HandleError(w, r, http.StatusInternalServerError, "Error inserting logs: ", err) response := schema.ResponseType{ Status: "OK",@@ -38,7 +26,7 @@ }
utils.SendResponse(w, r, response) } -func GetLogs(w http.ResponseWriter, r *http.Request, db *sql.DB) { +func (s *Server) GetLogsHandler(w http.ResponseWriter, r *http.Request) { utils.HandleMethodNotAllowed(w, r, http.MethodGet) projectID := r.URL.Query().Get("project_id")@@ -47,56 +35,19 @@ startTime := r.URL.Query().Get("start_time")
endTime := r.URL.Query().Get("end_time") level := r.URL.Query().Get("level") - query := "SELECT * FROM Logs WHERE " - - if projectID != "" { - query += "ProjectID = '" + projectID + "' AND " - } - if userID != "" { - query += "UserID = '" + userID + "' AND " - } - if startTime != "" { - query += "Time > '" + startTime + "' AND " - } - if endTime != "" { - query += "Time < '" + endTime + "' AND " - } - if level != "" { - query += "Level = '" + level + "' AND " - } - - if projectID != "" || userID != "" || startTime != "" || endTime != "" || level != "" { - query = query[:len(query)-5] - } else { - query = query[:len(query)-7] - } - - rows, err := db.Query(query) - utils.HandleError(w, r, http.StatusInternalServerError, "Error querying database: ", err) - defer rows.Close() - - var logs []schema.Log - for rows.Next() { - var log schema.Log - err = rows.Scan(&log.Time, &log.Level, &log.Message, &log.Subject, &log.UserID, &log.ProjectID) - utils.HandleError(w, r, http.StatusInternalServerError, "Error scanning row: ", err) - logs = append(logs, log) - } + logs, err := s.db.GetLogs(projectID, userID, startTime, endTime, level) + utils.HandleError(w, r, http.StatusInternalServerError, "Error getting logs: ", err) - w.Header().Set("Content-Type", "application/json") response := schema.ResponseType{ Status: "OK", Message: "Logs retrieved successfully", RequestID: r.Context().Value(schema.RequestIDKey{}).(string), Data: logs, } - err = json.NewEncoder(w).Encode(response) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - } + utils.SendResponse(w, r, response) } -func DeleteLogs(w http.ResponseWriter, r *http.Request, db *sql.DB) { +func (s *Server) DeleteLogsHandler(w http.ResponseWriter, r *http.Request) { utils.HandleMethodNotAllowed(w, r, http.MethodDelete) projectID := r.URL.Query().Get("project_id")@@ -104,36 +55,9 @@ userID := r.URL.Query().Get("user_id")
startTime := r.URL.Query().Get("start_time") endTime := r.URL.Query().Get("end_time") level := r.URL.Query().Get("level") - query := "DELETE FROM Logs WHERE " - if projectID != "" { - query += "ProjectID = '" + projectID + "' AND " - } - if userID != "" { - query += "UserID = '" + userID + "' AND " - } - if startTime != "" { - query += "Time > '" + startTime + "' AND " - } - if endTime != "" { - query += "Time < '" + endTime + "' AND " - } - if level != "" { - query += "Level = '" + level + "' AND " - } - - if projectID != "" || userID != "" || startTime != "" || endTime != "" || level != "" { - query = query[:len(query)-5] - } else { - query = query[:len(query)-7] - } - - stmt, err := db.Prepare(query) - utils.HandleError(w, r, http.StatusInternalServerError, "Error preparing statement: ", err) - defer stmt.Close() - - _, err = stmt.Exec() - utils.HandleError(w, r, http.StatusInternalServerError, "Error executing statement: ", err) + err := s.db.DeleteLogs(projectID, userID, startTime, endTime, level) + utils.HandleError(w, r, http.StatusInternalServerError, "Error deleting logs: ", err) response := schema.ResponseType{ Status: "OK",
M
internal/projects.go
→
internal/projects.go
@@ -1,7 +1,6 @@
package internal import ( - "database/sql" "encoding/json" "net/http" "watchman/schema"@@ -10,7 +9,7 @@
"github.com/google/uuid" ) -func CreateProject(w http.ResponseWriter, r *http.Request, db *sql.DB) { +func (s *Server) CreateProjectHandler(w http.ResponseWriter, r *http.Request) { utils.HandleMethodNotAllowed(w, r, http.MethodPost) var project schema.Project@@ -22,12 +21,8 @@ if project.ID == "" {
project.ID = uuid.New().String() } - stmt, err := db.Prepare("INSERT INTO Projects (ID, Name) VALUES (?, ?)") - utils.HandleError(w, r, http.StatusInternalServerError, "Error preparing statement: ", err) - defer stmt.Close() - - _, err = stmt.Exec(project.ID, project.Name) - utils.HandleError(w, r, http.StatusInternalServerError, "Error executing statement: ", err) + err = s.db.CreateProject(project) + utils.HandleError(w, r, http.StatusInternalServerError, "Error creating project: ", err) response := schema.ResponseType{ Status: "OK",@@ -37,37 +32,27 @@ }
utils.SendResponse(w, r, response) } -func GetProjectByID(w http.ResponseWriter, r *http.Request, db *sql.DB, projectID string) { +func (s *Server) GetProjectByIDHandler(w http.ResponseWriter, r *http.Request, projectID string) { utils.HandleMethodNotAllowed(w, r, http.MethodGet) - row := db.QueryRow("SELECT * FROM Projects WHERE ID = ?", projectID) - var projectByID schema.Project - err := row.Scan(&projectByID.ID, &projectByID.Name) - utils.HandleError(w, r, http.StatusInternalServerError, "Error querying database: ", err) + project, err := s.db.GetProjectByID(projectID) + utils.HandleError(w, r, http.StatusInternalServerError, "Error retrieving project: ", err) response := schema.ResponseType{ Status: "OK", Message: "Project retrieved successfully", RequestID: r.Context().Value(schema.RequestIDKey{}).(string), - Data: projectByID, + Data: project, } utils.SendResponse(w, r, response) } -func ListAllProjects(w http.ResponseWriter, r *http.Request, db *sql.DB) { +func (s *Server) ListAllProjectsHandler(w http.ResponseWriter, r *http.Request) { utils.HandleMethodNotAllowed(w, r, http.MethodGet) - rows, err := db.Query("SELECT * FROM Projects") - utils.HandleError(w, r, http.StatusInternalServerError, "Error querying database: ", err) - defer rows.Close() + projects, err := s.db.ListAllProjects() + utils.HandleError(w, r, http.StatusInternalServerError, "Error retreiving projects: ", err) - var projects []schema.Project - for rows.Next() { - var project schema.Project - err := rows.Scan(&project.ID, &project.Name) - utils.HandleError(w, r, http.StatusInternalServerError, "Error scanning row: ", err) - projects = append(projects, project) - } response := schema.ResponseType{ Status: "OK", Message: "Projects retrieved successfully",@@ -77,7 +62,7 @@ }
utils.SendResponse(w, r, response) } -func UpdateProjectByID(w http.ResponseWriter, r *http.Request, db *sql.DB, projectID string) { +func (s *Server) UpdateProjectByIDHandler(w http.ResponseWriter, r *http.Request, projectID string) { utils.HandleMethodNotAllowed(w, r, http.MethodPut) var project schema.Project@@ -85,12 +70,8 @@ decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&project) utils.HandleError(w, r, http.StatusBadRequest, "Error decoding JSON: ", err) - stmt, err := db.Prepare("UPDATE Projects SET Name = ?, ID = ? WHERE ID = ?") - utils.HandleError(w, r, http.StatusInternalServerError, "Error preparing statement: ", err) - defer stmt.Close() - - _, err = stmt.Exec(project.Name, project.ID, projectID) - utils.HandleError(w, r, http.StatusInternalServerError, "Error executing statement: ", err) + err = s.db.UpdateProjectByID(projectID, project) + utils.HandleError(w, r, http.StatusInternalServerError, "Error updating project: ", err) response := schema.ResponseType{ Status: "OK",@@ -100,15 +81,11 @@ }
utils.SendResponse(w, r, response) } -func DeleteProjectByID(w http.ResponseWriter, r *http.Request, db *sql.DB, projectID string) { +func (s *Server) DeleteProjectByIDHandler(w http.ResponseWriter, r *http.Request, projectID string) { utils.HandleMethodNotAllowed(w, r, http.MethodDelete) - stmt, err := db.Prepare("DELETE FROM Projects WHERE ID = ?") - utils.HandleError(w, r, http.StatusInternalServerError, "Error preparing statement: ", err) - defer stmt.Close() - - _, err = stmt.Exec(projectID) - utils.HandleError(w, r, http.StatusInternalServerError, "Error executing statement: ", err) + err := s.db.DeleteProjectByID(projectID) + utils.HandleError(w, r, http.StatusInternalServerError, "Error deleting project: ", err) response := schema.ResponseType{ Status: "OK",
M
main.go
→
main.go
@@ -1,97 +1,18 @@
package main import ( - "database/sql" - "encoding/json" "fmt" - "log" - "net/http" - "strconv" - "strings" "watchman/internal" - "watchman/middleware" - "watchman/schema" "watchman/utils" - - _ "github.com/mattn/go-sqlite3" ) func main() { - config := utils.ReadConfig() + server := internal.NewServer() + + fmt.Println("Staring server on port ", utils.ReadConfig().Port) - dbConnection, err := sql.Open("sqlite3", "./watchman.db") + err := server.ListenAndServe() if err != nil { - panic(err) + panic(fmt.Sprintf("cannot start server: %s", err)) } - defer dbConnection.Close() - - multiplexer := http.NewServeMux() - - multiplexer.HandleFunc("/health", utils.HealthCheckHandler) - - multiplexer.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodPost: - internal.CreateProject(w, r, dbConnection) - case http.MethodGet: - internal.ListAllProjects(w, r, dbConnection) - default: - utils.MethodNotAllowedHandler(w, r) - } - }) - - multiplexer.HandleFunc("/projects/", func(w http.ResponseWriter, r *http.Request) { - url := strings.TrimPrefix(r.URL.Path, "/projects/") - projectID := url - - if projectID == "" { - response := schema.ResponseType{ - Status: "ERROR", - Message: "Project ID not provided", - RequestID: r.Context().Value(schema.RequestIDKey{}).(string), - } - - w.WriteHeader(http.StatusBadRequest) - w.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(w).Encode(response) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - } - - switch r.Method { - case http.MethodGet: - internal.GetProjectByID(w, r, dbConnection, projectID) - case http.MethodPut: - internal.UpdateProjectByID(w, r, dbConnection, projectID) - case http.MethodDelete: - internal.DeleteProjectByID(w, r, dbConnection, projectID) - default: - utils.MethodNotAllowedHandler(w, r) - } - }) - - multiplexer.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodPost: - internal.BatchInsertLogs(w, r, dbConnection) - case http.MethodGet: - internal.GetLogs(w, r, dbConnection) - case http.MethodDelete: - internal.DeleteLogs(w, r, dbConnection) - default: - utils.MethodNotAllowedHandler(w, r) - } - }) - - multiplexer.HandleFunc("/login", internal.AdminLogin) - - fmt.Println("Starting server on port " + strconv.Itoa(config.Port)) - log.Fatal(http.ListenAndServe(":"+strconv.Itoa(config.Port), - middleware.CorsMiddleware( - middleware.RequestIDMiddleware( - middleware.Ratelimit( - config, - multiplexer, - ))))) }
A
middleware/apply.go
@@ -0,0 +1,20 @@
+package middleware + +import ( + "net/http" + "watchman/utils" +) + +func ApplyMiddleware(handler http.Handler) http.Handler { + config := utils.ReadConfig() + + httpHandlerWithMiddleware := CorsMiddleware( + RequestIDMiddleware( + Ratelimit( + config, handler, + ), + ), + ) + + return httpHandlerWithMiddleware +}
M
middleware/request_id.go
→
middleware/request_id.go
@@ -8,11 +8,6 @@
"github.com/google/uuid" ) -// NEEDS WORK: wrote a struct for RequestIDKey instead of "RequestID" string due to LSP warning to -// not use string key to avoid potential collisions, but a string is used in the middleware function -// for request IDs in echo's middleware package, not sure what is the best way to handle this -// RequestIDKey is moved to schema package - func RequestIDMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestID := uuid.New().String()
M
schema/schema.sql
→
schema/schema.sql
@@ -2,6 +2,7 @@ CREATE TABLE Projects (
ID TEXT PRIMARY KEY, Name TEXT NOT NULL ); + CREATE TABLE Logs ( Time INTEGER NOT NULL, Level TEXT NOT NULL,@@ -11,6 +12,7 @@ UserID TEXT,
ProjectID TEXT NOT NULL, FOREIGN KEY (ProjectID) REFERENCES Project(ID) ); + CREATE TABLE ProjectKeys ( ProjectID TEXT NOT NULL, AccessKey TEXT NOT NULL,
M
schema/types.go
→
schema/types.go
@@ -1,6 +1,8 @@
package schema -import "github.com/golang-jwt/jwt/v5" +import ( + "github.com/golang-jwt/jwt/v5" +) type ResponseType struct { Data interface{} `json:"data,omitempty"`
D
utils/common_handlers.go
@@ -1,24 +0,0 @@
-package utils - -import ( - "encoding/json" - "net/http" - "watchman/schema" -) - -func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("OK")) -} - -func MethodNotAllowedHandler(w http.ResponseWriter, r *http.Request) { - response := schema.ResponseType{ - Status: "ERROR", - Message: "Method Not Allowed", - RequestID: r.Context().Value(schema.RequestIDKey{}).(string), - } - w.Header().Set("Content-Type", "application/json") - err := json.NewEncoder(w).Encode(response) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } -}
M
utils/errors.go
→
utils/errors.go
@@ -1,18 +1,21 @@
package utils import ( + "encoding/json" "net/http" "watchman/schema" ) func HandleError(w http.ResponseWriter, r *http.Request, statusCode int, message string, err error) { - w.WriteHeader(statusCode) - response := schema.ResponseType{ - Status: "ERROR", - Message: message + err.Error(), - RequestID: r.Context().Value(schema.RequestIDKey{}).(string), + if err != nil { + w.WriteHeader(statusCode) + response := schema.ResponseType{ + Status: "ERROR", + Message: message + err.Error(), + RequestID: r.Context().Value(schema.RequestIDKey{}).(string), + } + SendResponse(w, r, response) } - SendResponse(w, r, response) } func HandleMethodNotAllowed(w http.ResponseWriter, r *http.Request, method string) {@@ -20,3 +23,16 @@ if r.Method != method {
HandleError(w, r, http.StatusMethodNotAllowed, "Method "+method+" not allowed", nil) } } + +func MethodNotAllowedHandler(w http.ResponseWriter, r *http.Request) { + response := schema.ResponseType{ + Status: "ERROR", + Message: "Method Not Allowed", + RequestID: r.Context().Value(schema.RequestIDKey{}).(string), + } + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +}