package api import ( "context" "encoding/json" "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/render" "github.com/google/uuid" "time" ) 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 EditMessage(w http.ResponseWriter, r *http.Request) { message, ok := r.Context().Value(messageKey{}).(*Message) if !ok || message == nil { render.Render(w, r, ErrNotFound) return } err := r.ParseMultipartForm(64 << 10) if err != nil { http.Error(w, "Unable to parse form", http.StatusBadRequest) return } body := r.FormValue("body") if body == "" { http.Error(w, "Message body cannot be empty", http.StatusBadRequest) return } message.Body = body message.Edited = time.Now() err = dbUpdateMessage(message) if err != nil { render.Render(w, r, ErrRender(err)) return } if err := render.Render(w, r, NewMessageResponse(message)); err != nil { render.Render(w, r, ErrRender(err)) return } } func DeleteMessage(w http.ResponseWriter, r *http.Request) { message, ok := r.Context().Value(messageKey{}).(*Message) if !ok || message == nil { render.Render(w, r, ErrNotFound) return } dbDeleteMessage(message.ID) if err := render.Render(w, r, NewMessageResponse(message)); err != nil { render.Render(w, r, ErrRender(err)) return } } func ListMessages(w http.ResponseWriter, r *http.Request) { dbMessages, err := dbGetAllMessages() if err != nil { render.Render(w, r, ErrRender(err)) return } if err := render.RenderList(w, r, NewMessageListResponse(dbMessages)); err != nil { render.Render(w, r, ErrRender(err)) return } } func newMessageID() string { return "msg_" + uuid.New().String() } func NewMessage(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(64 << 10) if err != nil { http.Error(w, "Unable to parse form", http.StatusBadRequest) return } // userID := r.FormValue("user_id") //if userID == "" { // http.Error(w, "Invalid user ID", http.StatusBadRequest) // return //} var user = r.Context().Value(userKey{}).(*User) body := r.FormValue("body") if body == "" { http.Error(w, "Invalid body", http.StatusBadRequest) return } msg := Message{ ID: newMessageID(), UserID: user.ID, Body: body, Timestamp: time.Now(), Edited: time.Time{}, } err = dbAddMessage(&msg) if err != nil { render.Render(w, r, ErrRender(err)) return } render.Render(w, r, NewMessageResponse(&msg)) } type messageKey struct{} type Message struct { ID string `json:"id"` UserID string `json:"user_id"` Body string `json:"body"` Timestamp time.Time `json:"timestamp"` Edited time.Time `json:"edited"` } 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 (m MessageResponse) MarshalJSON() ([]byte, error) { type OrderedMessageResponse struct { ID string `json:"id"` UserID string `json:"user_id"` Body string `json:"body"` Timestamp string `json:"timestamp"` Edited *string `json:"edited,omitempty"` // Use a pointer to allow null values User *UserPayload `json:"user,omitempty"` Elapsed int64 `json:"elapsed"` } var edited *string if !m.Message.Edited.IsZero() { // Check if Edited is not the zero value editedStr := m.Message.Edited.Format(time.RFC3339) edited = &editedStr } ordered := OrderedMessageResponse{ ID: m.Message.ID, UserID: m.Message.UserID, Body: m.Message.Body, Timestamp: m.Message.Timestamp.Format(time.RFC3339), Edited: edited, // Null if Edited is zero User: m.User, Elapsed: m.Elapsed, } return json.Marshal(ordered) }