Browse Source

Rewrite from the ground; SQL database

main
Unbewohnte 3 years ago
parent
commit
77fb229583
  1. 3
      .gitignore
  2. 15
      README.md
  3. 23
      database/database.json
  4. 49
      dbHandle/create.go
  5. 9
      dbHandle/db.go
  6. 29
      dbHandle/handle.go
  7. 5
      go.mod
  8. 0
      go.sum
  9. 26
      logs/logs.go
  10. 480
      main.go
  11. 35
      randomData/randomData.go

3
.gitignore vendored

@ -0,0 +1,3 @@
logs.log
crud-api
crud-api.exe

15
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.

23
database/database.json

@ -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"
}
]

49
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
}

9
dbHandle/db.go

@ -0,0 +1,9 @@
package dbhandle
import "database/sql"
type DB struct {
*sql.DB
}
const drivername string = "sqlite3"

29
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)

5
go.mod

@ -0,0 +1,5 @@
module github.com/Unbewohnte/crud-api
go 1.17
require github.com/mattn/go-sqlite3 v1.14.8

26
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
}

480
main.go

@ -1,51 +1,60 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
dbhandle "github.com/Unbewohnte/crud-api/dbHandle"
"github.com/Unbewohnte/crud-api/logs"
)
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"`
}
var (
port *uint = flag.Uint("port", 8080, "Specifies a port on which the helping page will be served")
dbname string = "database.db"
)
type randomDataHandler struct {
dbFilepath string
func init() {
// set up logs, parse flags
err := logs.SetUp()
if err != nil {
panic(err)
}
func InitLogs() {
var logsDir string = filepath.Join(".", "logs")
flag.Parse()
}
err := os.MkdirAll(logsDir, os.ModePerm)
func main() {
// create a local db file
db, err := dbhandle.CreateLocalDB(dbname)
if err != nil {
panic(err)
log.Fatalf("error setting up a database: %s", err)
}
logfile, err := os.Create(filepath.Join(logsDir, "logs.log"))
if err != nil {
panic(err)
mux := http.NewServeMux()
mux.HandleFunc("/", helpPage)
mux.HandleFunc("/randomdata", db.HandleGlobalWeb)
mux.HandleFunc("/randomdata/", db.HandleSpecificWeb)
server := &http.Server{
Addr: fmt.Sprintf(":%d", *port),
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
}
log.SetOutput(logfile)
log.Printf("Starting on port %d\n", *port)
fmt.Printf("Starting on port %d\n", *port)
log.Fatal(server.ListenAndServe())
}
func homepage(w http.ResponseWriter, r *http.Request) {
func helpPage(w http.ResponseWriter, r *http.Request) {
helpMessage := `
<h1>CRUD api in Go's standart library</h1>
<h1>CRUD api</h1>
<ul>
<li> (GET) <a href="/randomdata">/randomdata</a> - to get all database </li>
<li> (GET) <a href="/randomdata">/randomdata</a> - to get all info from database (obviously a bad idea in serious projects ᗜˬᗜ) </li>
<li> (GET) /randomdata/{id} - to get specific random data under corresponding id </li>
<li> (POST) /randomdata - to create random data </li>
<li> (DELETE) /randomdata/{id} - to delete specified random data </li>
@ -55,316 +64,105 @@ func homepage(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, helpMessage)
}
func newDatabaseHandler() *randomDataHandler {
dbDirpath := filepath.Join(".", "database")
err := os.MkdirAll(dbDirpath, os.ModePerm)
if err != nil {
panic(err)
}
dbFilepath := filepath.Join(dbDirpath, "database.json")
dbFile, err := os.OpenFile(dbFilepath, os.O_CREATE, os.ModePerm)
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
}
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)
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)
}
}
func init() {
InitLogs()
}
func main() {
databaseHandler := newDatabaseHandler()
servemux := http.NewServeMux()
servemux.HandleFunc("/", homepage)
servemux.HandleFunc("/randomdata", databaseHandler.handle)
servemux.HandleFunc("/randomdata/", databaseHandler.handleSpecific)
server := &http.Server{
Addr: ":8000",
Handler: servemux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
}
log.Fatal(server.ListenAndServe())
}
// 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)
// }
// }

35
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
}
Loading…
Cancel
Save