From 874fdd0e25812fbe31bec99d58c3134e2ba09548 Mon Sep 17 00:00:00 2001 From: Nicolas Dinquel Date: Wed, 19 Jun 2019 17:17:30 +0200 Subject: [PATCH] better hierarchy and better way to make a CRUD --- .gitignore | 4 +- driver/driver.go | 39 +++++++++ handler/http/handler.go | 98 ++++++++++++++++++++++ main.go | 154 ++++++---------------------------- models/errors.go | 8 ++ models/post.go | 8 ++ repository/post/post_mysql.go | 118 ++++++++++++++++++++++++++ repository/repository.go | 16 ++++ 8 files changed, 314 insertions(+), 131 deletions(-) create mode 100644 driver/driver.go create mode 100644 handler/http/handler.go create mode 100644 models/errors.go create mode 100644 models/post.go create mode 100644 repository/post/post_mysql.go create mode 100644 repository/repository.go diff --git a/.gitignore b/.gitignore index 9ebd30b..d700f66 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ -# ---> VisualStudioCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json - +vendor/* +blog diff --git a/driver/driver.go b/driver/driver.go new file mode 100644 index 0000000..5eb0b77 --- /dev/null +++ b/driver/driver.go @@ -0,0 +1,39 @@ +package driver + +import ( + "database/sql" + "fmt" + + _ "github.com/go-sql-driver/mysql" +) + +// DB ... +type DB struct { + SQL *sql.DB + // Mgo *mgo.database +} + +var dbConn = &DB{} + +// ConnectSQL ... +func ConnectSQL(host, port, uname, pass, dbname string) (*DB, error) { + dbSource := fmt.Sprintf( + "%s:%s@tcp(%s:%s)/%s?charset=utf8", + uname, + pass, + host, + port, + dbname, + ) + d, err := sql.Open("mysql", dbSource) + if err != nil { + panic(err) + } + dbConn.SQL = d + return dbConn, err +} + +// connectMongo ... +func connectMongo(host, port, uname, pass string) error { + return nil +} diff --git a/handler/http/handler.go b/handler/http/handler.go new file mode 100644 index 0000000..c4e20be --- /dev/null +++ b/handler/http/handler.go @@ -0,0 +1,98 @@ +package handler + +import ( + "blog/driver" + models "blog/models" + repository "blog/repository" + post "blog/repository/post" + "encoding/json" + "fmt" + "net/http" + "strconv" + + "github.com/go-chi/chi" +) + +// NewPostHandler ... +func NewPostHandler(db *driver.DB) *Post { + return &Post{ + repo: post.NewSQLPostRepo(db.SQL), + } +} + +// Post ... +type Post struct { + repo repository.PostRepo +} + +// Fetch ... +func (p *Post) Fetch(w http.ResponseWriter, r *http.Request) { + payload, _ := p.repo.Fetch(r.Context(), 25) + + respondwithJSON(w, http.StatusOK, payload) +} + +// Create a new post +func (p *Post) Create(w http.ResponseWriter, r *http.Request) { + post := models.Post{} + json.NewDecoder(r.Body).Decode(&post) + + newID, err := p.repo.Create(r.Context(), &post) + fmt.Println(newID) + if err != nil { + respondWithError(w, http.StatusInternalServerError, "Server Error") + } + respondwithJSON(w, http.StatusCreated, map[string]string{"message": "Successfully Created"}) +} + +// Update a post by id +func (p *Post) Update(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.Atoi(chi.URLParam(r, "id")) + data := models.Post{ID: int(id)} + json.NewDecoder(r.Body).Decode(&data) + payload, err := p.repo.Update(r.Context(), &data) + + if err != nil { + respondWithError(w, http.StatusInternalServerError, "Server Error") + } + + respondwithJSON(w, http.StatusOK, payload) +} + +// GetByID returns a post details +func (p *Post) GetByID(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.Atoi(chi.URLParam(r, "id")) + payload, err := p.repo.GetByID(r.Context(), int64(id)) + + if err != nil { + respondWithError(w, http.StatusNoContent, "Content not found") + } + + respondwithJSON(w, http.StatusOK, payload) +} + +// Delete a post +func (p *Post) Delete(w http.ResponseWriter, r *http.Request) { + id, _ := strconv.Atoi(chi.URLParam(r, "id")) + _, err := p.repo.Delete(r.Context(), int64(id)) + + if err != nil { + respondWithError(w, http.StatusInternalServerError, "Server Error") + } + + respondwithJSON(w, http.StatusMovedPermanently, map[string]string{"message": "Delete Successfully"}) +} + +// respondwithJSON write json response format +func respondwithJSON(w http.ResponseWriter, code int, payload interface{}) { + response, _ := json.Marshal(payload) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + w.Write(response) +} + +// respondwithError return error message +func respondWithError(w http.ResponseWriter, code int, msg string) { + respondwithJSON(w, code, map[string]string{"message": msg}) +} diff --git a/main.go b/main.go index d7fff8e..0a0a82a 100644 --- a/main.go +++ b/main.go @@ -1,146 +1,42 @@ package main import ( - "database/sql" - "encoding/json" + "blog/driver" + ph "blog/handler/http" "fmt" "net/http" + "os" - "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" - _ "github.com/go-sql-driver/mysql" -) - -var router *chi.Mux -var db *sql.DB -const ( - dbName = "go-mysql-crud" - dbPass = "" - dbHost = "localhost" - dbPort = "3306" + "github.com/go-chi/chi" ) -func routers() *chi.Mux { - router.Get("/posts", AllPosts) - router.Get("/posts/{id}", DetailPost) - router.Post("/posts", CreatePost) - router.Put("/posts/{id}", UpdatePost) - router.Delete("/posts/{id}", DeletePost) - - return router -} - -func init() { - router = chi.NewRouter() - router.Use(middleware.Recoverer) - - dbSource := fmt.Sprintf("root:%s@tcp(%s:%s)/%s?charset=utf8", dbPass, dbHost, dbPort, dbName) - fmt.Println(dbSource) - var err error - db, err = sql.Open("mysql", dbSource) - - catch(err) -} - -// Post ... -type Post struct { - ID int `json:"id"` - Title string `json:"title"` - Content string `json:"content"` -} - -// CreatePost ... -func CreatePost(w http.ResponseWriter, r *http.Request) { - var post Post - json.NewDecoder(r.Body).Decode(&post) - - query, err := db.Prepare("Insert posts SET title=?, content=?") - catch(err) - - _, er := query.Exec(post.Title, post.Content) - catch(er) - defer query.Close() - - respondwithJSON(w, http.StatusCreated, map[string]string{"message": "successfully created"}) -} - -// UpdatePost ... -func UpdatePost(w http.ResponseWriter, r *http.Request) { - var post Post - id := chi.URLParam(r, "id") - json.NewDecoder(r.Body).Decode(&post) - - query, err := db.Prepare("Update posts set title=?, content=? where id=?") - catch(err) - _, er := query.Exec(post.Title, post.Content, id) - catch(er) - - defer query.Close() - - respondwithJSON(w, http.StatusOK, map[string]string{"message": "update successfully"}) -} - -// DeletePost ... -func DeletePost(w http.ResponseWriter, r *http.Request) { - id := chi.URLParam(r, "id") - - query, err := db.Prepare("delete from post where id=?") - catch(err) - _, er := query.Exec(id) - catch(er) - query.Close() - - respondwithJSON(w, http.StatusOK, map[string]string{"message": "deleted successfully"}) -} - -// AllPosts ... -func AllPosts(w http.ResponseWriter, r *http.Request) { - errors := []error{} - payload := []Post{} - - rows, err := db.Query("Select id, title, content From posts") - catch(err) - - defer rows.Close() - - for rows.Next() { - data := Post{} - - er := rows.Scan(&data.ID, &data.Title, &data.Content) - - if er != nil { - errors = append(errors, er) - } - payload = append(payload, data) - } - - respondwithJSON(w, http.StatusOK, payload) -} - -// DetailPost ... -func DetailPost(w http.ResponseWriter, r *http.Request) { - payload := Post{} - id := chi.URLParam(r, "id") - - row := db.QueryRow("Select if, title, content From posts where id=?", id) +func main() { + dbName := os.Getenv("DB_NAME") + dbPass := os.Getenv("DB_PASS") + dbHost := os.Getenv("DB_HOST") + dbPort := os.Getenv("DB_PORT") - err := row.Scan( - &payload.ID, - &payload.Title, - &payload.Content, - ) + println("this is db") + connection, err := driver.ConnectSQL(dbHost, dbPort, "root", dbPass, dbName) if err != nil { - respondWithError(w, http.StatusNotFound, "no rows in result set") - return + fmt.Println(err) + os.Exit(-1) } - respondwithJSON(w, http.StatusOK, payload) -} + r := chi.NewRouter() + r.Use(middleware.Recoverer) + r.Use(middleware.Logger) -// -func main() { - routers() - http.ListenAndServe(":8005", Logger()) + pHandler := ph.NewPostHandler(connection) + r.Get("/posts", pHandler.Fetch) + r.Get("/posts/{id}", pHandler.GetByID) + r.Post("/posts", pHandler.Create) + r.Put("/posts/{id}", pHandler.Update) + r.Delete("/posts/{id}", pHandler.Delete) + + fmt.Println("Server listen at :8005") + http.ListenAndServe(":8005", r) } diff --git a/models/errors.go b/models/errors.go new file mode 100644 index 0000000..19bcfe0 --- /dev/null +++ b/models/errors.go @@ -0,0 +1,8 @@ +package models + +import "errors" + +var ( + // ErrNotFound ... + ErrNotFound = errors.New("requested item is not found") +) diff --git a/models/post.go b/models/post.go new file mode 100644 index 0000000..7588465 --- /dev/null +++ b/models/post.go @@ -0,0 +1,8 @@ +package models + +// Post ... +type Post struct { + ID int `json:"id"` + Title string `json:"title"` + Content string `json:"content"` +} diff --git a/repository/post/post_mysql.go b/repository/post/post_mysql.go new file mode 100644 index 0000000..029ac88 --- /dev/null +++ b/repository/post/post_mysql.go @@ -0,0 +1,118 @@ +package post + +import ( + "context" + "database/sql" + + models "blog/models" + pRepo "blog/repository" +) + +// NewSQLPostRepo ... +func NewSQLPostRepo(Conn *sql.DB) pRepo.PostRepo { + return &mysqlPostRepo{ + Conn: Conn, + } +} + +type mysqlPostRepo struct { + Conn *sql.DB +} + +// fetch ... +func (m *mysqlPostRepo) fetch(ctx context.Context, query string, args ...interface{}) ([]*models.Post, error) { + rows, err := m.Conn.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + payload := make([]*models.Post, 0) + for rows.Next() { + data := new(models.Post) + err := rows.Scan( + &data.ID, + &data.Title, + &data.Content, + ) + if err != nil { + return nil, err + } + payload = append(payload, data) + } + return payload, nil +} + +func (m *mysqlPostRepo) Fetch(ctx context.Context, num int64) ([]*models.Post, error) { + query := "Select id, title, content From posts limit ?" + + return m.fetch(ctx, query, num) +} + +func (m *mysqlPostRepo) GetByID(ctx context.Context, id int64) (*models.Post, error) { + query := "Select id, title, content From posts where id=?" + + rows, err := m.fetch(ctx, query, id) + if err != nil { + return nil, err + } + payload := &models.Post{} + if len(rows) > 0 { + payload = rows[0] + } else { + return nil, models.ErrNotFound + } + return payload, nil +} + +func (m *mysqlPostRepo) Create(ctx context.Context, p *models.Post) (int64, error) { + query := "Insert posts SET title=?, content=?" + + stmt, err := m.Conn.PrepareContext(ctx, query) + if err != nil { + return -1, err + } + + res, err := stmt.ExecContext(ctx, p.Title, p.Content) + defer stmt.Close() + + if err != nil { + return -1, err + } + return res.LastInsertId() +} + +func (m *mysqlPostRepo) Update(ctx context.Context, p *models.Post) (*models.Post, error) { + query := "Update posts set title=?, content=? where id=?" + + stmt, err := m.Conn.PrepareContext(ctx, query) + if err != nil { + return nil, err + } + _, err = stmt.ExecContext( + ctx, + p.Title, + p.Content, + p.ID, + ) + if err != nil { + return nil, err + } + defer stmt.Close() + return p, nil +} + +func (m *mysqlPostRepo) Delete(ctx context.Context, id int64) (bool, error) { + query := "Delete From posts Where id=?" + + stmt, err := m.Conn.PrepareContext(ctx, query) + if err != nil { + return false, err + } + _, err = stmt.ExecContext(ctx, id) + if err != nil { + return false, err + } + defer stmt.Close() + return true, nil +} diff --git a/repository/repository.go b/repository/repository.go new file mode 100644 index 0000000..56de9b2 --- /dev/null +++ b/repository/repository.go @@ -0,0 +1,16 @@ +package repository + +import ( + "context" + + "blog/models" +) + +// PostRepo explain... +type PostRepo interface { + Fetch(ctx context.Context, num int64) ([]*models.Post, error) + GetByID(ctx context.Context, id int64) (*models.Post, error) + Create(ctx context.Context, p *models.Post) (int64, error) + Update(ctx context.Context, p *models.Post) (*models.Post, error) + Delete(ctx context.Context, id int64) (bool, error) +}