NoteServer/main.go

234 lines
5.3 KiB
Go

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(&note.Id, &note.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, &note)
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(&note.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(&note.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, &note)
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
}