commit 5d0a4d17b9d5ae350868d795dae527f10a0814ec Author: James Shiffer Date: Fri Aug 12 21:10:29 2022 -0700 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5d7bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,90 @@ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Go template +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..22f8478 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,17 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:C:\Users\James.Shiffer\GolandProjects\femMemos\memos.db + $ProjectFileDir$ + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.38.0/sqlite-jdbc-3.38.0.jar + + + + + \ No newline at end of file diff --git a/.idea/femMemos.iml b/.idea/femMemos.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/femMemos.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4094383 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..5a6aee0 --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/db.go b/db.go new file mode 100644 index 0000000..9e7f18d --- /dev/null +++ b/db.go @@ -0,0 +1,91 @@ +package main + +import ( + "database/sql" + "errors" + "log" + "os" + "time" +) + +type Memo struct { + ID int64 `json:"id"` + Time string `json:"time"` + Message string `json:"message"` +} + +func openOrCreateDb(name string) *sql.DB { + new := false + if _, err := os.Stat(name); errors.Is(err, os.ErrNotExist) { + log.Println("memos db does not exist, creating.") + new = true + } + db, err := sql.Open("sqlite3", name) + if err != nil { + log.Fatal(err) + } + if new { + _, err = db.Exec(`create table memos ( + ID integer primary key autoincrement, + time text not null, + Message text not null + )`) + if err != nil { + log.Fatal(err) + } + } + return db +} + +func insertMemo(db *sql.DB, message string) int64 { + tx, err := db.Begin() + if err != nil { + log.Fatal(err) + } + + stmt, err := tx.Prepare("insert into memos (time, Message) values (?, ?)") + if err != nil { + log.Fatal(err) + } + defer stmt.Close() + + timeStr := time.Now().Format(time.RFC3339) + row, err := stmt.Exec(timeStr, message) + if err != nil { + log.Fatal(err) + } + + err = tx.Commit() + if err != nil { + log.Fatal(err) + } + + id, err := row.LastInsertId() + if err != nil { + log.Fatal(err) + } + + return id +} + +func getMemos(db *sql.DB) []Memo { + rows, err := db.Query("select * from memos order by id desc") + if err != nil { + log.Fatal(err) + } + defer rows.Close() + var memos []Memo + for rows.Next() { + var memo Memo + err = rows.Scan(&memo.ID, &memo.Time, &memo.Message) + if err != nil { + log.Fatal(err) + } + memos = append(memos, memo) + } + err = rows.Err() + if err != nil { + log.Fatal(err) + } + return memos +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9515c3f --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module femMemos + +go 1.19 + +require github.com/mattn/go-sqlite3 v1.14.14 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d5da9bc --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= diff --git a/http.go b/http.go new file mode 100644 index 0000000..d5a1422 --- /dev/null +++ b/http.go @@ -0,0 +1,55 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" +) + +type ErrorResponse struct { + Message string `json:"message"` +} + +type NewMemoResponse struct { + ID int64 `json:"id"` +} + +func httpGetMemos(w http.ResponseWriter, r *http.Request) { + memos := getMemos(DB) + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(memos) + if err != nil { + log.Fatal(err) + } +} + +func httpCreateMemo(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + w.WriteHeader(400) + fmt.Fprintf(w, "ParseForm() err: %v", err) + return + } + message := r.FormValue("message") + if len(message) == 0 { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(422) + json.NewEncoder(w).Encode(ErrorResponse{"Memo must have a Message"}) + return + } + id := insertMemo(DB, message) + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(NewMemoResponse{id}) +} + +func endpoint(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "GET": + httpGetMemos(w, r) + case "POST": + httpCreateMemo(w, r) + default: + w.WriteHeader(405) + fmt.Fprintf(w, "HTTP method must be GET or POST") + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..3d02108 --- /dev/null +++ b/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "database/sql" + "errors" + _ "github.com/mattn/go-sqlite3" + "log" + "net/http" +) + +var DB *sql.DB + +func main() { + DB = openOrCreateDb("./memos.db") + defer DB.Close() + + http.HandleFunc("/memos", endpoint) + log.Println("starting server") + err := http.ListenAndServe(":80", nil) + if errors.Is(err, http.ErrServerClosed) { + log.Println("server closed") + } else if err != nil { + log.Fatalf("error starting server: %s\n", err) + } +} diff --git a/memos.db b/memos.db new file mode 100644 index 0000000..4c65e96 Binary files /dev/null and b/memos.db differ