github.com/dhax/go-base@v0.0.0-20231004214136-8be7e5c1972b/api/app/profile.go (about) 1 package app 2 3 import ( 4 "context" 5 "errors" 6 "net/http" 7 8 "github.com/dhax/go-base/auth/jwt" 9 "github.com/dhax/go-base/models" 10 "github.com/go-chi/chi/v5" 11 "github.com/go-chi/render" 12 validation "github.com/go-ozzo/ozzo-validation" 13 ) 14 15 // The list of error types returned from account resource. 16 var ( 17 ErrProfileValidation = errors.New("profile validation error") 18 ) 19 20 // ProfileStore defines database operations for a profile. 21 type ProfileStore interface { 22 Get(accountID int) (*models.Profile, error) 23 Update(p *models.Profile) error 24 } 25 26 // ProfileResource implements profile management handler. 27 type ProfileResource struct { 28 Store ProfileStore 29 } 30 31 // NewProfileResource creates and returns a profile resource. 32 func NewProfileResource(store ProfileStore) *ProfileResource { 33 return &ProfileResource{ 34 Store: store, 35 } 36 } 37 38 func (rs *ProfileResource) router() *chi.Mux { 39 r := chi.NewRouter() 40 r.Use(rs.profileCtx) 41 r.Get("/", rs.get) 42 r.Put("/", rs.update) 43 return r 44 } 45 46 func (rs *ProfileResource) profileCtx(next http.Handler) http.Handler { 47 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 48 claims := jwt.ClaimsFromCtx(r.Context()) 49 p, err := rs.Store.Get(claims.ID) 50 if err != nil { 51 log(r).WithField("profileCtx", claims.Sub).Error(err) 52 render.Render(w, r, ErrInternalServerError) 53 return 54 } 55 ctx := context.WithValue(r.Context(), ctxProfile, p) 56 next.ServeHTTP(w, r.WithContext(ctx)) 57 }) 58 } 59 60 type profileRequest struct { 61 *models.Profile 62 ProtectedID int `json:"id"` 63 } 64 65 func (d *profileRequest) Bind(r *http.Request) error { 66 return nil 67 } 68 69 type profileResponse struct { 70 *models.Profile 71 } 72 73 func newProfileResponse(p *models.Profile) *profileResponse { 74 return &profileResponse{ 75 Profile: p, 76 } 77 } 78 79 func (rs *ProfileResource) get(w http.ResponseWriter, r *http.Request) { 80 p := r.Context().Value(ctxProfile).(*models.Profile) 81 render.Respond(w, r, newProfileResponse(p)) 82 } 83 84 func (rs *ProfileResource) update(w http.ResponseWriter, r *http.Request) { 85 p := r.Context().Value(ctxProfile).(*models.Profile) 86 data := &profileRequest{Profile: p} 87 if err := render.Bind(r, data); err != nil { 88 render.Render(w, r, ErrInvalidRequest(err)) 89 } 90 91 if err := rs.Store.Update(p); err != nil { 92 switch err.(type) { 93 case validation.Errors: 94 render.Render(w, r, ErrValidation(ErrProfileValidation, err.(validation.Errors))) 95 return 96 } 97 render.Render(w, r, ErrRender(err)) 98 return 99 } 100 render.Respond(w, r, newProfileResponse(p)) 101 }