github.com/dhax/go-base@v0.0.0-20231004214136-8be7e5c1972b/api/admin/accounts.go (about) 1 package admin 2 3 import ( 4 "context" 5 "errors" 6 "net/http" 7 "strconv" 8 9 "github.com/dhax/go-base/auth/pwdless" 10 "github.com/dhax/go-base/database" 11 validation "github.com/go-ozzo/ozzo-validation" 12 13 "github.com/go-chi/chi/v5" 14 "github.com/go-chi/render" 15 ) 16 17 // The list of error types returned from account resource. 18 var ( 19 ErrAccountValidation = errors.New("account validation error") 20 ) 21 22 // AccountStore defines database operations for account management. 23 type AccountStore interface { 24 List(*database.AccountFilter) ([]pwdless.Account, int, error) 25 Create(*pwdless.Account) error 26 Get(id int) (*pwdless.Account, error) 27 Update(*pwdless.Account) error 28 Delete(*pwdless.Account) error 29 } 30 31 // AccountResource implements account management handler. 32 type AccountResource struct { 33 Store AccountStore 34 } 35 36 // NewAccountResource creates and returns an account resource. 37 func NewAccountResource(store AccountStore) *AccountResource { 38 return &AccountResource{ 39 Store: store, 40 } 41 } 42 43 func (rs *AccountResource) router() *chi.Mux { 44 r := chi.NewRouter() 45 r.Get("/", rs.list) 46 r.Post("/", rs.create) 47 r.Route("/{accountID}", func(r chi.Router) { 48 r.Use(rs.accountCtx) 49 r.Get("/", rs.get) 50 r.Put("/", rs.update) 51 r.Delete("/", rs.delete) 52 }) 53 return r 54 } 55 56 func (rs *AccountResource) accountCtx(next http.Handler) http.Handler { 57 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 58 id, err := strconv.Atoi(chi.URLParam(r, "accountID")) 59 if err != nil { 60 render.Render(w, r, ErrBadRequest) 61 return 62 } 63 account, err := rs.Store.Get(id) 64 if err != nil { 65 render.Render(w, r, ErrNotFound) 66 return 67 } 68 ctx := context.WithValue(r.Context(), ctxAccount, account) 69 next.ServeHTTP(w, r.WithContext(ctx)) 70 }) 71 } 72 73 type accountRequest struct { 74 *pwdless.Account 75 } 76 77 func (d *accountRequest) Bind(r *http.Request) error { 78 return nil 79 } 80 81 type accountResponse struct { 82 *pwdless.Account 83 } 84 85 func newAccountResponse(a *pwdless.Account) *accountResponse { 86 resp := &accountResponse{Account: a} 87 return resp 88 } 89 90 type accountListResponse struct { 91 Accounts *[]pwdless.Account `json:"accounts"` 92 Count int `json:"count"` 93 } 94 95 func newAccountListResponse(a *[]pwdless.Account, count int) *accountListResponse { 96 resp := &accountListResponse{ 97 Accounts: a, 98 Count: count, 99 } 100 return resp 101 } 102 103 func (rs *AccountResource) list(w http.ResponseWriter, r *http.Request) { 104 f, err := database.NewAccountFilter(r.URL.Query()) 105 if err != nil { 106 render.Render(w, r, ErrRender(err)) 107 return 108 } 109 al, count, err := rs.Store.List(f) 110 if err != nil { 111 render.Render(w, r, ErrRender(err)) 112 return 113 } 114 render.Respond(w, r, newAccountListResponse(&al, count)) 115 } 116 117 func (rs *AccountResource) create(w http.ResponseWriter, r *http.Request) { 118 data := &accountRequest{} 119 if err := render.Bind(r, data); err != nil { 120 render.Render(w, r, ErrInvalidRequest(err)) 121 return 122 } 123 124 if err := rs.Store.Create(data.Account); err != nil { 125 switch err.(type) { 126 case validation.Errors: 127 render.Render(w, r, ErrValidation(ErrAccountValidation, err.(validation.Errors))) 128 return 129 } 130 render.Render(w, r, ErrInvalidRequest(err)) 131 return 132 } 133 render.Respond(w, r, newAccountResponse(data.Account)) 134 } 135 136 func (rs *AccountResource) get(w http.ResponseWriter, r *http.Request) { 137 acc := r.Context().Value(ctxAccount).(*pwdless.Account) 138 render.Respond(w, r, newAccountResponse(acc)) 139 } 140 141 func (rs *AccountResource) update(w http.ResponseWriter, r *http.Request) { 142 acc := r.Context().Value(ctxAccount).(*pwdless.Account) 143 data := &accountRequest{Account: acc} 144 if err := render.Bind(r, data); err != nil { 145 render.Render(w, r, ErrInvalidRequest(err)) 146 return 147 } 148 149 if err := rs.Store.Update(acc); err != nil { 150 switch err.(type) { 151 case validation.Errors: 152 render.Render(w, r, ErrValidation(ErrAccountValidation, err.(validation.Errors))) 153 return 154 } 155 render.Render(w, r, ErrInvalidRequest(err)) 156 return 157 } 158 159 render.Respond(w, r, newAccountResponse(acc)) 160 } 161 162 func (rs *AccountResource) delete(w http.ResponseWriter, r *http.Request) { 163 acc := r.Context().Value(ctxAccount).(*pwdless.Account) 164 if err := rs.Store.Delete(acc); err != nil { 165 render.Render(w, r, ErrInvalidRequest(err)) 166 return 167 } 168 render.Respond(w, r, http.NoBody) 169 }