diff --git a/go.mod b/go.mod index d526731..02223ce 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,5 @@ module git.cringe-studios.com/NoteServer go 1.20 require github.com/mattn/go-sqlite3 v1.14.17 + +require github.com/julienschmidt/httprouter v1.3.0 diff --git a/go.sum b/go.sum index 04784a2..0508386 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= diff --git a/main.go b/main.go index f60c155..26af1db 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,9 @@ import ( "io" "log" "net/http" + "strconv" + "github.com/julienschmidt/httprouter" _ "github.com/mattn/go-sqlite3" ) @@ -19,6 +21,7 @@ type Note struct { func httpError(err error, statusCode int, message string, w http.ResponseWriter) bool { if err != nil { + log.Println(err) w.WriteHeader(statusCode) io.WriteString(w, message) return true @@ -27,7 +30,7 @@ func httpError(err error, statusCode int, message string, w http.ResponseWriter) return false } -func getNotes(w http.ResponseWriter, req *http.Request) { +func getNotes(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { res, err := db.Query(`SELECT Id, Content FROM Notes`) if httpError(err, http.StatusInternalServerError, "Failed to load notes", w) { return @@ -52,10 +55,11 @@ func getNotes(w http.ResponseWriter, req *http.Request) { return } + w.Header().Set("Content-Type", "application/json") w.Write(json) } -func postNotes(w http.ResponseWriter, req *http.Request) { +func postNotes(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { data, err := io.ReadAll(req.Body) if httpError(err, http.StatusBadRequest, "Failed to read data", w) { return @@ -91,28 +95,102 @@ func postNotes(w http.ResponseWriter, req *http.Request) { return } + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) w.Write(json) } -func putNote(w http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPut { - w.WriteHeader(http.StatusBadRequest) - io.WriteString(w, "Invalid method") +func getNote(w http.ResponseWriter, req *http.Request, p httprouter.Params) { + id, err := strconv.ParseInt(p.ByName("id"), 10, 64) + if httpError(err, http.StatusBadRequest, "Invalid id", w) { return } + + stmt, err := db.Prepare(`SELECT Content FROM Notes WHERE Id = ?`) + if httpError(err, http.StatusInternalServerError, "Failed to get note", w) { + return + } + + defer stmt.Close() + + res := stmt.QueryRow(id) + + var note Note = Note{Id: id} + + err = res.Scan(¬e.Content) + if err == sql.ErrNoRows { + w.WriteHeader(http.StatusNotFound) + io.WriteString(w, "Note doesn't exist") + return + } + + if httpError(err, http.StatusInternalServerError, "Failed to get note", w) { + return + } + + json, err := json.Marshal(note) + if httpError(err, http.StatusInternalServerError, "Failed to create JSON", w) { + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(json) } -func handleNotes(w http.ResponseWriter, req *http.Request) { - switch req.Method { - case http.MethodGet: - getNotes(w, req) - case http.MethodPost: - postNotes(w, req) - default: - w.WriteHeader(http.StatusBadRequest) - io.WriteString(w, "Invalid method") +func putNote(w http.ResponseWriter, req *http.Request, p httprouter.Params) { + id, err := strconv.ParseInt(p.ByName("id"), 10, 64) + if httpError(err, http.StatusBadRequest, "Invalid id", w) { + return } + + data, err := io.ReadAll(req.Body) + if httpError(err, http.StatusBadRequest, "Failed to read data", w) { + return + } + + var note Note + err = json.Unmarshal(data, ¬e) + if httpError(err, http.StatusBadRequest, "Invalid request", w) { + return + } + + if len(note.Content) == 0 { + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "Missing note content") + return + } + + stmt, err := db.Prepare(`UPDATE Notes SET Content = ? WHERE Id = ?`) + if httpError(err, http.StatusInternalServerError, "Failed to update note", w) { + return + } + + defer stmt.Close() + + res, err := stmt.Exec(note.Content, id) + if httpError(err, http.StatusInternalServerError, "Failed to update note", w) { + return + } + + updated, err := res.RowsAffected() + if httpError(err, http.StatusInternalServerError, "Failed to update note", w) { + return + } + + if updated == 0 { + w.WriteHeader(http.StatusNotFound) + io.WriteString(w, "Note doesn't exist") + return + } + + note.Id = id + json, err := json.Marshal(note) + if httpError(err, http.StatusInternalServerError, "Failed to create JSON", w) { + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(json) } func main() { @@ -134,7 +212,11 @@ func main() { log.Println("Listening") - http.HandleFunc("/api/notes", handleNotes) - http.HandleFunc("/api/note/", putNote) - http.ListenAndServe(":8080", nil) // TODO: configure host, port, TLS + router := httprouter.New() + router.GET("/api/notes", getNotes) + router.POST("/api/notes", postNotes) + router.GET("/api/note/:id", getNote) + router.PUT("/api/note/:id", putNote) + + http.ListenAndServe(":8080", router) // TODO: configure host, port, TLS }