diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..95c00ca
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+logs.log
+crud-api
+crud-api.exe
\ No newline at end of file
diff --git a/README.md b/README.md
index 9dc9a5e..781d913 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,9 @@
# CRUD api
-## A simple CRUD api written in Go`s standart library
+## A simple CRUD api written in Go
-API has implementation of "GET", "POST", "PUT", "DELETE" http methods, allowing to Create, Read, Update and Delete json objects in database.
+API has implementation of "GET", "POST", "PUT", "DELETE" http methods, allowing to Create, Read, Update and Delete objects in database via json input.
-The structure of a basic json object represents Go struct "RandomData" with exported fields "title" (string), "text" (string) and unexported fields "DateCreated" (time.Time), "LastUpdated" (time.Time) and ID (int64)
+---
-Example of a single object stored in a json database : {
- "ID": 1618064651615612586,
- "DateCreated": "2021-04-10T14:24:11.615612068Z",
- "LastUpdated": "2021-04-10T14:24:11.615612068Z",
- "title": "Title",
- "text": "text"
- }
+Just practicing things
-This project was my first take on such thing. The goal was to create a basic working example and it looks like that I`ve achieved that goal.
diff --git a/database/database.json b/database/database.json
deleted file mode 100644
index 05590d1..0000000
--- a/database/database.json
+++ /dev/null
@@ -1,23 +0,0 @@
-[
- {
- "ID": 1618064636424100339,
- "DateCreated": "2021-04-10T14:23:56.424100065Z",
- "LastUpdated": "2021-04-10T14:23:56.424100065Z",
- "title": "FUMO?",
- "text": "FUMO"
- },
- {
- "ID": 1618064651615612586,
- "DateCreated": "2021-04-10T14:24:11.615612068Z",
- "LastUpdated": "2021-04-10T14:24:11.615612068Z",
- "title": "Title",
- "text": "text"
- },
- {
- "ID": 1618065099475731301,
- "DateCreated": "2021-04-10T14:31:39.47573104Z",
- "LastUpdated": "2021-04-10T14:31:39.47573104Z",
- "title": "link",
- "text": "https://youtu.be/dQw4w9WgXcQ"
- }
-]
\ No newline at end of file
diff --git a/dbHandle/create.go b/dbHandle/create.go
new file mode 100644
index 0000000..7661d85
--- /dev/null
+++ b/dbHandle/create.go
@@ -0,0 +1,49 @@
+package dbhandle
+
+import (
+ "database/sql"
+ "os"
+ "path/filepath"
+
+ _ "github.com/mattn/go-sqlite3"
+)
+
+// full path to the local database that has been created by `CreateLocalDB`
+var dbpath string
+
+// Creates a local database file in the same directory
+// as executable if does not exist already. Creates a table "randomdata" with
+// such structure: (id PRIMARY KEY, title TEXT, text TEXT), which
+// represents underlying fields of `RandomData`
+func CreateLocalDB(dbName string) (*DB, error) {
+ // double check if dbName is actually just a name, not a path
+ dbName = filepath.Base(dbName)
+
+ executablePath, err := os.Executable()
+ if err != nil {
+ return nil, err
+ }
+ exeDir := filepath.Dir(executablePath)
+
+ dbpath = filepath.Join(exeDir, dbName)
+
+ // create db if does not exist
+ dbfile, err := os.OpenFile(dbpath, os.O_CREATE, os.ModePerm)
+ if err != nil {
+ return nil, err
+ }
+ dbfile.Close()
+
+ // create table that suits `RandomData` struct
+ db, err := sql.Open(drivername, dbpath)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = db.Exec("CREATE TABLE IF NOT EXISTS randomdata (id INTEGER PRIMARY KEY, title TEXT, text TEXT)")
+ if err != nil {
+ return nil, err
+ }
+
+ return &DB{db}, nil
+}
diff --git a/dbHandle/db.go b/dbHandle/db.go
new file mode 100644
index 0000000..84ae967
--- /dev/null
+++ b/dbHandle/db.go
@@ -0,0 +1,9 @@
+package dbhandle
+
+import "database/sql"
+
+type DB struct {
+ *sql.DB
+}
+
+const drivername string = "sqlite3"
diff --git a/dbHandle/handle.go b/dbHandle/handle.go
new file mode 100644
index 0000000..441b15f
--- /dev/null
+++ b/dbHandle/handle.go
@@ -0,0 +1,29 @@
+package dbhandle
+
+import (
+ "net/http"
+
+ randomdata "github.com/Unbewohnte/crud-api/randomData"
+ _ "github.com/mattn/go-sqlite3"
+)
+
+func (db *DB) GetEverything() ([]*randomdata.RandomData, error)
+
+func (db *DB) GetSpecific() (*randomdata.RandomData, error)
+
+func (db *DB) DeleteSpecific() error
+
+func (db *DB) PatchSpecific() error
+
+func (db *DB) Create(rd randomdata.RandomData) error {
+ _, err := db.Exec("INSERT INTO randomdata (title, text) VALUES (?, ?)", rd.Title, rd.Text)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (db *DB) HandleSpecificWeb(w http.ResponseWriter, r *http.Request)
+
+func (db *DB) HandleGlobalWeb(w http.ResponseWriter, r *http.Request)
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..08cc4bb
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/Unbewohnte/crud-api
+
+go 1.17
+
+require github.com/mattn/go-sqlite3 v1.14.8
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..e69de29
diff --git a/logs/logs.go b/logs/logs.go
new file mode 100644
index 0000000..1db05b8
--- /dev/null
+++ b/logs/logs.go
@@ -0,0 +1,26 @@
+package logs
+
+import (
+ "log"
+ "os"
+ "path/filepath"
+)
+
+// Create a logfile in the same directory as executable and set output
+// of `log` package to it
+func SetUp() error {
+ executablePath, err := os.Executable()
+ if err != nil {
+ return err
+ }
+ exeDir := filepath.Dir(executablePath)
+
+ logfile, err := os.Create(filepath.Join(exeDir, "logs.log"))
+ if err != nil {
+ return err
+ }
+
+ log.SetOutput(logfile)
+
+ return nil
+}
diff --git a/main.go b/main.go
index 77ec167..36e7233 100644
--- a/main.go
+++ b/main.go
@@ -1,370 +1,168 @@
package main
import (
- "encoding/json"
+ "flag"
"fmt"
- "io"
"log"
"net/http"
- "os"
- "path/filepath"
- "strconv"
- "strings"
"time"
-)
-
-type RandomData struct {
- // unexported for json
- ID int64
- DateCreated time.Time
- LastUpdated time.Time
- // exported for json
- Title string `json:"title"`
- Text string `json:"text"`
-}
-
-type randomDataHandler struct {
- dbFilepath string
-}
-func InitLogs() {
- var logsDir string = filepath.Join(".", "logs")
-
- err := os.MkdirAll(logsDir, os.ModePerm)
- if err != nil {
- panic(err)
- }
- logfile, err := os.Create(filepath.Join(logsDir, "logs.log"))
- if err != nil {
- panic(err)
- }
- log.SetOutput(logfile)
-}
-
-func homepage(w http.ResponseWriter, r *http.Request) {
- helpMessage := `
-
CRUD api in Go's standart library
-
- - (GET) /randomdata - to get all database
- - (GET) /randomdata/{id} - to get specific random data under corresponding id
- - (POST) /randomdata - to create random data
- - (DELETE) /randomdata/{id} - to delete specified random data
- - (PUT) /randomdata/{id} - to update random data with given id
-
- `
- fmt.Fprint(w, helpMessage)
-}
+ dbhandle "github.com/Unbewohnte/crud-api/dbHandle"
+ "github.com/Unbewohnte/crud-api/logs"
+)
-func newDatabaseHandler() *randomDataHandler {
- dbDirpath := filepath.Join(".", "database")
- err := os.MkdirAll(dbDirpath, os.ModePerm)
- if err != nil {
- panic(err)
- }
- dbFilepath := filepath.Join(dbDirpath, "database.json")
+var (
+ port *uint = flag.Uint("port", 8080, "Specifies a port on which the helping page will be served")
+ dbname string = "database.db"
+)
- dbFile, err := os.OpenFile(dbFilepath, os.O_CREATE, os.ModePerm)
+func init() {
+ // set up logs, parse flags
+ err := logs.SetUp()
if err != nil {
panic(err)
}
- defer dbFile.Close()
-
- log.Println("Successfully created new database handler")
-
- return &randomDataHandler{
- dbFilepath: dbFilepath,
- }
-}
-
-func (dbHandler *randomDataHandler) writeRandomData(newData RandomData) error {
- dbBytes, err := dbHandler.readDatabase()
- if err != nil {
- log.Println("Error reading db (writeRandomData) : ", err)
- }
-
- var db []RandomData
-
- err = json.Unmarshal(dbBytes, &db)
- if err != nil {
- log.Println("Error unmarshalling db (writeRandomData) : ", err)
- }
-
- db = append(db, newData)
-
- dbFile, err := os.OpenFile(dbHandler.dbFilepath, os.O_WRONLY, 0644)
- if err != nil {
- log.Println("Error opening db file (writeRandomData) : ", err)
- }
- defer dbFile.Close()
-
- jsonBytes, err := json.MarshalIndent(db, "", " ")
- if err != nil {
- log.Println("Error marshalling db (writeRandomData) : ", err)
- }
-
- dbFile.Write(jsonBytes)
-
- return nil
-}
-
-func (dbHandler *randomDataHandler) readDatabase() ([]byte, error) {
- dbBytes, err := os.ReadFile(dbHandler.dbFilepath)
- if err != nil {
- log.Println("Error reading db (readDatabase) : ", err)
- return nil, err
- }
- return dbBytes, nil
-}
-
-func (dbHandler *randomDataHandler) removeRandomData(id int64) error {
- dbBytes, err := dbHandler.readDatabase()
- if err != nil {
- return err
- }
-
- var db []RandomData
- err = json.Unmarshal(dbBytes, &db)
- if err != nil {
- return err
- }
- var counter int64 = 0
- for _, randomData := range db {
- if id == randomData.ID {
- db = append(db[:counter], db[counter+1:]...)
- err = dbHandler.writeDB(db)
- if err != nil {
- return err
- }
- }
- counter++
- }
- return nil
+ flag.Parse()
}
-func (dbHandler *randomDataHandler) writeDB(db []RandomData) error {
- jsonEncodedDB, err := json.MarshalIndent(db, "", " ")
- if err != nil {
- return err
- }
-
- dbFile, err := os.OpenFile(dbHandler.dbFilepath, os.O_WRONLY|os.O_TRUNC, 0644)
- if err != nil {
- return err
- }
- defer dbFile.Close()
-
- dbFile.Write(jsonEncodedDB)
-
- return nil
-}
-
-func (dbHandler *randomDataHandler) get(w http.ResponseWriter, r *http.Request) {
- dbBytes, err := dbHandler.readDatabase()
- if err != nil {
- log.Println("Error reading db (get) : ", err)
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
-
- w.Write(dbBytes)
-}
-
-func (dbHandler *randomDataHandler) create(w http.ResponseWriter, r *http.Request) {
- if r.Header.Get("content-type") != "application/json" {
- w.WriteHeader(http.StatusUnsupportedMediaType)
- w.Write([]byte(fmt.Sprintf("Got `%s` instead of `application/json`", r.Header.Get("content-type"))))
- return
- }
- requestBody, err := io.ReadAll(r.Body)
- if err != nil {
- log.Println("Error reading http request (create) : ", err)
- w.WriteHeader(http.StatusBadRequest)
- return
- }
- defer r.Body.Close()
-
- var newRandomData RandomData
- err = json.Unmarshal(requestBody, &newRandomData)
- if err != nil {
- log.Printf("Error unmarshalling http request (create) : %q \n", err)
- w.WriteHeader(http.StatusBadRequest)
- return
- }
-
- newRandomData.DateCreated = time.Now().UTC()
- newRandomData.LastUpdated = newRandomData.DateCreated
- newRandomData.ID = time.Now().UTC().UnixNano()
-
- err = dbHandler.writeRandomData(newRandomData)
- if err != nil {
- log.Println("Error writing RandomData (create): ", err)
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
-
- w.WriteHeader(http.StatusOK)
-
- log.Println("Successfuly added to db : ", newRandomData)
-}
-
-func (dbHandler *randomDataHandler) getSpecificRandomData(w http.ResponseWriter, r *http.Request) {
- givenID := strings.Split(r.URL.String(), "/")[2]
-
- dbBytes, err := dbHandler.readDatabase()
- if err != nil {
- log.Println("Error reading db (getSpecificRandomData) : ", err)
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
-
- var db []RandomData
-
- err = json.Unmarshal(dbBytes, &db)
- if err != nil {
- log.Println("Error unmarshalling database (getSpecificRandomData) : ", err)
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
-
- int64GivenID, _ := strconv.ParseInt(givenID, 10, 64)
- for _, randomData := range db {
- if int64GivenID == randomData.ID {
- response, err := json.MarshalIndent(randomData, "", " ")
- if err != nil {
- log.Println("Error marshaling response(getSpecificRandomData) : ", err)
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
-
- w.Write(response)
- return
- }
- }
- w.WriteHeader(http.StatusNotFound)
-}
-
-func (dbHandler *randomDataHandler) updateSpecificRandomData(w http.ResponseWriter, r *http.Request) {
- if r.Header.Get("content-type") != "application/json" {
- w.WriteHeader(http.StatusUnsupportedMediaType)
- w.Write([]byte(fmt.Sprintf("Got `%s` instead of `application/json`", r.Header.Get("content-type"))))
- return
- }
-
- requestBody, err := io.ReadAll(r.Body)
- if err != nil {
- log.Println("Error reading http request (create) : ", err)
- w.WriteHeader(http.StatusBadRequest)
- r.Body.Close()
- return
- }
- defer r.Body.Close()
-
- var givenUpdatedRandomData RandomData
- err = json.Unmarshal(requestBody, &givenUpdatedRandomData)
- if err != nil {
- log.Println("Error unmarshalling request body (updateSpecificRandomData) : ", err)
- return
- }
-
- givenID := strings.Split(r.URL.String(), "/")[2]
-
- dbBytes, err := dbHandler.readDatabase()
- if err != nil {
- log.Println("Error reading db (updateSpecificRandomData) : ", err)
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
- var db []RandomData
-
- err = json.Unmarshal(dbBytes, &db)
- if err != nil {
- log.Println("Error unmarshalling database (update) : ", err)
- return
- }
-
- int64GivenID, _ := strconv.ParseInt(givenID, 10, 64)
- var counter int64
- for _, randomData := range db {
- if int64GivenID == randomData.ID {
- var updatedRandomData RandomData
-
- updatedRandomData = givenUpdatedRandomData
- updatedRandomData.ID = randomData.ID
- updatedRandomData.DateCreated = randomData.DateCreated
- updatedRandomData.LastUpdated = time.Now().UTC()
-
- dbHandler.removeRandomData(int64GivenID)
- dbHandler.writeRandomData(updatedRandomData)
-
- log.Printf("Successfully updated RandomData with id %v \n", updatedRandomData.ID)
- return
- }
- counter++
- }
-
- w.WriteHeader(http.StatusNotFound)
-}
-
-func (dbHandler *randomDataHandler) deleteSpecificRandomData(w http.ResponseWriter, r *http.Request) {
- givenID := strings.Split(r.URL.String(), "/")[2]
-
- int64GivenID, _ := strconv.ParseInt(givenID, 10, 64)
-
- err := dbHandler.removeRandomData(int64GivenID)
+func main() {
+ // create a local db file
+ db, err := dbhandle.CreateLocalDB(dbname)
if err != nil {
- log.Println("Error removing RandomData (deleteSpecificRandomData) : ", err)
- w.WriteHeader(http.StatusInternalServerError)
- return
+ log.Fatalf("error setting up a database: %s", err)
}
- w.WriteHeader(http.StatusOK)
-
- log.Printf("Successfully deleted RandomData with id %v \n", int64GivenID)
-}
-
-func (dbHandler *randomDataHandler) handle(w http.ResponseWriter, r *http.Request) {
- switch r.Method {
- case "GET":
- dbHandler.get(w, r)
- case "POST":
- dbHandler.create(w, r)
- default:
- w.WriteHeader(http.StatusMethodNotAllowed)
- }
-}
-
-func (dbHandler *randomDataHandler) handleSpecific(w http.ResponseWriter, r *http.Request) {
- switch r.Method {
- case "GET":
- dbHandler.getSpecificRandomData(w, r)
- case "PUT":
- dbHandler.updateSpecificRandomData(w, r)
- case "DELETE":
- dbHandler.deleteSpecificRandomData(w, r)
- default:
- w.WriteHeader(http.StatusMethodNotAllowed)
- }
-}
-
-func init() {
- InitLogs()
-}
-
-func main() {
-
- databaseHandler := newDatabaseHandler()
-
- servemux := http.NewServeMux()
- servemux.HandleFunc("/", homepage)
- servemux.HandleFunc("/randomdata", databaseHandler.handle)
- servemux.HandleFunc("/randomdata/", databaseHandler.handleSpecific)
+ mux := http.NewServeMux()
+ mux.HandleFunc("/", helpPage)
+ mux.HandleFunc("/randomdata", db.HandleGlobalWeb)
+ mux.HandleFunc("/randomdata/", db.HandleSpecificWeb)
server := &http.Server{
- Addr: ":8000",
- Handler: servemux,
+ Addr: fmt.Sprintf(":%d", *port),
+ Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
}
+ log.Printf("Starting on port %d\n", *port)
+ fmt.Printf("Starting on port %d\n", *port)
+
log.Fatal(server.ListenAndServe())
}
+
+func helpPage(w http.ResponseWriter, r *http.Request) {
+ helpMessage := `
+ CRUD api
+
+ - (GET) /randomdata - to get all info from database (obviously a bad idea in serious projects ᗜˬᗜ)
+ - (GET) /randomdata/{id} - to get specific random data under corresponding id
+ - (POST) /randomdata - to create random data
+ - (DELETE) /randomdata/{id} - to delete specified random data
+ - (PUT) /randomdata/{id} - to update random data with given id
+
+ `
+ fmt.Fprint(w, helpMessage)
+}
+
+// func (dbHandler *randomDataHandler) updateSpecificRandomData(w http.ResponseWriter, r *http.Request) {
+// if r.Header.Get("content-type") != "application/json" {
+// w.WriteHeader(http.StatusUnsupportedMediaType)
+// w.Write([]byte(fmt.Sprintf("Got `%s` instead of `application/json`", r.Header.Get("content-type"))))
+// return
+// }
+
+// requestBody, err := io.ReadAll(r.Body)
+// if err != nil {
+// log.Println("Error reading http request (create) : ", err)
+// w.WriteHeader(http.StatusBadRequest)
+// r.Body.Close()
+// return
+// }
+// defer r.Body.Close()
+
+// var givenUpdatedRandomData RandomData
+// err = json.Unmarshal(requestBody, &givenUpdatedRandomData)
+// if err != nil {
+// log.Println("Error unmarshalling request body (updateSpecificRandomData) : ", err)
+// return
+// }
+
+// givenID := strings.Split(r.URL.String(), "/")[2]
+
+// dbBytes, err := dbHandler.readDatabase()
+// if err != nil {
+// log.Println("Error reading db (updateSpecificRandomData) : ", err)
+// w.WriteHeader(http.StatusInternalServerError)
+// return
+// }
+// var db []RandomData
+
+// err = json.Unmarshal(dbBytes, &db)
+// if err != nil {
+// log.Println("Error unmarshalling database (update) : ", err)
+// return
+// }
+
+// int64GivenID, _ := strconv.ParseInt(givenID, 10, 64)
+// var counter int64
+// for _, randomData := range db {
+// if int64GivenID == randomData.ID {
+// var updatedRandomData RandomData
+
+// updatedRandomData = givenUpdatedRandomData
+// updatedRandomData.ID = randomData.ID
+// updatedRandomData.DateCreated = randomData.DateCreated
+// updatedRandomData.LastUpdated = time.Now().UTC()
+
+// dbHandler.removeRandomData(int64GivenID)
+// dbHandler.writeRandomData(updatedRandomData)
+
+// log.Printf("Successfully updated RandomData with id %v \n", updatedRandomData.ID)
+// return
+// }
+// counter++
+// }
+
+// w.WriteHeader(http.StatusNotFound)
+// }
+
+// func (dbHandler *randomDataHandler) deleteSpecificRandomData(w http.ResponseWriter, r *http.Request) {
+// givenID := strings.Split(r.URL.String(), "/")[2]
+
+// int64GivenID, _ := strconv.ParseInt(givenID, 10, 64)
+
+// err := dbHandler.removeRandomData(int64GivenID)
+// if err != nil {
+// log.Println("Error removing RandomData (deleteSpecificRandomData) : ", err)
+// w.WriteHeader(http.StatusInternalServerError)
+// return
+// }
+
+// w.WriteHeader(http.StatusOK)
+
+// log.Printf("Successfully deleted RandomData with id %v \n", int64GivenID)
+// }
+
+// func (dbHandler *randomDataHandler) handle(w http.ResponseWriter, r *http.Request) {
+// switch r.Method {
+// case "GET":
+// dbHandler.get(w, r)
+// case "POST":
+// dbHandler.create(w, r)
+// default:
+// w.WriteHeader(http.StatusMethodNotAllowed)
+// }
+// }
+
+// func (dbHandler *randomDataHandler) handleSpecific(w http.ResponseWriter, r *http.Request) {
+// switch r.Method {
+// case "GET":
+// dbHandler.getSpecificRandomData(w, r)
+// case "PUT":
+// dbHandler.updateSpecificRandomData(w, r)
+// case "DELETE":
+// dbHandler.deleteSpecificRandomData(w, r)
+// default:
+// w.WriteHeader(http.StatusMethodNotAllowed)
+// }
+// }
diff --git a/randomData/randomData.go b/randomData/randomData.go
new file mode 100644
index 0000000..ed921bf
--- /dev/null
+++ b/randomData/randomData.go
@@ -0,0 +1,35 @@
+package randomdata
+
+import (
+ "encoding/json"
+ "time"
+)
+
+// The `bridge` between input values and a record in db
+type RandomData struct {
+ DateCreated int64
+ LastUpdated int64
+ Title string `json:"title"`
+ Text string `json:"text"`
+}
+
+// Create a new `RandomData`
+func New(title string, text string) *RandomData {
+ return &RandomData{
+ DateCreated: time.Now().UTC().Unix(),
+ LastUpdated: time.Now().UTC().Unix(),
+ Title: title,
+ Text: text,
+ }
+}
+
+// Unmarshal `RandomData` from Json encoded bytes
+func FromJson(jsonBytes []byte) (*RandomData, error) {
+ var randomData RandomData
+ err := json.Unmarshal(jsonBytes, &randomData)
+ if err != nil {
+ return nil, err
+ }
+
+ return &randomData, nil
+}