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  }