Brijesh's Git Server — watchman @ 9d309b4532c566f9be86d545f8e4cd8791a4ee8c

observability tool, needs to be rewritten once identity is stable

feat: projects and logs handlers but no embedded replicas

added all planned handlers for projects and logs and few extra ones as well

could not figure out how to solve SIGSEGV error with turso's embedded replicas

could use a minio server and systemd timers for db backups
Brijesh ops@brijesh.dev
Thu, 27 Jun 2024 02:00:13 +0530
commit

9d309b4532c566f9be86d545f8e4cd8791a4ee8c

parent

cd48349fb70225313368346a4b01af65c5a3b7b0

M .env.env

@@ -1,1 +1,4 @@

PORT=4000 + +DB_URL=libsql://watchman-wbrijesh.turso.io +DB_TOKEN=eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MTkzMDgzMzMsImlkIjoiZDI2ZWZjOTItNGU0ZC00NDY3LWIxZjQtNTgwZjc2ZjYzOTJjIn0.4XzwBsWhSLxm1hGO307_zlBaIBZ-YhMrtwEiTIaselVFDdbA3N-14ucMjdgoeYkUBeiA3UibXTvjSkiHLaNODg
A .gitignore

@@ -0,0 +1,2 @@

+.env +watchman.db
A README.md

@@ -0,0 +1,117 @@

+# Watchman + +## Overview + +Watchman is a hacky small scale logging tool for my personal projects kind of like that art you made in kindergarden (awesome, but not exactly museum worthy). + +i wrote this purely for my own amusement, since it doesn't even have basic auth, this project is strictly a "don't try this at home" situation. + +Maybe someday I'll get around to adding auth, but even then, this is best used with toy projects. + +### Project Management API + +#### TOC + +- [1. Create Project](#1-create-project) +- [2. List Projects](#2-list-projects) +- [3. Get Project By ID](#3-get-project-by-id) +- [4. Update Project](#4-update-project) +- [5. Delete Project](#5-delete-project) + +#### 1. Create Project + +```sh +curl -X POST http://127.0.0.1:4000/projects -d '{"Name": "Project 1"}' +``` + +#### 2. List Projects + +```sh +curl http://127.0.0.1:4000/projects +``` + +#### 3. Get Project By ID + +```sh +curl -X POST http://127.0.0.1:4000/project -d '{"ID": "1"}' +``` + +#### 4. Update Project + +```sh +curl -X PUT http://127.0.0.1:4000/projects -d '{"ID": "1", "Name": "Project 1 Updated"}' +``` + +#### 5. Delete Project + +```sh +curl -X DELETE http://127.0.0.1:4000/projects -d '{"ID": "1"}' +``` + +### Log Management API + +#### TOC + +- [1. Create a New Log](#1-create-a-new-log) +- [2. Get All Logs](#2-get-all-logs) +- [3. Get Logs by Project ID](#3-get-logs-by-project-id) +- [4. Get Logs by User ID](#4-get-logs-by-user-id) +- [5. Get Logs in Time Range](#5-get-logs-in-time-range) +- [6. Get Logs by Level](#6-get-logs-by-level) +- [7. Delete Logs by Project ID](#7-delete-logs-by-project-id) +- [8. Delete Logs by User ID](#8-delete-logs-by-user-id) +- [9. Delete Logs by Time Range](#9-delete-logs-by-time-range) + +#### 1. Create a New Log + +```sh +curl -X POST http://127.0.0.1:4000/logs -H "Content-Type: application/json" -d '{"level": "INFO", "message": "Log message", "subject": "Log subject", "user_id": "user1", "project_id": "project1"}' +``` + +#### 2. Get All Logs + +```sh +curl -X GET http://127.0.0.1:4000/logs +``` + +#### 3. Get Logs by Project ID + +```sh +curl -X GET 'http://127.0.0.1:4000/logs/project?project_id=project1' +``` + +#### 4. Get Logs by User ID + +```sh +curl -X GET 'http://127.0.0.1:4000/logs/user?user_id=user1' +``` + +#### 5. Get Logs in Time Range + +```sh +curl -X GET 'http://127.0.0.1:4000/logs/time?start_time=TIMESTAMP&end_time=TIMESTAMP' +``` + +#### 6. Get Logs by Level + +```sh +curl -X GET 'http://127.0.0.1:4000/logs/level?level=INFO' +``` + +#### 7. Delete Logs by Project ID + +```sh +curl -X DELETE 'http://127.0.0.1:4000/logs/project?project_id=project1' +``` + +#### 8. Delete Logs by User ID + +```sh +curl -X DELETE 'http://127.0.0.1:4000/logs/user?user_id=user1' +``` + +#### 9. Delete Logs by Time Range + +```sh +curl -X DELETE 'http://127.0.0.1:4000/logs/time?start_time=TIMESTAMP&end_time=TIMESTAMP' +```
M go.modgo.mod

@@ -3,7 +3,8 @@

go 1.22.4 require ( - github.com/joho/godotenv v1.5.0 github.com/google/uuid v1.6.0 + github.com/joho/godotenv v1.5.0 golang.org/x/time v0.5.0 + github.com/mattn/go-sqlite3 v1.14.22 )
M go.sumgo.sum

@@ -1,6 +1,16 @@

+github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= 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/joho/godotenv v1.5.0 h1:C/Vohk/9L1RCoS/UW2gfyi2N0EElSW3yb9zwi3PjosE= github.com/joho/godotenv v1.5.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06/go.mod h1:FUkZ5OHjlGPjnM2UyGJz9TypXQFgYqw6AFNO1UiROTM= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/tursodatabase/go-libsql v0.0.0-20240429120401-651096bbee0b h1:R7hev4b96zgXjKbS2ZNbHBnDvyFZhH+LlMqtKH6hIkU= +github.com/tursodatabase/go-libsql v0.0.0-20240429120401-651096bbee0b/go.mod h1:TjsB2miB8RW2Sse8sdxzVTdeGlx74GloD5zJYUC38d8= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
A internal/logs.go

@@ -0,0 +1,382 @@

+package internal + +import ( + "database/sql" + "encoding/json" + "fmt" + "net/http" + "time" + "watchman/schema" +) + +// CREATE NEW LOG: INSERT INTO Logs (Time, Level, Message, Subject, UserID, ProjectID) VALUES (?, ?, ?, ?, ?, ?) +// GET ALL LOGS FROM DB: SELECT * FROM Logs +// GET LOGS BY PROJECT ID: SELECT * FROM Logs WHERE ProjectID = ? +// GET LOGS BY USER ID: SELECT * FROM Logs WHERE UserID = ? +// GET LOGS IN TIME RANGE: SELECT * FROM Logs WHERE Time > ? AND Time < ? +// GET LOGS BY LEVEL: SELECT * FROM Logs WHERE Level = ? +// DELETE LOGS BY PROJECT ID: DELETE FROM Logs WHERE ProjectID = ? +// DELETE LOGS BY USER ID: DELETE FROM Logs WHERE UserID = ? +// DELETE LOGS BY TIME RANGE: DELETE FROM Logs WHERE Time > ? AND Time < ? + +func CreateLog(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + var log schema.Log + log.Time = int32(time.Now().Unix()) + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&log) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "Error decoding log data: %v", err) + return + } + + stmt, err := db.Prepare("INSERT INTO Logs (Time, Level, Message, Subject, UserID, ProjectID) VALUES (?, ?, ?, ?, ?, ?)") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error preparing SQL statement: %v", err) + return + } + defer stmt.Close() + + _, err = stmt.Exec(log.Time, log.Level, log.Message, log.Subject, log.UserID, log.ProjectID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error inserting log into database: %v", err) + return + } + + response := schema.Response_Type{ + Status: "OK", + Message: "Log created successfully", + RequestID: r.Context().Value(schema.RequestIDKey{}).(string), + } + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(response) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } +} + +func GetAllLogs(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + rows, err := db.Query("SELECT * FROM Logs") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error querying database: %v", err) + return + } + 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 { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error scanning row: %v", err) + return + } + logs = append(logs, log) + } + + w.Header().Set("Content-Type", "application/json") + response := schema.Response_Type{ + 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) + } +} + +func GetLogsByProjectID(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + projectID := r.URL.Query().Get("project_id") + + rows, err := db.Query("SELECT * FROM Logs WHERE ProjectID = ?", projectID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error querying database: %v", err) + return + } + 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 { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error scanning row: %v", err) + return + } + logs = append(logs, log) + } + + w.Header().Set("Content-Type", "application/json") + response := schema.Response_Type{ + 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) + } +} + +func GetLogsByUserID(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + userID := r.URL.Query().Get("user_id") + + rows, err := db.Query("SELECT * FROM Logs WHERE UserID = ?", userID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error querying database: %v", err) + return + } + 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 { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error scanning row: %v", err) + return + } + logs = append(logs, log) + } + + w.Header().Set("Content-Type", "application/json") + response := schema.Response_Type{ + 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) + } +} + +func GetLogsInTimeRange(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + startTime := r.URL.Query().Get("start_time") + endTime := r.URL.Query().Get("end_time") + + rows, err := db.Query("SELECT * FROM Logs WHERE Time > ? AND Time < ?", startTime, endTime) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error querying database: %v", err) + return + } + 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 { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error scanning row: %v", err) + return + } + logs = append(logs, log) + } + + w.Header().Set("Content-Type", "application/json") + response := schema.Response_Type{ + 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) + } +} + +func GetLogsByLevel(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + level := r.URL.Query().Get("level") + + rows, err := db.Query("SELECT * FROM Logs WHERE Level = ?", level) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error querying database: %v", err) + return + } + 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 { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error scanning row: %v", err) + return + } + logs = append(logs, log) + } + + w.Header().Set("Content-Type", "application/json") + response := schema.Response_Type{ + 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) + } +} + +func DeleteLogsByProjectID(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodDelete { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + projectID := r.URL.Query().Get("project_id") + + stmt, err := db.Prepare("DELETE FROM Logs WHERE ProjectID = ?") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error preparing SQL statement: %v", err) + return + } + defer stmt.Close() + + _, err = stmt.Exec(projectID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error deleting logs from database: %v", err) + return + } + + response := schema.Response_Type{ + Status: "OK", + Message: "Logs deleted successfully", + RequestID: r.Context().Value(schema.RequestIDKey{}).(string), + } + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(response) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } +} + +func DeleteLogsByUserID(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodDelete { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + userID := r.URL.Query().Get("user_id") + + stmt, err := db.Prepare("DELETE FROM Logs WHERE UserID = ?") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error preparing SQL statement: %v", err) + return + } + defer stmt.Close() + + _, err = stmt.Exec(userID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error deleting logs from database: %v", err) + return + } + + response := schema.Response_Type{ + Status: "OK", + Message: "Logs deleted successfully", + RequestID: r.Context().Value(schema.RequestIDKey{}).(string), + } + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(response) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } +} + +func DeleteLogsByTimeRange(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodDelete { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + startTime := r.URL.Query().Get("start_time") + endTime := r.URL.Query().Get("end_time") + + stmt, err := db.Prepare("DELETE FROM Logs WHERE Time > ? AND Time < ?") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error preparing SQL statement: %v", err) + return + } + defer stmt.Close() + + _, err = stmt.Exec(startTime, endTime) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error deleting logs from database: %v", err) + return + } + + response := schema.Response_Type{ + Status: "OK", + Message: "Logs deleted successfully", + RequestID: r.Context().Value(schema.RequestIDKey{}).(string), + } + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(response) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } +}
A internal/projects.go

@@ -0,0 +1,231 @@

+package internal + +import ( + "database/sql" + "encoding/json" + "fmt" + "net/http" + "watchman/schema" + "watchman/utils" +) + +// CREATE: INSERT INTO Projects (ID, Name) VALUES (?, ?) +// READ: SELECT * FROM Projects +// READ WITH ID: SELECT * FROM Projects WHERE ID = ? +// UPDATE: UPDATE Projects SET Name = ? WHERE ID = ? +// DELETE: DELETE FROM Projects WHERE ID = ? + +func CreateProject(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + var project schema.Project + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&project) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "Error decoding project data: %v", err) + return + } + + if project.ID == "" { + project.ID = utils.Generate_UUID() + } + + stmt, err := db.Prepare("INSERT INTO Projects (ID, Name) VALUES (?, ?)") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error preparing SQL statement: %v", err) + return + } + defer stmt.Close() + + _, err = stmt.Exec(project.ID, project.Name) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error inserting project into database: %v", err) + return + } + + response := schema.Response_Type{ + Status: "OK", + Message: "Project created successfully", + 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) + } +} + +func ListProjects(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + rows, err := db.Query("SELECT * FROM Projects") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error querying database: %v", err) + return + } + defer rows.Close() + + var projects []schema.Project + for rows.Next() { + var project schema.Project + err := rows.Scan(&project.ID, &project.Name) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error scanning row: %v", err) + return + } + projects = append(projects, project) + } + + response := schema.Response_Type{ + Status: "OK", + Message: "Projects retrieved successfully", + RequestID: r.Context().Value(schema.RequestIDKey{}).(string), + Data: projects, + } + + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} + +func GetProjectByID(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + var project schema.Project + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&project) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "Error decoding project data: %v", err) + return + } + + row := db.QueryRow("SELECT * FROM Projects WHERE ID = ?", project.ID) + var projectByID schema.Project + err = row.Scan(&projectByID.ID, &projectByID.Name) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error querying database: %v", err) + return + } + + response := schema.Response_Type{ + Status: "OK", + Message: "Project retrieved successfully", + RequestID: r.Context().Value(schema.RequestIDKey{}).(string), + Data: projectByID, + } + + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} + +func UpdateProject(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodPut { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + var project schema.Project + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&project) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "Error decoding project data: %v", err) + return + } + + stmt, err := db.Prepare("UPDATE Projects SET Name = ? WHERE ID = ?") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error preparing SQL statement: %v", err) + return + } + defer stmt.Close() + + _, err = stmt.Exec(project.Name, project.ID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error updating project in database: %v", err) + return + } + + response := schema.Response_Type{ + Status: "OK", + Message: "Project updated successfully", + 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) + } +} + +func DeleteProject(w http.ResponseWriter, r *http.Request, db *sql.DB) { + if r.Method != http.MethodDelete { + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintf(w, "Method %s not allowed", r.Method) + return + } + + var project schema.Project + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&project) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "Error decoding project data: %v", err) + return + } + + stmt, err := db.Prepare("DELETE FROM Projects WHERE ID = ?") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error preparing SQL statement: %v", err) + return + } + defer stmt.Close() + + _, err = stmt.Exec(project.ID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error deleting project from database: %v", err) + return + } + + response := schema.Response_Type{ + Status: "OK", + Message: "Project deleted successfully", + 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 main.gomain.go

@@ -1,11 +1,15 @@

package main import ( + "database/sql" "fmt" "log" "net/http" + "watchman/internal" "watchman/middleware" "watchman/utils" + + _ "github.com/mattn/go-sqlite3" ) const (

@@ -26,10 +30,92 @@

func main() { port := utils.Load_ENV("PORT") + db_connection, err := sql.Open("sqlite3", "./watchman.db") + if err != nil { + panic(err) + } + defer db_connection.Close() + multiplexer := http.NewServeMux() - multiplexer.HandleFunc("/", utils.Example_Handler) multiplexer.HandleFunc("/health", utils.Health_Check_Handler) + + multiplexer.HandleFunc("/project", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + internal.GetProjectByID(w, r, db_connection) + default: + utils.Method_Not_Allowed_Handler(w, r) + } + }) + + multiplexer.HandleFunc("/projects", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodPost: + internal.CreateProject(w, r, db_connection) + case http.MethodGet: + internal.ListProjects(w, r, db_connection) + case http.MethodPut: + internal.UpdateProject(w, r, db_connection) + case http.MethodDelete: + internal.DeleteProject(w, r, db_connection) + default: + utils.Method_Not_Allowed_Handler(w, r) + } + }) + + multiplexer.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodPost: + internal.CreateLog(w, r, db_connection) + case http.MethodGet: + internal.GetAllLogs(w, r, db_connection) + default: + utils.Method_Not_Allowed_Handler(w, r) + } + }) + + multiplexer.HandleFunc("/logs/project", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + internal.GetLogsByProjectID(w, r, db_connection) + // case http.MethodDelete: + // internal.DeleteLogsByProjectID(w, r, db_c nnection) + default: + utils.Method_Not_Allowed_Handler(w, r) + } + }) + + multiplexer.HandleFunc("/logs/user", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + internal.GetLogsByUserID(w, r, db_connection) + // case http.MethodDelete: + // internal.DeleteLogsByUserID(w, r, db_connection) + default: + utils.Method_Not_Allowed_Handler(w, r) + } + }) + + multiplexer.HandleFunc("/logs/time", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + internal.GetLogsInTimeRange(w, r, db_connection) + // case http.MethodDelete: + // internal.DeleteLogsByTimeRange(w, r, db_connection) + default: + utils.Method_Not_Allowed_Handler(w, r) + } + }) + + multiplexer.HandleFunc("/logs/level", func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + internal.GetLogsByLevel(w, r, db_connection) + default: + utils.Method_Not_Allowed_Handler(w, r) + } + }) fmt.Println("Starting server on " + port) log.Fatal(http.ListenAndServe(":"+port,
A schema/db.go

@@ -0,0 +1,15 @@

+package schema + +type Project struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type Log struct { + Level string `json:"level"` + Message string `json:"message"` + Subject string `json:"subject"` + UserID string `json:"user_id"` + ProjectID string `json:"project_id"` + Time int32 `json:"time"` +}
M schema/types.goschema/types.go

@@ -1,9 +1,10 @@

package schema type Response_Type struct { - Status string `json:"status"` - Message string `json:"message"` - RequestID string `json:"request_id"` + Status string `json:"status"` + Message string `json:"message"` + RequestID string `json:"request_id"` + Data interface{} `json:"data,omitempty"` } type RequestIDKey struct{}
M utils/common_handlers.goutils/common_handlers.go

@@ -10,13 +10,12 @@ func Health_Check_Handler(w http.ResponseWriter, r *http.Request) {

w.Write([]byte("OK")) } -func Example_Handler(w http.ResponseWriter, r *http.Request) { +func Method_Not_Allowed_Handler(w http.ResponseWriter, r *http.Request) { response := schema.Response_Type{ - Status: "OK", - Message: "Everything is fine", + 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 {
A utils/uuid.go

@@ -0,0 +1,7 @@

+package utils + +import "github.com/google/uuid" + +func Generate_UUID() string { + return uuid.New().String() +}