From 0b36bcbd6af65d79cb7d25a6ea4f02ef8036e4bd Mon Sep 17 00:00:00 2001 From: William P Date: Sun, 29 Dec 2024 19:48:41 -0500 Subject: [PATCH] abstraction --- api/api.go | 43 ++++++++++ api/db.go | 21 +++++ api/error.go | 41 ++++++++++ api/example_data.go | 13 +++ api/message.go | 68 +++++++++++++++ api/response.go | 23 ++++++ api/user.go | 10 +++ main.go | 195 +------------------------------------------- 8 files changed, 221 insertions(+), 193 deletions(-) create mode 100644 api/api.go create mode 100644 api/db.go create mode 100644 api/error.go create mode 100644 api/example_data.go create mode 100644 api/message.go create mode 100644 api/response.go create mode 100644 api/user.go diff --git a/api/api.go b/api/api.go new file mode 100644 index 0000000..0cf0386 --- /dev/null +++ b/api/api.go @@ -0,0 +1,43 @@ +package api + +import ( + "flag" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/render" +) + +func Start() { + flag.Parse() + + r := chi.NewRouter() + + r.Use(middleware.RequestID) + r.Use(middleware.Logger) + r.Use(middleware.Recoverer) + r.Use(middleware.URLFormat) + r.Use(render.SetContentType(render.ContentTypeJSON)) + + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("hello world")) + }) + + r.Get("/ping", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("pong")) + }) + + r.Get("/panic", func(w http.ResponseWriter, r *http.Request) { + panic("oh no") + }) + + r.Route("/messages", func(r chi.Router) { + r.Route("/{messageID}", func(r chi.Router) { + r.Use(MessageCtx) // Load message + r.Get("/", GetMessage) + }) + }) + + http.ListenAndServe(":3000", r) +} diff --git a/api/db.go b/api/db.go new file mode 100644 index 0000000..03dc161 --- /dev/null +++ b/api/db.go @@ -0,0 +1,21 @@ +package api + +import "errors" + +func dbGetUser(id int64) (*User, error) { + for _, u := range users { + if u.ID == id { + return u, nil + } + } + return nil, errors.New("User not found") +} + +func dbGetMessage(id string) (*Message, error) { + for _, a := range messages { + if a.ID == id { + return a, nil + } + } + return nil, errors.New("Message not found") +} diff --git a/api/error.go b/api/error.go new file mode 100644 index 0000000..b7e8404 --- /dev/null +++ b/api/error.go @@ -0,0 +1,41 @@ +package api + +import ( + "net/http" + + "github.com/go-chi/render" +) + +type ErrResponse struct { + Err error `json:"-"` // low-level runtime error + HTTPStatusCode int `json:"-"` // http response status code + + StatusText string `json:"status"` // user-level status message + AppCode int64 `json:"code,omitempty"` // application-specific error code + ErrorText string `json:"error,omitempty"` // application-level error message, for debugging +} + +func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error { + render.Status(r, e.HTTPStatusCode) + return nil +} + +func ErrInvalidRequest(err error) render.Renderer { + return &ErrResponse{ + Err: err, + HTTPStatusCode: 400, + StatusText: "Invalid request.", + ErrorText: err.Error(), + } +} + +func ErrRender(err error) render.Renderer { + return &ErrResponse{ + Err: err, + HTTPStatusCode: 422, + StatusText: "Error rendering response.", + ErrorText: err.Error(), + } +} + +var ErrNotFound = &ErrResponse{HTTPStatusCode: 404, StatusText: "Resource not found."} diff --git a/api/example_data.go b/api/example_data.go new file mode 100644 index 0000000..11b8d0c --- /dev/null +++ b/api/example_data.go @@ -0,0 +1,13 @@ +package api + +var messages = []*Message{ + {ID: "1", UserID: 1, Body: "hello", Timestamp: 1234567890}, + {ID: "2", UserID: 2, Body: "world", Timestamp: 1234567890}, + {ID: "3", UserID: 1, Body: "abababa", Timestamp: 1234567890}, + {ID: "4", UserID: 2, Body: "bitch", Timestamp: 1234567890}, +} + +var users = []*User{ + {ID: 1, Name: "duby"}, + {ID: 2, Name: "astolfo"}, +} diff --git a/api/message.go b/api/message.go new file mode 100644 index 0000000..0caa74c --- /dev/null +++ b/api/message.go @@ -0,0 +1,68 @@ +package api + +import ( + "context" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +func MessageCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var message *Message + var err error + + if messageID := chi.URLParam(r, "messageID"); messageID != "" { + message, err = dbGetMessage(messageID) + } else { + render.Render(w, r, ErrNotFound) + return + } + if err != nil { + render.Render(w, r, ErrNotFound) + return + } + + ctx := context.WithValue(r.Context(), messageKey{}, message) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} +func GetMessage(w http.ResponseWriter, r *http.Request) { + message, ok := r.Context().Value(messageKey{}).(*Message) + if !ok || message == nil { + render.Render(w, r, ErrNotFound) + return + } + + if err := render.Render(w, r, NewMessageResponse(message)); err != nil { + render.Render(w, r, ErrRender(err)) + return + } + +} + +type messageKey struct{} + +type Message struct { + ID string `json:"id"` + UserID int64 `json:"user_id"` + Body string `json:"body"` + Timestamp int64 `json:"timestamp"` +} + +type MessageRequest struct { + *Message + + User *UserPayload `json:"user"` + + ProtectedID string `json:"id"` +} + +type MessageResponse struct { + *Message + + User *UserPayload `json:"user,omitempty"` + + Elapsed int64 `json:"elapsed"` +} diff --git a/api/response.go b/api/response.go new file mode 100644 index 0000000..deb1ec8 --- /dev/null +++ b/api/response.go @@ -0,0 +1,23 @@ +package api + +import "net/http" + +func NewMessageResponse(message *Message) *MessageResponse { + resp := &MessageResponse{Message: message} + + if resp.User == nil { + if user, _ := dbGetUser(resp.UserID); user != nil { + resp.User = NewUserPayloadResponse(user) + } + } + + return resp +} + +func (mr *MessageResponse) Render(w http.ResponseWriter, r *http.Request) error { + return nil +} + +func NewUserPayloadResponse(user *User) *UserPayload { + return &UserPayload{User: user} +} diff --git a/api/user.go b/api/user.go new file mode 100644 index 0000000..0c3cc50 --- /dev/null +++ b/api/user.go @@ -0,0 +1,10 @@ +package api + +type User struct { + ID int64 `json:"id"` + Name string `json:"name"` +} + +type UserPayload struct { + *User +} diff --git a/main.go b/main.go index 4d9c011..ecc1c75 100644 --- a/main.go +++ b/main.go @@ -1,200 +1,9 @@ package main import ( - "context" - "errors" - "flag" - "net/http" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "github.com/go-chi/render" + "git.dubyatp.xyz/chat-api-server/api" ) -//var routes = flag.Bool("routes", false, "Generate router documentation") - func main() { - flag.Parse() - - r := chi.NewRouter() - - r.Use(middleware.RequestID) - r.Use(middleware.Logger) - r.Use(middleware.Recoverer) - r.Use(middleware.URLFormat) - r.Use(render.SetContentType(render.ContentTypeJSON)) - - r.Get("/", func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("hello world")) - }) - - r.Get("/ping", func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("pong")) - }) - - r.Get("/panic", func(w http.ResponseWriter, r *http.Request) { - panic("oh no") - }) - - r.Route("/messages", func(r chi.Router) { - r.Route("/{messageID}", func(r chi.Router) { - r.Use(MessageCtx) // Load message - r.Get("/", GetMessage) - }) - }) - - http.ListenAndServe(":3000", r) - -} - -type ErrResponse struct { - Err error `json:"-"` // low-level runtime error - HTTPStatusCode int `json:"-"` // http response status code - - StatusText string `json:"status"` // user-level status message - AppCode int64 `json:"code,omitempty"` // application-specific error code - ErrorText string `json:"error,omitempty"` // application-level error message, for debugging -} - -func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error { - render.Status(r, e.HTTPStatusCode) - return nil -} - -func ErrInvalidRequest(err error) render.Renderer { - return &ErrResponse{ - Err: err, - HTTPStatusCode: 400, - StatusText: "Invalid request.", - ErrorText: err.Error(), - } -} - -func ErrRender(err error) render.Renderer { - return &ErrResponse{ - Err: err, - HTTPStatusCode: 422, - StatusText: "Error rendering response.", - ErrorText: err.Error(), - } -} - -var ErrNotFound = &ErrResponse{HTTPStatusCode: 404, StatusText: "Resource not found."} - -type messageKey struct{} - -func MessageCtx(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var message *Message - var err error - - if messageID := chi.URLParam(r, "messageID"); messageID != "" { - message, err = dbGetMessage(messageID) - } else { - render.Render(w, r, ErrNotFound) - return - } - if err != nil { - render.Render(w, r, ErrNotFound) - return - } - - ctx := context.WithValue(r.Context(), messageKey{}, message) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} -func GetMessage(w http.ResponseWriter, r *http.Request) { - message, ok := r.Context().Value(messageKey{}).(*Message) - if !ok || message == nil { - render.Render(w, r, ErrNotFound) - return - } - - if err := render.Render(w, r, NewMessageResponse(message)); err != nil { - render.Render(w, r, ErrRender(err)) - return - } - -} - -func NewMessageResponse(message *Message) *MessageResponse { - resp := &MessageResponse{Message: message} - - if resp.User == nil { - if user, _ := dbGetUser(resp.UserID); user != nil { - resp.User = NewUserPayloadResponse(user) - } - } - - return resp -} - -func NewUserPayloadResponse(user *User) *UserPayload { - return &UserPayload{User: user} -} - -type User struct { - ID int64 `json:"id"` - Name string `json:"name"` -} - -type Message struct { - ID string `json:"id"` - UserID int64 `json:"user_id"` - Body string `json:"body"` - Timestamp int64 `json:"timestamp"` -} - -var users = []*User{ - {ID: 1, Name: "duby"}, - {ID: 2, Name: "astolfo"}, -} - -var messages = []*Message{ - {ID: "1", UserID: 1, Body: "hello", Timestamp: 1234567890}, - {ID: "2", UserID: 2, Body: "world", Timestamp: 1234567890}, - {ID: "3", UserID: 1, Body: "abababa", Timestamp: 1234567890}, - {ID: "4", UserID: 2, Body: "bitch", Timestamp: 1234567890}, -} - -type UserPayload struct { - *User -} - -type MessageRequest struct { - *Message - - User *UserPayload `json:"user"` - - ProtectedID string `json:"id"` -} - -type MessageResponse struct { - *Message - - User *UserPayload `json:"user,omitempty"` - - Elapsed int64 `json:"elapsed"` -} - -func (mr *MessageResponse) Render(w http.ResponseWriter, r *http.Request) error { - return nil -} - -func dbGetUser(id int64) (*User, error) { - for _, u := range users { - if u.ID == id { - return u, nil - } - } - return nil, errors.New("User not found") -} - -func dbGetMessage(id string) (*Message, error) { - for _, a := range messages { - if a.ID == id { - return a, nil - } - } - return nil, errors.New("Message not found") + api.Start() }