NoteServer/main.go

308 lines
6.6 KiB
Go
Raw Normal View History

2023-08-17 22:32:43 +02:00
package main
import (
"database/sql"
"encoding/json"
"io"
"log"
"net/http"
2023-08-19 23:33:10 +02:00
"os"
2023-08-18 22:34:42 +02:00
"strconv"
2023-08-17 22:32:43 +02:00
2023-08-18 22:34:42 +02:00
"github.com/julienschmidt/httprouter"
2023-08-17 22:32:43 +02:00
_ "github.com/mattn/go-sqlite3"
)
2023-08-19 23:33:10 +02:00
const configPath = "config.json"
2023-08-17 22:32:43 +02:00
var db *sql.DB
2023-08-19 23:33:10 +02:00
type config struct {
Host string `json:"host"`
Port int `json:"port"`
}
type note struct {
2023-08-17 22:32:43 +02:00
Id int64 `json:"id"`
2023-08-24 22:53:38 +02:00
Title string `json:"title"`
2023-08-17 22:32:43 +02:00
Content string `json:"content"`
}
func httpError(err error, statusCode int, message string, w http.ResponseWriter) bool {
if err != nil {
2023-08-24 22:53:38 +02:00
log.Println(message, err)
2023-08-18 22:43:07 +02:00
writeError(w, statusCode, message)
2023-08-17 22:32:43 +02:00
return true
}
return false
}
2023-08-18 22:43:07 +02:00
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)
}
2023-08-18 22:34:42 +02:00
func getNotes(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
2023-08-24 22:53:38 +02:00
res, err := db.Query(`SELECT Id, Title, Content FROM Notes`)
2023-08-17 22:32:43 +02:00
if httpError(err, http.StatusInternalServerError, "Failed to load notes", w) {
return
}
defer res.Close()
2023-08-19 23:33:10 +02:00
var notes []note = []note{}
2023-08-17 22:32:43 +02:00
for res.Next() {
if httpError(res.Err(), http.StatusInternalServerError, "Failed to load notes", w) {
return
}
2023-08-19 23:33:10 +02:00
var note note
2023-08-24 22:53:38 +02:00
res.Scan(&note.Id, &note.Title, &note.Content)
2023-08-17 22:32:43 +02:00
notes = append(notes, note)
}
json, err := json.Marshal(notes)
if httpError(err, http.StatusInternalServerError, "Failed to create JSON", w) {
return
}
2023-08-18 22:34:42 +02:00
w.Header().Set("Content-Type", "application/json")
2023-08-17 22:32:43 +02:00
w.Write(json)
}
2023-08-18 22:34:42 +02:00
func postNotes(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
2023-08-17 22:32:43 +02:00
data, err := io.ReadAll(req.Body)
if httpError(err, http.StatusBadRequest, "Failed to read data", w) {
return
}
2023-08-19 23:33:10 +02:00
var note note
2023-08-17 22:32:43 +02:00
err = json.Unmarshal(data, &note)
if httpError(err, http.StatusBadRequest, "Invalid request", w) {
return
}
2023-08-24 22:53:38 +02:00
if len(note.Title) == 0 {
writeError(w, http.StatusBadRequest, "Missing note title")
return
}
2023-08-17 22:32:43 +02:00
if len(note.Content) == 0 {
2023-08-18 22:43:07 +02:00
writeError(w, http.StatusBadRequest, "Missing note content")
2023-08-17 22:32:43 +02:00
return
}
2023-08-24 22:53:38 +02:00
stmt, err := db.Prepare(`INSERT INTO Notes(Title, Content) VALUES(?, ?) RETURNING Id`)
2023-08-17 22:32:43 +02:00
if httpError(err, http.StatusInternalServerError, "Failed to store note", w) {
return
}
defer stmt.Close()
2023-08-24 22:53:38 +02:00
res := stmt.QueryRow(note.Title, note.Content)
2023-08-17 22:32:43 +02:00
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
}
2023-08-18 22:34:42 +02:00
w.Header().Set("Content-Type", "application/json")
2023-08-17 22:32:43 +02:00
w.WriteHeader(http.StatusCreated)
w.Write(json)
}
2023-08-18 22:34:42 +02:00
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
}
2023-08-24 22:53:38 +02:00
stmt, err := db.Prepare(`SELECT Title, Content FROM Notes WHERE Id = ?`)
2023-08-18 22:34:42 +02:00
if httpError(err, http.StatusInternalServerError, "Failed to get note", w) {
return
}
defer stmt.Close()
res := stmt.QueryRow(id)
2023-08-19 23:33:10 +02:00
var note note = note{Id: id}
2023-08-18 22:34:42 +02:00
2023-08-24 22:53:38 +02:00
err = res.Scan(&note.Title, &note.Content)
2023-08-18 22:34:42 +02:00
if err == sql.ErrNoRows {
2023-08-18 22:43:07 +02:00
writeError(w, http.StatusNotFound, "Note doesn't exist")
2023-08-18 22:34:42 +02:00
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) {
2023-08-17 22:32:43 +02:00
return
}
2023-08-18 22:34:42 +02:00
w.Header().Set("Content-Type", "application/json")
w.Write(json)
2023-08-17 22:32:43 +02:00
}
2023-08-18 22:34:42 +02:00
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
}
2023-08-19 23:33:10 +02:00
var note note
2023-08-18 22:34:42 +02:00
err = json.Unmarshal(data, &note)
if httpError(err, http.StatusBadRequest, "Invalid request", w) {
return
}
2023-08-24 22:53:38 +02:00
if len(note.Title) == 0 {
writeError(w, http.StatusBadRequest, "Missing note title")
return
}
2023-08-18 22:34:42 +02:00
if len(note.Content) == 0 {
2023-08-18 22:43:07 +02:00
writeError(w, http.StatusBadRequest, "Missing note content")
2023-08-18 22:34:42 +02:00
return
}
2023-08-24 22:53:38 +02:00
stmt, err := db.Prepare(`UPDATE Notes SET Title = ?, Content = ? WHERE Id = ?`)
2023-08-18 22:34:42 +02:00
if httpError(err, http.StatusInternalServerError, "Failed to update note", w) {
return
}
defer stmt.Close()
2023-08-24 22:53:38 +02:00
res, err := stmt.Exec(note.Title, note.Content, id)
2023-08-18 22:34:42 +02:00
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
2023-08-17 22:32:43 +02:00
}
2023-08-18 22:34:42 +02:00
if updated == 0 {
2023-08-18 22:43:07 +02:00
writeError(w, http.StatusNotFound, "Note doesn't exist")
2023-08-18 22:34:42 +02:00
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)
2023-08-17 22:32:43 +02:00
}
2023-08-19 23:33:10 +02:00
func fileExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func loadConfig(path string) (config, error) {
raw, err := os.ReadFile(path)
if err != nil {
return config{}, err
}
var cfg config = config{}
err = json.Unmarshal(raw, &cfg)
if err != nil {
return config{}, err
}
return cfg, nil
}
2023-08-17 22:32:43 +02:00
func main() {
2023-08-19 23:33:10 +02:00
log.Println("Loading config")
exists, err := fileExists(configPath)
if err != nil {
log.Fatalln(err)
}
if !exists {
// Config etc
json, err := json.MarshalIndent(config{
Host: "localhost",
Port: 8080,
}, "", "\t")
if err != nil {
log.Fatalln(err)
}
os.WriteFile(configPath, json, 0o664)
log.Println("Created config.json, edit accordingly")
return
}
config, err := loadConfig(configPath)
if err != nil {
log.Fatalln(config)
}
2023-08-17 22:32:43 +02:00
log.Println("Starting NoteServer")
log.Println("Loading database")
db, err = sql.Open("sqlite3", "./notes.sqlite")
if err != nil {
log.Fatalln("Failed to load db:", err)
}
log.Println("Creating missing tables")
2023-08-24 22:53:38 +02:00
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS Notes(Id INTEGER PRIMARY KEY AUTOINCREMENT, Title TEXT, Content TEXT)`)
2023-08-17 22:32:43 +02:00
if err != nil {
log.Fatalln("Failed to create table:", err)
}
log.Println("Listening")
2023-08-18 22:34:42 +02:00
router := httprouter.New()
router.GET("/api/notes", getNotes)
router.POST("/api/notes", postNotes)
2023-08-19 23:33:10 +02:00
router.GET("/api/notes/:id", getNote)
router.PUT("/api/notes/:id", putNote)
2023-08-18 22:34:42 +02:00
http.ListenAndServe(":8080", router) // TODO: configure host, port, TLS
2023-08-17 22:32:43 +02:00
}