package main import ( "database/sql" "encoding/json" "io" "log" "net/http" "strconv" "github.com/julienschmidt/httprouter" _ "github.com/mattn/go-sqlite3" ) var db *sql.DB type Note struct { Id int64 `json:"id"` Content string `json:"content"` } func httpError(err error, statusCode int, message string, w http.ResponseWriter) bool { if err != nil { log.Println(err) writeError(w, statusCode, message) return true } return false } func writeError(w http.ResponseWriter, statusCode int, message string) { type errorResponse struct { Message string `json:"message"` } response := errorResponse{Message: message} json, err := json.Marshal(response) if err != nil { return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) w.Write(json) } 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 } defer res.Close() var notes []Note = []Note{} for res.Next() { if httpError(res.Err(), http.StatusInternalServerError, "Failed to load notes", w) { return } var note Note res.Scan(¬e.Id, ¬e.Content) notes = append(notes, note) } json, err := json.Marshal(notes) if httpError(err, http.StatusInternalServerError, "Failed to create JSON", w) { return } w.Header().Set("Content-Type", "application/json") w.Write(json) } 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 } var note Note err = json.Unmarshal(data, ¬e) if httpError(err, http.StatusBadRequest, "Invalid request", w) { return } if len(note.Content) == 0 { writeError(w, http.StatusBadRequest, "Missing note content") return } stmt, err := db.Prepare(`INSERT INTO Notes(Content) VALUES(?) RETURNING Id`) if httpError(err, http.StatusInternalServerError, "Failed to store note", w) { return } defer stmt.Close() res := stmt.QueryRow(note.Content) err = res.Scan(¬e.Id) if httpError(err, http.StatusInternalServerError, "Failed to store 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.WriteHeader(http.StatusCreated) w.Write(json) } 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 { writeError(w, http.StatusNotFound, "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 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 { writeError(w, http.StatusBadRequest, "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 { writeError(w, http.StatusNotFound, "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() { log.Println("Starting NoteServer") log.Println("Loading database") var err error db, err = sql.Open("sqlite3", "./notes.sqlite") if err != nil { log.Fatalln("Failed to load db:", err) } log.Println("Creating missing tables") _, err = db.Exec(`CREATE TABLE IF NOT EXISTS Notes(Id INTEGER PRIMARY KEY AUTOINCREMENT, Content TEXT)`) if err != nil { log.Fatalln("Failed to create table:", err) } log.Println("Listening") 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 }