diff --git a/blogs/models.go b/blogs/models.go new file mode 100644 index 0000000..391602b --- /dev/null +++ b/blogs/models.go @@ -0,0 +1,66 @@ +package blogs + +import ( + "errors" +) + +type Blog struct { + ID string `json:"id"` + Title string `json:"title"` + Author string `json:"author"` + Likes int `json:"likes"` + Comments []Comment `json:"comments"` +} + +type Comment struct { + Author string `json:"author"` + CommentText string `json:"commentText"` +} + +var ( + NotFoundErr = errors.New("not found") +) + +type MemStore struct { + list map[string]Blog +} + +func NewMemStore() *MemStore { + list := make(map[string]Blog) + return &MemStore{ + list, + } +} + +func (m MemStore) Add(title string, blog Blog) error { + m.list[title] = blog + return nil +} + +func (m MemStore) Get(title string) (Blog, error) { + + if val, ok := m.list[title]; ok { + return val, nil + } + + return Blog{}, NotFoundErr +} + +func (m MemStore) List() (map[string]Blog, error) { + return m.list, nil +} + +func (m MemStore) Update(title string, blog Blog) error { + + if _, ok := m.list[title]; ok { + m.list[title] = blog + return nil + } + + return NotFoundErr +} + +func (m MemStore) Remove(title string) error { + delete(m.list, title) + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..742c319 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module blog-app + +go 1.22.0 + +require ( + github.com/gosimple/slug v1.13.1 + github.com/gosimple/unidecode v1.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b7fc16e --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q= +github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= +github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= +github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= diff --git a/main.go b/main.go new file mode 100644 index 0000000..10d05c0 --- /dev/null +++ b/main.go @@ -0,0 +1,109 @@ +// Implemented following this guide +// https://www.jetbrains.com/guide/go/tutorials/rest_api_series/stdlib/ + +package main + +import ( + "blog-app/blogs" + "encoding/json" + "net/http" + "regexp" + + "github.com/gosimple/slug" +) + +func main() { + store := blogs.NewMemStore() + blogsHandler := NewBlogsHandler(store) + + mux := http.NewServeMux() + + mux.Handle("/", &HomeHandler{}) + mux.Handle("/blogs", blogsHandler) + mux.Handle("/blog", blogsHandler) + + http.ListenAndServe(":8080", mux) +} + +type HomeHandler struct{} + +func (h *HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello World from Go :D")) +} + +type BlogsHandler struct { + store blogStore +} + +func (h *BlogsHandler) CreateBlog(w http.ResponseWriter, r *http.Request) { + var blog blogs.Blog + if err := json.NewDecoder(r.Body).Decode(&blog); err != nil { + InternalServerErrorHandler(w, r) + return + } + + resourceID := slug.Make(blog.Title) + + if err := h.store.Add(resourceID, blog); err != nil { + InternalServerErrorHandler(w, r) + return + } + + w.WriteHeader(http.StatusOK) +} + +func (h *BlogsHandler) ListBlogs(w http.ResponseWriter, r *http.Request) {} +func (h *BlogsHandler) GetBlog(w http.ResponseWriter, r *http.Request) {} +func (h *BlogsHandler) UpdateBlog(w http.ResponseWriter, r *http.Request) {} +func (h *BlogsHandler) DeleteBlog(w http.ResponseWriter, r *http.Request) {} + +var ( + BlogRe = regexp.MustCompile(`^/blogs/*$`) + BlogReWithID = regexp.MustCompile(`^/blogs/([a-z0-9]+(?:-[a-z0-9]+)+)$`) +) + +func (h *BlogsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodPost && BlogRe.MatchString(r.URL.Path): + h.CreateBlog(w, r) + return + case r.Method == http.MethodGet && BlogRe.MatchString(r.URL.Path): + h.ListBlogs(w, r) + return + case r.Method == http.MethodGet && BlogReWithID.MatchString(r.URL.Path): + h.GetBlog(w, r) + return + case r.Method == http.MethodPut && BlogReWithID.MatchString(r.URL.Path): + h.UpdateBlog(w, r) + return + case r.Method == http.MethodDelete && BlogReWithID.MatchString(r.URL.Path): + h.DeleteBlog(w, r) + return + default: + return + } +} + +func NewBlogsHandler(s blogStore) *BlogsHandler { + return &BlogsHandler{ + store: s, + } +} + +type blogStore interface { + Add(name string, blog blogs.Blog) error + Get(name string) (blogs.Blog, error) + Update(name string, blog blogs.Blog) error + List() (map[string]blogs.Blog, error) + Remove(name string) error +} + +func InternalServerErrorHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("500 Internal Server Error")) +} + +func NotFoundHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("404 Not Found")) +}