diff --git a/app/contexts.go b/app/contexts.go index c9bd220..3be3915 100644 --- a/app/contexts.go +++ b/app/contexts.go @@ -157,6 +157,50 @@ func (ctx *FindByEmailUserContext) InternalServerError(r error) error { return ctx.ResponseData.Service.Send(ctx.Context, 500, r) } +// FindUsersUserContext provides the user findUsers action context. +type FindUsersUserContext struct { + context.Context + *goa.ResponseData + *goa.RequestData + Payload *FilterPayload +} + +// NewFindUsersUserContext parses the incoming request URL and body, performs validations and creates the +// context used by the user controller findUsers action. +func NewFindUsersUserContext(ctx context.Context, r *http.Request, service *goa.Service) (*FindUsersUserContext, error) { + var err error + resp := goa.ContextResponse(ctx) + resp.Service = service + req := goa.ContextRequest(ctx) + req.Request = r + rctx := FindUsersUserContext{Context: ctx, ResponseData: resp, RequestData: req} + return &rctx, err +} + +// OK sends a HTTP response with status code 200. +func (ctx *FindUsersUserContext) OK(r *UsersPage) error { + if ctx.ResponseData.Header().Get("Content-Type") == "" { + ctx.ResponseData.Header().Set("Content-Type", "application/mt.ckan.users-page+json") + } + return ctx.ResponseData.Service.Send(ctx.Context, 200, r) +} + +// BadRequest sends a HTTP response with status code 400. +func (ctx *FindUsersUserContext) BadRequest(r error) error { + if ctx.ResponseData.Header().Get("Content-Type") == "" { + ctx.ResponseData.Header().Set("Content-Type", "application/vnd.goa.error") + } + return ctx.ResponseData.Service.Send(ctx.Context, 400, r) +} + +// InternalServerError sends a HTTP response with status code 500. +func (ctx *FindUsersUserContext) InternalServerError(r error) error { + if ctx.ResponseData.Header().Get("Content-Type") == "" { + ctx.ResponseData.Header().Set("Content-Type", "application/vnd.goa.error") + } + return ctx.ResponseData.Service.Send(ctx.Context, 500, r) +} + // ForgotPasswordUserContext provides the user forgotPassword action context. type ForgotPasswordUserContext struct { context.Context diff --git a/app/controllers.go b/app/controllers.go index 6018c78..0df3ed7 100644 --- a/app/controllers.go +++ b/app/controllers.go @@ -62,6 +62,7 @@ type UserController interface { Create(*CreateUserContext) error Find(*FindUserContext) error FindByEmail(*FindByEmailUserContext) error + FindUsers(*FindUsersUserContext) error ForgotPassword(*ForgotPasswordUserContext) error ForgotPasswordUpdate(*ForgotPasswordUpdateUserContext) error Get(*GetUserContext) error @@ -79,6 +80,7 @@ func MountUserController(service *goa.Service, ctrl UserController) { service.Mux.Handle("OPTIONS", "/users", ctrl.MuxHandler("preflight", handleUserOrigin(cors.HandlePreflight()), nil)) service.Mux.Handle("OPTIONS", "/users/find", ctrl.MuxHandler("preflight", handleUserOrigin(cors.HandlePreflight()), nil)) service.Mux.Handle("OPTIONS", "/users/find/email", ctrl.MuxHandler("preflight", handleUserOrigin(cors.HandlePreflight()), nil)) + service.Mux.Handle("OPTIONS", "/users/list", ctrl.MuxHandler("preflight", handleUserOrigin(cors.HandlePreflight()), nil)) service.Mux.Handle("OPTIONS", "/users/password/forgot", ctrl.MuxHandler("preflight", handleUserOrigin(cors.HandlePreflight()), nil)) service.Mux.Handle("OPTIONS", "/users/:userId", ctrl.MuxHandler("preflight", handleUserOrigin(cors.HandlePreflight()), nil)) service.Mux.Handle("OPTIONS", "/users/me", ctrl.MuxHandler("preflight", handleUserOrigin(cors.HandlePreflight()), nil)) @@ -151,6 +153,28 @@ func MountUserController(service *goa.Service, ctrl UserController) { service.Mux.Handle("POST", "/users/find/email", ctrl.MuxHandler("findByEmail", h, unmarshalFindByEmailUserPayload)) service.LogInfo("mount", "ctrl", "User", "action", "FindByEmail", "route", "POST /users/find/email") + h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { + // Check if there was an error loading the request + if err := goa.ContextError(ctx); err != nil { + return err + } + // Build the context + rctx, err := NewFindUsersUserContext(ctx, req, service) + if err != nil { + return err + } + // Build the payload + if rawPayload := goa.ContextRequest(ctx).Payload; rawPayload != nil { + rctx.Payload = rawPayload.(*FilterPayload) + } else { + return goa.MissingPayloadError() + } + return ctrl.FindUsers(rctx) + } + h = handleUserOrigin(h) + service.Mux.Handle("POST", "/users/list", ctrl.MuxHandler("findUsers", h, unmarshalFindUsersUserPayload)) + service.LogInfo("mount", "ctrl", "User", "action", "FindUsers", "route", "POST /users/list") + h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { // Check if there was an error loading the request if err := goa.ContextError(ctx); err != nil { @@ -374,6 +398,21 @@ func unmarshalFindByEmailUserPayload(ctx context.Context, service *goa.Service, return nil } +// unmarshalFindUsersUserPayload unmarshals the request body into the context request data Payload field. +func unmarshalFindUsersUserPayload(ctx context.Context, service *goa.Service, req *http.Request) error { + payload := &filterPayload{} + if err := service.DecodeRequest(req, payload); err != nil { + return err + } + if err := payload.Validate(); err != nil { + // Initialize payload with private data structure so it can be logged + goa.ContextRequest(ctx).Payload = payload + return err + } + goa.ContextRequest(ctx).Payload = payload.Publicize() + return nil +} + // unmarshalForgotPasswordUserPayload unmarshals the request body into the context request data Payload field. func unmarshalForgotPasswordUserPayload(ctx context.Context, service *goa.Service, req *http.Request) error { payload := &emailPayload{} diff --git a/app/media_types.go b/app/media_types.go index bd8972b..eb6b357 100644 --- a/app/media_types.go +++ b/app/media_types.go @@ -14,6 +14,30 @@ import ( "github.com/keitaroinc/goa" ) +// UsersPage media type (default view) +// +// Identifier: application/mt.ckan.users-page+json; view=default +type UsersPage struct { + // Users list + Items []*Users `form:"items,omitempty" json:"items,omitempty" yaml:"items,omitempty" xml:"items,omitempty"` + // Page number (1-based). + Page *int `form:"page,omitempty" json:"page,omitempty" yaml:"page,omitempty" xml:"page,omitempty"` + // Items per page. + PageSize *int `form:"pageSize,omitempty" json:"pageSize,omitempty" yaml:"pageSize,omitempty" xml:"pageSize,omitempty"` +} + +// Validate validates the UsersPage media type instance. +func (mt *UsersPage) Validate() (err error) { + for _, e := range mt.Items { + if e != nil { + if err2 := e.Validate(); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + } + return +} + // users media type (default view) // // Identifier: application/vnd.goa.user+json; view=default diff --git a/app/test/user_testing.go b/app/test/user_testing.go index c2cb378..96dbff2 100644 --- a/app/test/user_testing.go +++ b/app/test/user_testing.go @@ -833,6 +833,249 @@ func FindByEmailUserOK(t goatest.TInterface, ctx context.Context, service *goa.S return rw, mt } +// FindUsersUserBadRequest runs the method FindUsers of the given controller with the given parameters and payload. +// It returns the response writer so it's possible to inspect the response headers and the media type struct written to the response. +// If ctx is nil then context.Background() is used. +// If service is nil then a default service is created. +func FindUsersUserBadRequest(t goatest.TInterface, ctx context.Context, service *goa.Service, ctrl app.UserController, payload *app.FilterPayload) (http.ResponseWriter, error) { + // Setup service + var ( + logBuf bytes.Buffer + resp interface{} + + respSetter goatest.ResponseSetterFunc = func(r interface{}) { resp = r } + ) + if service == nil { + service = goatest.Service(&logBuf, respSetter) + } else { + logger := log.New(&logBuf, "", log.Ltime) + service.WithLogger(goa.NewLogger(logger)) + newEncoder := func(io.Writer) goa.Encoder { return respSetter } + service.Encoder = goa.NewHTTPEncoder() // Make sure the code ends up using this decoder + service.Encoder.Register(newEncoder, "*/*") + } + + // Validate payload + err := payload.Validate() + if err != nil { + e, ok := err.(goa.ServiceError) + if !ok { + panic(err) // bug + } + return nil, e + } + + // Setup request context + rw := httptest.NewRecorder() + u := &url.URL{ + Path: fmt.Sprintf("/users/list"), + } + req, _err := http.NewRequest("POST", u.String(), nil) + if _err != nil { + panic("invalid test " + _err.Error()) // bug + } + prms := url.Values{} + if ctx == nil { + ctx = context.Background() + } + goaCtx := goa.NewContext(goa.WithAction(ctx, "UserTest"), rw, req, prms) + findUsersCtx, __err := app.NewFindUsersUserContext(goaCtx, req, service) + if __err != nil { + _e, _ok := __err.(goa.ServiceError) + if !_ok { + panic("invalid test data " + __err.Error()) // bug + } + return nil, _e + } + findUsersCtx.Payload = payload + + // Perform action + __err = ctrl.FindUsers(findUsersCtx) + + // Validate response + if __err != nil { + t.Fatalf("controller returned %+v, logs:\n%s", __err, logBuf.String()) + } + if rw.Code != 400 { + t.Errorf("invalid response status code: got %+v, expected 400", rw.Code) + } + var mt error + if resp != nil { + var __ok bool + mt, __ok = resp.(error) + if !__ok { + t.Fatalf("invalid response media: got variable of type %T, value %+v, expected instance of error", resp, resp) + } + } + + // Return results + return rw, mt +} + +// FindUsersUserInternalServerError runs the method FindUsers of the given controller with the given parameters and payload. +// It returns the response writer so it's possible to inspect the response headers and the media type struct written to the response. +// If ctx is nil then context.Background() is used. +// If service is nil then a default service is created. +func FindUsersUserInternalServerError(t goatest.TInterface, ctx context.Context, service *goa.Service, ctrl app.UserController, payload *app.FilterPayload) (http.ResponseWriter, error) { + // Setup service + var ( + logBuf bytes.Buffer + resp interface{} + + respSetter goatest.ResponseSetterFunc = func(r interface{}) { resp = r } + ) + if service == nil { + service = goatest.Service(&logBuf, respSetter) + } else { + logger := log.New(&logBuf, "", log.Ltime) + service.WithLogger(goa.NewLogger(logger)) + newEncoder := func(io.Writer) goa.Encoder { return respSetter } + service.Encoder = goa.NewHTTPEncoder() // Make sure the code ends up using this decoder + service.Encoder.Register(newEncoder, "*/*") + } + + // Validate payload + err := payload.Validate() + if err != nil { + e, ok := err.(goa.ServiceError) + if !ok { + panic(err) // bug + } + return nil, e + } + + // Setup request context + rw := httptest.NewRecorder() + u := &url.URL{ + Path: fmt.Sprintf("/users/list"), + } + req, _err := http.NewRequest("POST", u.String(), nil) + if _err != nil { + panic("invalid test " + _err.Error()) // bug + } + prms := url.Values{} + if ctx == nil { + ctx = context.Background() + } + goaCtx := goa.NewContext(goa.WithAction(ctx, "UserTest"), rw, req, prms) + findUsersCtx, __err := app.NewFindUsersUserContext(goaCtx, req, service) + if __err != nil { + _e, _ok := __err.(goa.ServiceError) + if !_ok { + panic("invalid test data " + __err.Error()) // bug + } + return nil, _e + } + findUsersCtx.Payload = payload + + // Perform action + __err = ctrl.FindUsers(findUsersCtx) + + // Validate response + if __err != nil { + t.Fatalf("controller returned %+v, logs:\n%s", __err, logBuf.String()) + } + if rw.Code != 500 { + t.Errorf("invalid response status code: got %+v, expected 500", rw.Code) + } + var mt error + if resp != nil { + var __ok bool + mt, __ok = resp.(error) + if !__ok { + t.Fatalf("invalid response media: got variable of type %T, value %+v, expected instance of error", resp, resp) + } + } + + // Return results + return rw, mt +} + +// FindUsersUserOK runs the method FindUsers of the given controller with the given parameters and payload. +// It returns the response writer so it's possible to inspect the response headers and the media type struct written to the response. +// If ctx is nil then context.Background() is used. +// If service is nil then a default service is created. +func FindUsersUserOK(t goatest.TInterface, ctx context.Context, service *goa.Service, ctrl app.UserController, payload *app.FilterPayload) (http.ResponseWriter, *app.UsersPage) { + // Setup service + var ( + logBuf bytes.Buffer + resp interface{} + + respSetter goatest.ResponseSetterFunc = func(r interface{}) { resp = r } + ) + if service == nil { + service = goatest.Service(&logBuf, respSetter) + } else { + logger := log.New(&logBuf, "", log.Ltime) + service.WithLogger(goa.NewLogger(logger)) + newEncoder := func(io.Writer) goa.Encoder { return respSetter } + service.Encoder = goa.NewHTTPEncoder() // Make sure the code ends up using this decoder + service.Encoder.Register(newEncoder, "*/*") + } + + // Validate payload + err := payload.Validate() + if err != nil { + e, ok := err.(goa.ServiceError) + if !ok { + panic(err) // bug + } + t.Errorf("unexpected payload validation error: %+v", e) + return nil, nil + } + + // Setup request context + rw := httptest.NewRecorder() + u := &url.URL{ + Path: fmt.Sprintf("/users/list"), + } + req, _err := http.NewRequest("POST", u.String(), nil) + if _err != nil { + panic("invalid test " + _err.Error()) // bug + } + prms := url.Values{} + if ctx == nil { + ctx = context.Background() + } + goaCtx := goa.NewContext(goa.WithAction(ctx, "UserTest"), rw, req, prms) + findUsersCtx, __err := app.NewFindUsersUserContext(goaCtx, req, service) + if __err != nil { + _e, _ok := __err.(goa.ServiceError) + if !_ok { + panic("invalid test data " + __err.Error()) // bug + } + t.Errorf("unexpected parameter validation error: %+v", _e) + return nil, nil + } + findUsersCtx.Payload = payload + + // Perform action + __err = ctrl.FindUsers(findUsersCtx) + + // Validate response + if __err != nil { + t.Fatalf("controller returned %+v, logs:\n%s", __err, logBuf.String()) + } + if rw.Code != 200 { + t.Errorf("invalid response status code: got %+v, expected 200", rw.Code) + } + var mt *app.UsersPage + if resp != nil { + var __ok bool + mt, __ok = resp.(*app.UsersPage) + if !__ok { + t.Fatalf("invalid response media: got variable of type %T, value %+v, expected instance of app.UsersPage", resp, resp) + } + __err = mt.Validate() + if __err != nil { + t.Errorf("invalid response media type: %s", __err) + } + } + + // Return results + return rw, mt +} + // ForgotPasswordUserBadRequest runs the method ForgotPassword of the given controller with the given parameters and payload. // It returns the response writer so it's possible to inspect the response headers and the media type struct written to the response. // If ctx is nil then context.Background() is used. diff --git a/app/user_types.go b/app/user_types.go index b72cd5b..a3623c9 100644 --- a/app/user_types.go +++ b/app/user_types.go @@ -256,6 +256,142 @@ func (ut *EmailPayload) Validate() (err error) { return } +// filterPayload user type. +type filterPayload struct { + // Users filter. + Filter []*filterProperty `form:"filter,omitempty" json:"filter,omitempty" yaml:"filter,omitempty" xml:"filter,omitempty"` + // Page number (1-based). + Page *int `form:"page,omitempty" json:"page,omitempty" yaml:"page,omitempty" xml:"page,omitempty"` + // Items per page. + PageSize *int `form:"pageSize,omitempty" json:"pageSize,omitempty" yaml:"pageSize,omitempty" xml:"pageSize,omitempty"` + // Sort specification. + Sort *orderSpec `form:"sort,omitempty" json:"sort,omitempty" yaml:"sort,omitempty" xml:"sort,omitempty"` +} + +// Validate validates the filterPayload type instance. +func (ut *filterPayload) Validate() (err error) { + if ut.Page == nil { + err = goa.MergeErrors(err, goa.MissingAttributeError(`request`, "page")) + } + if ut.PageSize == nil { + err = goa.MergeErrors(err, goa.MissingAttributeError(`request`, "pageSize")) + } + for _, e := range ut.Filter { + if e != nil { + if err2 := e.Validate(); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + } + if ut.Sort != nil { + if err2 := ut.Sort.Validate(); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// Publicize creates FilterPayload from filterPayload +func (ut *filterPayload) Publicize() *FilterPayload { + var pub FilterPayload + if ut.Filter != nil { + pub.Filter = make([]*FilterProperty, len(ut.Filter)) + for i2, elem2 := range ut.Filter { + pub.Filter[i2] = elem2.Publicize() + } + } + if ut.Page != nil { + pub.Page = *ut.Page + } + if ut.PageSize != nil { + pub.PageSize = *ut.PageSize + } + if ut.Sort != nil { + pub.Sort = ut.Sort.Publicize() + } + return &pub +} + +// FilterPayload user type. +type FilterPayload struct { + // Users filter. + Filter []*FilterProperty `form:"filter,omitempty" json:"filter,omitempty" yaml:"filter,omitempty" xml:"filter,omitempty"` + // Page number (1-based). + Page int `form:"page" json:"page" yaml:"page" xml:"page"` + // Items per page. + PageSize int `form:"pageSize" json:"pageSize" yaml:"pageSize" xml:"pageSize"` + // Sort specification. + Sort *OrderSpec `form:"sort,omitempty" json:"sort,omitempty" yaml:"sort,omitempty" xml:"sort,omitempty"` +} + +// Validate validates the FilterPayload type instance. +func (ut *FilterPayload) Validate() (err error) { + + for _, e := range ut.Filter { + if e != nil { + if err2 := e.Validate(); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + } + if ut.Sort != nil { + if err2 := ut.Sort.Validate(); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// filterProperty user type. +type filterProperty struct { + // Property name + Property *string `form:"property,omitempty" json:"property,omitempty" yaml:"property,omitempty" xml:"property,omitempty"` + // Property value to match + Value *string `form:"value,omitempty" json:"value,omitempty" yaml:"value,omitempty" xml:"value,omitempty"` +} + +// Validate validates the filterProperty type instance. +func (ut *filterProperty) Validate() (err error) { + if ut.Property == nil { + err = goa.MergeErrors(err, goa.MissingAttributeError(`request`, "property")) + } + if ut.Value == nil { + err = goa.MergeErrors(err, goa.MissingAttributeError(`request`, "value")) + } + return +} + +// Publicize creates FilterProperty from filterProperty +func (ut *filterProperty) Publicize() *FilterProperty { + var pub FilterProperty + if ut.Property != nil { + pub.Property = *ut.Property + } + if ut.Value != nil { + pub.Value = *ut.Value + } + return &pub +} + +// FilterProperty user type. +type FilterProperty struct { + // Property name + Property string `form:"property" json:"property" yaml:"property" xml:"property"` + // Property value to match + Value string `form:"value" json:"value" yaml:"value" xml:"value"` +} + +// Validate validates the FilterProperty type instance. +func (ut *FilterProperty) Validate() (err error) { + if ut.Property == "" { + err = goa.MergeErrors(err, goa.MissingAttributeError(`type`, "property")) + } + if ut.Value == "" { + err = goa.MergeErrors(err, goa.MissingAttributeError(`type`, "value")) + } + return +} + // Password Reset payload type forgotPasswordPayload struct { // Email of the user @@ -343,6 +479,56 @@ func (ut *ForgotPasswordPayload) Validate() (err error) { return } +// orderSpec user type. +type orderSpec struct { + // Sort order. Can be 'asc' or 'desc'. + Direction *string `form:"direction,omitempty" json:"direction,omitempty" yaml:"direction,omitempty" xml:"direction,omitempty"` + // Sort by property + Property *string `form:"property,omitempty" json:"property,omitempty" yaml:"property,omitempty" xml:"property,omitempty"` +} + +// Validate validates the orderSpec type instance. +func (ut *orderSpec) Validate() (err error) { + if ut.Property == nil { + err = goa.MergeErrors(err, goa.MissingAttributeError(`request`, "property")) + } + if ut.Direction == nil { + err = goa.MergeErrors(err, goa.MissingAttributeError(`request`, "direction")) + } + return +} + +// Publicize creates OrderSpec from orderSpec +func (ut *orderSpec) Publicize() *OrderSpec { + var pub OrderSpec + if ut.Direction != nil { + pub.Direction = *ut.Direction + } + if ut.Property != nil { + pub.Property = *ut.Property + } + return &pub +} + +// OrderSpec user type. +type OrderSpec struct { + // Sort order. Can be 'asc' or 'desc'. + Direction string `form:"direction" json:"direction" yaml:"direction" xml:"direction"` + // Sort by property + Property string `form:"property" json:"property" yaml:"property" xml:"property"` +} + +// Validate validates the OrderSpec type instance. +func (ut *OrderSpec) Validate() (err error) { + if ut.Property == "" { + err = goa.MergeErrors(err, goa.MissingAttributeError(`type`, "property")) + } + if ut.Direction == "" { + err = goa.MergeErrors(err, goa.MissingAttributeError(`type`, "direction")) + } + return +} + // UpdateUserPayload type updateUserPayload struct { // Status of user account diff --git a/client/media_types.go b/client/media_types.go index 7cf3125..1832cc4 100644 --- a/client/media_types.go +++ b/client/media_types.go @@ -15,6 +15,37 @@ import ( "net/http" ) +// UsersPage media type (default view) +// +// Identifier: application/mt.ckan.users-page+json; view=default +type UsersPage struct { + // Users list + Items []*Users `form:"items,omitempty" json:"items,omitempty" yaml:"items,omitempty" xml:"items,omitempty"` + // Page number (1-based). + Page *int `form:"page,omitempty" json:"page,omitempty" yaml:"page,omitempty" xml:"page,omitempty"` + // Items per page. + PageSize *int `form:"pageSize,omitempty" json:"pageSize,omitempty" yaml:"pageSize,omitempty" xml:"pageSize,omitempty"` +} + +// Validate validates the UsersPage media type instance. +func (mt *UsersPage) Validate() (err error) { + for _, e := range mt.Items { + if e != nil { + if err2 := e.Validate(); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + } + return +} + +// DecodeUsersPage decodes the UsersPage instance encoded in resp body. +func (c *Client) DecodeUsersPage(resp *http.Response) (*UsersPage, error) { + var decoded UsersPage + err := c.Decoder.Decode(&decoded, resp.Body, resp.Header.Get("Content-Type")) + return &decoded, err +} + // DecodeErrorResponse decodes the ErrorResponse instance encoded in resp body. func (c *Client) DecodeErrorResponse(resp *http.Response) (*goa.ErrorResponse, error) { var decoded goa.ErrorResponse diff --git a/client/user.go b/client/user.go index 0b45b9f..7c0216f 100644 --- a/client/user.go +++ b/client/user.go @@ -148,6 +148,49 @@ func (c *Client) NewFindByEmailUserRequest(ctx context.Context, path string, pay return req, nil } +// FindUsersUserPath computes a request path to the findUsers action of user. +func FindUsersUserPath() string { + + return fmt.Sprintf("/users/list") +} + +// Find (filter) users by some filter. +func (c *Client) FindUsersUser(ctx context.Context, path string, payload *FilterPayload, contentType string) (*http.Response, error) { + req, err := c.NewFindUsersUserRequest(ctx, path, payload, contentType) + if err != nil { + return nil, err + } + return c.Client.Do(ctx, req) +} + +// NewFindUsersUserRequest create the request corresponding to the findUsers action endpoint of the user resource. +func (c *Client) NewFindUsersUserRequest(ctx context.Context, path string, payload *FilterPayload, contentType string) (*http.Request, error) { + var body bytes.Buffer + if contentType == "" { + contentType = "*/*" // Use default encoder + } + err := c.Encoder.Encode(payload, &body, contentType) + if err != nil { + return nil, fmt.Errorf("failed to encode body: %s", err) + } + scheme := c.Scheme + if scheme == "" { + scheme = "http" + } + u := url.URL{Host: c.Host, Scheme: scheme, Path: path} + req, err := http.NewRequest("POST", u.String(), &body) + if err != nil { + return nil, err + } + header := req.Header + if contentType == "*/*" { + header.Set("Content-Type", "application/json") + } else { + header.Set("Content-Type", contentType) + } + return req, nil +} + // ForgotPasswordUserPath computes a request path to the forgotPassword action of user. func ForgotPasswordUserPath() string { @@ -288,12 +331,12 @@ func (c *Client) NewGetAllUserRequest(ctx context.Context, path string, limit *i u := url.URL{Host: c.Host, Scheme: scheme, Path: path} values := u.Query() if limit != nil { - tmp12 := strconv.Itoa(*limit) - values.Set("limit", tmp12) + tmp13 := strconv.Itoa(*limit) + values.Set("limit", tmp13) } if offset != nil { - tmp13 := strconv.Itoa(*offset) - values.Set("offset", tmp13) + tmp14 := strconv.Itoa(*offset) + values.Set("offset", tmp14) } if order != nil { values.Set("order", *order) diff --git a/client/user_types.go b/client/user_types.go index 844bdd8..ddc5db4 100644 --- a/client/user_types.go +++ b/client/user_types.go @@ -256,6 +256,142 @@ func (ut *EmailPayload) Validate() (err error) { return } +// filterPayload user type. +type filterPayload struct { + // Users filter. + Filter []*filterProperty `form:"filter,omitempty" json:"filter,omitempty" yaml:"filter,omitempty" xml:"filter,omitempty"` + // Page number (1-based). + Page *int `form:"page,omitempty" json:"page,omitempty" yaml:"page,omitempty" xml:"page,omitempty"` + // Items per page. + PageSize *int `form:"pageSize,omitempty" json:"pageSize,omitempty" yaml:"pageSize,omitempty" xml:"pageSize,omitempty"` + // Sort specification. + Sort *orderSpec `form:"sort,omitempty" json:"sort,omitempty" yaml:"sort,omitempty" xml:"sort,omitempty"` +} + +// Validate validates the filterPayload type instance. +func (ut *filterPayload) Validate() (err error) { + if ut.Page == nil { + err = goa.MergeErrors(err, goa.MissingAttributeError(`request`, "page")) + } + if ut.PageSize == nil { + err = goa.MergeErrors(err, goa.MissingAttributeError(`request`, "pageSize")) + } + for _, e := range ut.Filter { + if e != nil { + if err2 := e.Validate(); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + } + if ut.Sort != nil { + if err2 := ut.Sort.Validate(); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// Publicize creates FilterPayload from filterPayload +func (ut *filterPayload) Publicize() *FilterPayload { + var pub FilterPayload + if ut.Filter != nil { + pub.Filter = make([]*FilterProperty, len(ut.Filter)) + for i2, elem2 := range ut.Filter { + pub.Filter[i2] = elem2.Publicize() + } + } + if ut.Page != nil { + pub.Page = *ut.Page + } + if ut.PageSize != nil { + pub.PageSize = *ut.PageSize + } + if ut.Sort != nil { + pub.Sort = ut.Sort.Publicize() + } + return &pub +} + +// FilterPayload user type. +type FilterPayload struct { + // Users filter. + Filter []*FilterProperty `form:"filter,omitempty" json:"filter,omitempty" yaml:"filter,omitempty" xml:"filter,omitempty"` + // Page number (1-based). + Page int `form:"page" json:"page" yaml:"page" xml:"page"` + // Items per page. + PageSize int `form:"pageSize" json:"pageSize" yaml:"pageSize" xml:"pageSize"` + // Sort specification. + Sort *OrderSpec `form:"sort,omitempty" json:"sort,omitempty" yaml:"sort,omitempty" xml:"sort,omitempty"` +} + +// Validate validates the FilterPayload type instance. +func (ut *FilterPayload) Validate() (err error) { + + for _, e := range ut.Filter { + if e != nil { + if err2 := e.Validate(); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + } + if ut.Sort != nil { + if err2 := ut.Sort.Validate(); err2 != nil { + err = goa.MergeErrors(err, err2) + } + } + return +} + +// filterProperty user type. +type filterProperty struct { + // Property name + Property *string `form:"property,omitempty" json:"property,omitempty" yaml:"property,omitempty" xml:"property,omitempty"` + // Property value to match + Value *string `form:"value,omitempty" json:"value,omitempty" yaml:"value,omitempty" xml:"value,omitempty"` +} + +// Validate validates the filterProperty type instance. +func (ut *filterProperty) Validate() (err error) { + if ut.Property == nil { + err = goa.MergeErrors(err, goa.MissingAttributeError(`request`, "property")) + } + if ut.Value == nil { + err = goa.MergeErrors(err, goa.MissingAttributeError(`request`, "value")) + } + return +} + +// Publicize creates FilterProperty from filterProperty +func (ut *filterProperty) Publicize() *FilterProperty { + var pub FilterProperty + if ut.Property != nil { + pub.Property = *ut.Property + } + if ut.Value != nil { + pub.Value = *ut.Value + } + return &pub +} + +// FilterProperty user type. +type FilterProperty struct { + // Property name + Property string `form:"property" json:"property" yaml:"property" xml:"property"` + // Property value to match + Value string `form:"value" json:"value" yaml:"value" xml:"value"` +} + +// Validate validates the FilterProperty type instance. +func (ut *FilterProperty) Validate() (err error) { + if ut.Property == "" { + err = goa.MergeErrors(err, goa.MissingAttributeError(`type`, "property")) + } + if ut.Value == "" { + err = goa.MergeErrors(err, goa.MissingAttributeError(`type`, "value")) + } + return +} + // Password Reset payload type forgotPasswordPayload struct { // Email of the user @@ -343,6 +479,56 @@ func (ut *ForgotPasswordPayload) Validate() (err error) { return } +// orderSpec user type. +type orderSpec struct { + // Sort order. Can be 'asc' or 'desc'. + Direction *string `form:"direction,omitempty" json:"direction,omitempty" yaml:"direction,omitempty" xml:"direction,omitempty"` + // Sort by property + Property *string `form:"property,omitempty" json:"property,omitempty" yaml:"property,omitempty" xml:"property,omitempty"` +} + +// Validate validates the orderSpec type instance. +func (ut *orderSpec) Validate() (err error) { + if ut.Property == nil { + err = goa.MergeErrors(err, goa.MissingAttributeError(`request`, "property")) + } + if ut.Direction == nil { + err = goa.MergeErrors(err, goa.MissingAttributeError(`request`, "direction")) + } + return +} + +// Publicize creates OrderSpec from orderSpec +func (ut *orderSpec) Publicize() *OrderSpec { + var pub OrderSpec + if ut.Direction != nil { + pub.Direction = *ut.Direction + } + if ut.Property != nil { + pub.Property = *ut.Property + } + return &pub +} + +// OrderSpec user type. +type OrderSpec struct { + // Sort order. Can be 'asc' or 'desc'. + Direction string `form:"direction" json:"direction" yaml:"direction" xml:"direction"` + // Sort by property + Property string `form:"property" json:"property" yaml:"property" xml:"property"` +} + +// Validate validates the OrderSpec type instance. +func (ut *OrderSpec) Validate() (err error) { + if ut.Property == "" { + err = goa.MergeErrors(err, goa.MissingAttributeError(`type`, "property")) + } + if ut.Direction == "" { + err = goa.MergeErrors(err, goa.MissingAttributeError(`type`, "direction")) + } + return +} + // UpdateUserPayload type updateUserPayload struct { // Status of user account diff --git a/design/design.go b/design/design.go index dff8580..9b26a8e 100644 --- a/design/design.go +++ b/design/design.go @@ -85,6 +85,15 @@ var _ = Resource("user", func() { Response(InternalServerError, ErrorMedia) }) + Action("findUsers", func() { + Description("Find (filter) users by some filter.") + Routing(POST("/list")) + Payload(FilterPayload) + Response(OK, UsersPageMedia) + Response(BadRequest, ErrorMedia) + Response(InternalServerError, ErrorMedia) + }) + Action("find", func() { Description("Find a user by email+password") Routing(POST("find")) @@ -281,3 +290,42 @@ var _ = Resource("swagger", func() { Files("swagger.json", "swagger/swagger.json") Files("swagger-ui/*filepath", "swagger-ui/dist") }) + +// FilterPayload Users filter request payload. +var FilterPayload = Type("FilterPayload", func() { + Attribute("page", Integer, "Page number (1-based).") + Attribute("pageSize", Integer, "Items per page.") + Attribute("filter", ArrayOf(FilterProperty), "Users filter.") + Attribute("sort", OrderSpec, "Sort specification.") + Required("page", "pageSize") +}) + +// FilterProperty Single property filter. Holds the property name and the value to be matched for that property. +var FilterProperty = Type("FilterProperty", func() { + Attribute("property", String, "Property name") + Attribute("value", String, "Property value to match") + Required("property", "value") +}) + +// OrderSpec specifies the sorting - by which property and the direction, either 'asc' (ascending) +// or 'desc' (descending). +var OrderSpec = Type("OrderSpec", func() { + Attribute("property", String, "Sort by property") + Attribute("direction", String, "Sort order. Can be 'asc' or 'desc'.") + Required("property", "direction") +}) + +// UsersPageMedia result of filter-by. One result page along with items (array of Users). +var UsersPageMedia = MediaType("application/mt.ckan.users-page+json", func() { + TypeName("UsersPage") + Attributes(func() { + Attribute("page", Integer, "Page number (1-based).") + Attribute("pageSize", Integer, "Items per page.") + Attribute("items", ArrayOf(UserMedia), "Users list") + }) + View("default", func() { + Attribute("page") + Attribute("pageSize") + Attribute("items") + }) +}) diff --git a/helpers/helpers.go b/helpers/helpers.go new file mode 100644 index 0000000..bbfab64 --- /dev/null +++ b/helpers/helpers.go @@ -0,0 +1,7 @@ +package helpers + +import "time" + +func CurrentTimeMilliseconds() int64 { + return time.Now().UnixNano() / 1000000 +} diff --git a/store/mock.go b/store/mock.go index 85c320b..37c0e56 100644 --- a/store/mock.go +++ b/store/mock.go @@ -4,8 +4,7 @@ import ( "sync" "github.com/Microkubes/backends" - "github.com/keitaroinc/goa" - "github.com/satori/go.uuid" + "gopkg.in/mgo.v2/bson" ) type MapStore map[string]interface{} @@ -27,8 +26,8 @@ func NewDB() User { users := &DB{ MapStore: map[string]interface{}{ - "b8cfa84f-bb6c-4c84-b39b-76dd32653921": map[string]interface{}{ - "id": "b8cfa84f-bb6c-4c84-b39b-76dd32653921", + "5df2103b5f1b640001142d3c": map[string]interface{}{ + "id": "5df2103b5f1b640001142d3c", "email": "keitaro-user1@gmail.com", "password": "keitaro", "externalID": "some-id", @@ -172,14 +171,11 @@ func (db *DB) Save(object interface{}, filter backends.Filter) (interface{}, err return nil, backends.ErrBackendError(INTERNAL_ERROR) } - id, err := uuid.NewV4() - if err != nil { - return nil, goa.ErrInternal(err) - } + id := bson.NewObjectId().Hex() - (*payload)["id"] = id.String() + (*payload)["id"] = id - db.MapStore[id.String()] = *payload + db.MapStore[id] = *payload result = &UserRecord{ ExternalID: "", Roles: []string{}, diff --git a/store/store.go b/store/store.go index 6aef32a..cf0c4e3 100644 --- a/store/store.go +++ b/store/store.go @@ -3,6 +3,7 @@ package store import ( "github.com/Microkubes/backends" "github.com/Microkubes/microservice-user/app" + "gopkg.in/mgo.v2/bson" ) type FPToken struct { @@ -11,7 +12,7 @@ type FPToken struct { } type UserRecord struct { - ID string `json:"id" bson:"_id"` + ID bson.ObjectId `json:"id" bson:"_id"` // Status of user account Active bool `form:"active" json:"active" yaml:"active" xml:"active"` // Email of user @@ -30,6 +31,10 @@ type UserRecord struct { Token string `form:"token,omitempty" json:"token,omitempty" yaml:"token,omitempty" xml:"token,omitempty"` // Tokens for forgotten password FPToken FPToken `form:"forgotPasswordTokens" json:"forgotPasswordTokens" yaml:"forgotPasswordTokens" xml:"forgotPasswordTokens"` + // Time of creating + CreatedAt int64 `json:"createdAt,omitempty" bson:"createdAt"` + // Time of modifying + ModifiedAt int64 `json:"modifiedAt,omitempty" bson:"modifiedAt"` } func (u *UserRecord) ToAppUsers() *app.Users { @@ -37,7 +42,7 @@ func (u *UserRecord) ToAppUsers() *app.Users { Active: u.Active, Email: u.Email, ExternalID: u.ExternalID, - ID: u.ID, + ID: u.ID.Hex(), Namespaces: u.Namespaces, Organizations: u.Organizations, Roles: u.Roles, diff --git a/swagger/swagger.json b/swagger/swagger.json index e9cab25..87fb708 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"The user microservice","description":"A service that provides basic access to the user data","version":"1.0"},"host":"localhost:8080","schemes":["http"],"consumes":["application/json","application/xml","application/gob","application/x-gob"],"produces":["application/json","application/xml","application/gob","application/x-gob"],"paths":{"/swagger-ui/{filepath}":{"get":{"summary":"Download swagger-ui/dist","operationId":"swagger#/swagger-ui/*filepath","parameters":[{"name":"filepath","in":"path","description":"Relative file path","required":true,"type":"string"}],"responses":{"200":{"description":"File downloaded","schema":{"type":"file"}},"404":{"description":"File not found","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/swagger.json":{"get":{"summary":"Download swagger/swagger.json","operationId":"swagger#/swagger.json","responses":{"200":{"description":"File downloaded","schema":{"type":"file"}}},"schemes":["http"]}},"/users":{"get":{"tags":["user"],"summary":"getAll user","description":"Retrieves all active users","operationId":"user#getAll","produces":["application/vnd.goa.error","text/plain"],"parameters":[{"name":"limit","in":"query","description":"Limit users per page","required":false,"type":"integer"},{"name":"offset","in":"query","description":"Number of users to skip","required":false,"type":"integer"},{"name":"order","in":"query","description":"Order by","required":false,"type":"string"},{"name":"sorting","in":"query","required":false,"type":"string","enum":["asc","desc"]}],"responses":{"200":{"description":"OK"},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]},"post":{"tags":["user"],"summary":"create user","description":"Creates user","operationId":"user#create","produces":["application/vnd.goa.error","application/vnd.goa.user+json"],"parameters":[{"name":"payload","in":"body","description":"CreateUserPayload","required":true,"schema":{"$ref":"#/definitions/CreateUserPayload"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/users"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/find":{"post":{"tags":["user"],"summary":"find user","description":"Find a user by email+password","operationId":"user#find","produces":["application/vnd.goa.error","application/vnd.goa.user+json"],"parameters":[{"name":"payload","in":"body","description":"Email and password credentials","required":true,"schema":{"$ref":"#/definitions/Credentials"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/users"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/find/email":{"post":{"tags":["user"],"summary":"findByEmail user","description":"Find a user by email","operationId":"user#findByEmail","produces":["application/vnd.goa.error","application/vnd.goa.user+json"],"parameters":[{"name":"payload","in":"body","description":"Email payload","required":true,"schema":{"$ref":"#/definitions/EmailPayload"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/users"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/me":{"get":{"tags":["user"],"summary":"getMe user","description":"Retrieves the user information for the authenticated user","operationId":"user#getMe","produces":["application/vnd.goa.error","application/vnd.goa.user+json"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/users"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/password/forgot":{"put":{"tags":["user"],"summary":"forgotPasswordUpdate user","description":"Password token validation \u0026 password update","operationId":"user#forgotPasswordUpdate","produces":["application/vnd.goa.error","text/plain"],"parameters":[{"name":"payload","in":"body","description":"Password Reset payload","required":true,"schema":{"$ref":"#/definitions/ForgotPasswordPayload"}}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]},"post":{"tags":["user"],"summary":"forgotPassword user","description":"Forgot password action (sending email to user with link for resseting password)","operationId":"user#forgotPassword","produces":["application/vnd.goa.error","text/plain"],"parameters":[{"name":"payload","in":"body","description":"Email payload","required":true,"schema":{"$ref":"#/definitions/EmailPayload"}}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/verification/reset":{"post":{"tags":["user"],"summary":"resetVerificationToken user","description":"Reset verification token","operationId":"user#resetVerificationToken","produces":["application/vnd.goa.error","resettokenmedia"],"parameters":[{"name":"payload","in":"body","description":"Email payload","required":true,"schema":{"$ref":"#/definitions/EmailPayload"}}],"responses":{"200":{"description":"Verification token reset","schema":{"$ref":"#/definitions/ResetToken"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/verify":{"get":{"tags":["user"],"summary":"verify user","description":"Verify a user by token","operationId":"user#verify","produces":["application/vnd.goa.error","text/plain"],"parameters":[{"name":"token","in":"query","description":"Token","required":false,"type":"string"}],"responses":{"200":{"description":"User is verified"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/{userId}":{"get":{"tags":["user"],"summary":"get user","description":"Get user by id","operationId":"user#get","produces":["application/vnd.goa.error","application/vnd.goa.user+json"],"parameters":[{"name":"userId","in":"path","description":"User ID","required":true,"type":"string"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/users"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]},"put":{"tags":["user"],"summary":"update user","description":"Update user","operationId":"user#update","produces":["application/vnd.goa.error","application/vnd.goa.user+json"],"parameters":[{"name":"userId","in":"path","description":"User ID","required":true,"type":"string"},{"name":"payload","in":"body","description":"UpdateUserPayload","required":true,"schema":{"$ref":"#/definitions/UpdateUserPayload"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/users"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}}},"definitions":{"CreateUserPayload":{"title":"CreateUserPayload","type":"object","properties":{"active":{"type":"boolean","description":"Status of user account","default":false,"example":true},"email":{"type":"string","description":"Email of user","example":"vinnie.d'amore@kessler.net","format":"email"},"externalId":{"type":"string","description":"External id of user","example":"Voluptatum debitis iusto et."},"namespaces":{"type":"array","items":{"type":"string","example":"Quo quo amet occaecati ut."},"description":"List of namespaces this user belongs to","example":["Quo quo amet occaecati ut.","Quo quo amet occaecati ut.","Quo quo amet occaecati ut."]},"organizations":{"type":"array","items":{"type":"string","example":"Deleniti quis et."},"description":"List of organizations to which this user belongs to","example":["Deleniti quis et."]},"password":{"type":"string","description":"Password of user","example":"789xuu49q","minLength":6,"maxLength":30},"roles":{"type":"array","items":{"type":"string","example":"Velit quaerat nam velit incidunt sunt."},"description":"Roles of user","example":["Velit quaerat nam velit incidunt sunt."]},"token":{"type":"string","description":"Token for email verification","example":"Reprehenderit quisquam maxime."}},"description":"CreateUserPayload","example":{"active":true,"email":"vinnie.d'amore@kessler.net","externalId":"Voluptatum debitis iusto et.","namespaces":["Quo quo amet occaecati ut.","Quo quo amet occaecati ut.","Quo quo amet occaecati ut."],"organizations":["Deleniti quis et."],"password":"789xuu49q","roles":["Velit quaerat nam velit incidunt sunt."],"token":"Reprehenderit quisquam maxime."},"required":["email"]},"Credentials":{"title":"Credentials","type":"object","properties":{"email":{"type":"string","description":"Email of user","example":"myles@mooreschneider.name","format":"email"},"password":{"type":"string","description":"Password of user","example":"vhhtvx6y8","minLength":6,"maxLength":30}},"description":"Email and password credentials","example":{"email":"myles@mooreschneider.name","password":"vhhtvx6y8"},"required":["email","password"]},"EmailPayload":{"title":"EmailPayload","type":"object","properties":{"email":{"type":"string","description":"Email of user","example":"marianna_hartmann@oreillylebsack.org","format":"email"}},"description":"Email payload","example":{"email":"marianna_hartmann@oreillylebsack.org"},"required":["email"]},"ForgotPasswordPayload":{"title":"ForgotPasswordPayload","type":"object","properties":{"email":{"type":"string","description":"Email of the user","example":"delaney@ziemeruecker.com","format":"email"},"password":{"type":"string","description":"New password","example":"vmrl0fmhxw","minLength":6,"maxLength":30},"token":{"type":"string","description":"Forgot password token","example":"Quidem corrupti reprehenderit sit aut molestiae."}},"description":"Password Reset payload","example":{"email":"delaney@ziemeruecker.com","password":"vmrl0fmhxw","token":"Quidem corrupti reprehenderit sit aut molestiae."},"required":["email","password","token"]},"ResetToken":{"title":"Mediatype identifier: resettokenmedia; view=default","type":"object","properties":{"email":{"type":"string","description":"User email","example":"Provident fugit corrupti dignissimos nisi voluptatum."},"id":{"type":"string","description":"User ID","example":"Mollitia sint."},"token":{"type":"string","description":"New token","example":"Officia ipsum voluptatem."}},"description":"ResetToken media type (default view)","example":{"email":"Provident fugit corrupti dignissimos nisi voluptatum.","id":"Mollitia sint.","token":"Officia ipsum voluptatem."},"required":["id","email","token"]},"UpdateUserPayload":{"title":"UpdateUserPayload","type":"object","properties":{"active":{"type":"boolean","description":"Status of user account","default":false,"example":false},"email":{"type":"string","description":"Email of user","example":"isaac@mclaughlin.net","format":"email"},"externalId":{"type":"string","description":"External id of user","example":"Eveniet doloribus minus laudantium rerum soluta unde."},"namespaces":{"type":"array","items":{"type":"string","example":"Assumenda ut quam in dolorem."},"description":"List of namespaces this user belongs to","example":["Assumenda ut quam in dolorem."]},"organizations":{"type":"array","items":{"type":"string","example":"Voluptatum rem eos voluptatibus."},"description":"List of organizations to which this user belongs to","example":["Voluptatum rem eos voluptatibus.","Voluptatum rem eos voluptatibus."]},"password":{"type":"string","description":"Password of user","example":"2s23h8wwm4","minLength":6,"maxLength":30},"roles":{"type":"array","items":{"type":"string","example":"Autem sit sit inventore neque qui."},"description":"Roles of user","example":["Autem sit sit inventore neque qui.","Autem sit sit inventore neque qui.","Autem sit sit inventore neque qui."]},"token":{"type":"string","description":"Token for email verification","example":"Voluptas cumque."}},"description":"UpdateUserPayload","example":{"active":false,"email":"isaac@mclaughlin.net","externalId":"Eveniet doloribus minus laudantium rerum soluta unde.","namespaces":["Assumenda ut quam in dolorem."],"organizations":["Voluptatum rem eos voluptatibus.","Voluptatum rem eos voluptatibus."],"password":"2s23h8wwm4","roles":["Autem sit sit inventore neque qui.","Autem sit sit inventore neque qui.","Autem sit sit inventore neque qui."],"token":"Voluptas cumque."}},"error":{"title":"Mediatype identifier: application/vnd.goa.error; view=default","type":"object","properties":{"code":{"type":"string","description":"an application-specific error code, expressed as a string value.","example":"invalid_value"},"detail":{"type":"string","description":"a human-readable explanation specific to this occurrence of the problem.","example":"Value of ID must be an integer"},"id":{"type":"string","description":"a unique identifier for this particular occurrence of the problem.","example":"3F1FKVRR"},"meta":{"type":"object","description":"a meta object containing non-standard meta-information about the error.","example":{"timestamp":1458609066},"additionalProperties":true},"status":{"type":"string","description":"the HTTP status code applicable to this problem, expressed as a string value.","example":"400"}},"description":"Error response media type (default view)","example":{"code":"invalid_value","detail":"Value of ID must be an integer","id":"3F1FKVRR","meta":{"timestamp":1458609066},"status":"400"}},"users":{"title":"Mediatype identifier: application/vnd.goa.user+json; view=default","type":"object","properties":{"active":{"type":"boolean","description":"Status of user account","default":false,"example":true},"email":{"type":"string","description":"Email of user","example":"thad@herman.name","format":"email"},"externalId":{"type":"string","description":"External id of user","example":"Ullam occaecati quae odio rerum aliquid in."},"id":{"type":"string","description":"Unique user ID","example":"Reprehenderit ea quam optio placeat."},"namespaces":{"type":"array","items":{"type":"string","example":"Quo quo amet occaecati ut."},"description":"List of namespaces this user belongs to","example":["Quo quo amet occaecati ut.","Quo quo amet occaecati ut.","Quo quo amet occaecati ut."]},"organizations":{"type":"array","items":{"type":"string","example":"Deleniti quis et."},"description":"List of organizations to which this user belongs to","example":["Deleniti quis et.","Deleniti quis et."]},"roles":{"type":"array","items":{"type":"string","example":"Velit quaerat nam velit incidunt sunt."},"description":"Roles of user","example":["Velit quaerat nam velit incidunt sunt.","Velit quaerat nam velit incidunt sunt.","Velit quaerat nam velit incidunt sunt."]}},"description":"users media type (default view)","example":{"active":true,"email":"thad@herman.name","externalId":"Ullam occaecati quae odio rerum aliquid in.","id":"Reprehenderit ea quam optio placeat.","namespaces":["Quo quo amet occaecati ut.","Quo quo amet occaecati ut.","Quo quo amet occaecati ut."],"organizations":["Deleniti quis et.","Deleniti quis et."],"roles":["Velit quaerat nam velit incidunt sunt.","Velit quaerat nam velit incidunt sunt.","Velit quaerat nam velit incidunt sunt."]},"required":["id","email","roles","externalId","active"]}},"responses":{"OK":{"description":"OK"}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"The user microservice","description":"A service that provides basic access to the user data","version":"1.0"},"host":"localhost:8080","schemes":["http"],"consumes":["application/json","application/xml","application/gob","application/x-gob"],"produces":["application/json","application/xml","application/gob","application/x-gob"],"paths":{"/swagger-ui/{filepath}":{"get":{"summary":"Download swagger-ui/dist","operationId":"swagger#/swagger-ui/*filepath","parameters":[{"name":"filepath","in":"path","description":"Relative file path","required":true,"type":"string"}],"responses":{"200":{"description":"File downloaded","schema":{"type":"file"}},"404":{"description":"File not found","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/swagger.json":{"get":{"summary":"Download swagger/swagger.json","operationId":"swagger#/swagger.json","responses":{"200":{"description":"File downloaded","schema":{"type":"file"}}},"schemes":["http"]}},"/users":{"get":{"tags":["user"],"summary":"getAll user","description":"Retrieves all active users","operationId":"user#getAll","produces":["application/vnd.goa.error","text/plain"],"parameters":[{"name":"limit","in":"query","description":"Limit users per page","required":false,"type":"integer"},{"name":"offset","in":"query","description":"Number of users to skip","required":false,"type":"integer"},{"name":"order","in":"query","description":"Order by","required":false,"type":"string"},{"name":"sorting","in":"query","required":false,"type":"string","enum":["asc","desc"]}],"responses":{"200":{"description":"OK"},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]},"post":{"tags":["user"],"summary":"create user","description":"Creates user","operationId":"user#create","produces":["application/vnd.goa.error","application/vnd.goa.user+json"],"parameters":[{"name":"payload","in":"body","description":"CreateUserPayload","required":true,"schema":{"$ref":"#/definitions/CreateUserPayload"}}],"responses":{"201":{"description":"Created","schema":{"$ref":"#/definitions/users"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/find":{"post":{"tags":["user"],"summary":"find user","description":"Find a user by email+password","operationId":"user#find","produces":["application/vnd.goa.error","application/vnd.goa.user+json"],"parameters":[{"name":"payload","in":"body","description":"Email and password credentials","required":true,"schema":{"$ref":"#/definitions/Credentials"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/users"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/find/email":{"post":{"tags":["user"],"summary":"findByEmail user","description":"Find a user by email","operationId":"user#findByEmail","produces":["application/vnd.goa.error","application/vnd.goa.user+json"],"parameters":[{"name":"payload","in":"body","description":"Email payload","required":true,"schema":{"$ref":"#/definitions/EmailPayload"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/users"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/list":{"post":{"tags":["user"],"summary":"findUsers user","description":"Find (filter) users by some filter.","operationId":"user#findUsers","produces":["application/mt.ckan.users-page+json","application/vnd.goa.error"],"parameters":[{"name":"payload","in":"body","required":true,"schema":{"$ref":"#/definitions/FilterPayload"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/UsersPage"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/me":{"get":{"tags":["user"],"summary":"getMe user","description":"Retrieves the user information for the authenticated user","operationId":"user#getMe","produces":["application/vnd.goa.error","application/vnd.goa.user+json"],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/users"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/password/forgot":{"put":{"tags":["user"],"summary":"forgotPasswordUpdate user","description":"Password token validation \u0026 password update","operationId":"user#forgotPasswordUpdate","produces":["application/vnd.goa.error","text/plain"],"parameters":[{"name":"payload","in":"body","description":"Password Reset payload","required":true,"schema":{"$ref":"#/definitions/ForgotPasswordPayload"}}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]},"post":{"tags":["user"],"summary":"forgotPassword user","description":"Forgot password action (sending email to user with link for resseting password)","operationId":"user#forgotPassword","produces":["application/vnd.goa.error","text/plain"],"parameters":[{"name":"payload","in":"body","description":"Email payload","required":true,"schema":{"$ref":"#/definitions/EmailPayload"}}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/verification/reset":{"post":{"tags":["user"],"summary":"resetVerificationToken user","description":"Reset verification token","operationId":"user#resetVerificationToken","produces":["application/vnd.goa.error","resettokenmedia"],"parameters":[{"name":"payload","in":"body","description":"Email payload","required":true,"schema":{"$ref":"#/definitions/EmailPayload"}}],"responses":{"200":{"description":"Verification token reset","schema":{"$ref":"#/definitions/ResetToken"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/verify":{"get":{"tags":["user"],"summary":"verify user","description":"Verify a user by token","operationId":"user#verify","produces":["application/vnd.goa.error","text/plain"],"parameters":[{"name":"token","in":"query","description":"Token","required":false,"type":"string"}],"responses":{"200":{"description":"User is verified"},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}},"/users/{userId}":{"get":{"tags":["user"],"summary":"get user","description":"Get user by id","operationId":"user#get","produces":["application/vnd.goa.error","application/vnd.goa.user+json"],"parameters":[{"name":"userId","in":"path","description":"User ID","required":true,"type":"string"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/users"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]},"put":{"tags":["user"],"summary":"update user","description":"Update user","operationId":"user#update","produces":["application/vnd.goa.error","application/vnd.goa.user+json"],"parameters":[{"name":"userId","in":"path","description":"User ID","required":true,"type":"string"},{"name":"payload","in":"body","description":"UpdateUserPayload","required":true,"schema":{"$ref":"#/definitions/UpdateUserPayload"}}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/users"}},"400":{"description":"Bad Request","schema":{"$ref":"#/definitions/error"}},"404":{"description":"Not Found","schema":{"$ref":"#/definitions/error"}},"500":{"description":"Internal Server Error","schema":{"$ref":"#/definitions/error"}}},"schemes":["http"]}}},"definitions":{"CreateUserPayload":{"title":"CreateUserPayload","type":"object","properties":{"active":{"type":"boolean","description":"Status of user account","default":false,"example":true},"email":{"type":"string","description":"Email of user","example":"myles_lemke@gaylord.name","format":"email"},"externalId":{"type":"string","description":"External id of user","example":"Tenetur animi a sunt deserunt tempora quam."},"namespaces":{"type":"array","items":{"type":"string","example":"Amet occaecati."},"description":"List of namespaces this user belongs to","example":["Amet occaecati.","Amet occaecati."]},"organizations":{"type":"array","items":{"type":"string","example":"Et deleniti quis et consequuntur officiis."},"description":"List of organizations to which this user belongs to","example":["Et deleniti quis et consequuntur officiis.","Et deleniti quis et consequuntur officiis.","Et deleniti quis et consequuntur officiis."]},"password":{"type":"string","description":"Password of user","example":"34i2en7cd7","minLength":6,"maxLength":30},"roles":{"type":"array","items":{"type":"string","example":"Nam velit incidunt sunt sed provident."},"description":"Roles of user","example":["Nam velit incidunt sunt sed provident."]},"token":{"type":"string","description":"Token for email verification","example":"Et omnis neque consequatur."}},"description":"CreateUserPayload","example":{"active":true,"email":"myles_lemke@gaylord.name","externalId":"Tenetur animi a sunt deserunt tempora quam.","namespaces":["Amet occaecati.","Amet occaecati."],"organizations":["Et deleniti quis et consequuntur officiis.","Et deleniti quis et consequuntur officiis.","Et deleniti quis et consequuntur officiis."],"password":"34i2en7cd7","roles":["Nam velit incidunt sunt sed provident."],"token":"Et omnis neque consequatur."},"required":["email"]},"Credentials":{"title":"Credentials","type":"object","properties":{"email":{"type":"string","description":"Email of user","example":"alva@grahamklein.org","format":"email"},"password":{"type":"string","description":"Password of user","example":"kzsr2yeiek","minLength":6,"maxLength":30}},"description":"Email and password credentials","example":{"email":"alva@grahamklein.org","password":"kzsr2yeiek"},"required":["email","password"]},"EmailPayload":{"title":"EmailPayload","type":"object","properties":{"email":{"type":"string","description":"Email of user","example":"janie@gaylord.info","format":"email"}},"description":"Email payload","example":{"email":"janie@gaylord.info"},"required":["email"]},"FilterPayload":{"title":"FilterPayload","type":"object","properties":{"filter":{"type":"array","items":{"$ref":"#/definitions/FilterProperty"},"description":"Users filter.","example":[{"property":"Soluta omnis et pariatur consequatur accusantium occaecati.","value":"Harum ipsam impedit vitae sed."}]},"page":{"type":"integer","description":"Page number (1-based).","example":2650884019839767564,"format":"int64"},"pageSize":{"type":"integer","description":"Items per page.","example":4871160491463818035,"format":"int64"},"sort":{"$ref":"#/definitions/OrderSpec"}},"example":{"filter":[{"property":"Soluta omnis et pariatur consequatur accusantium occaecati.","value":"Harum ipsam impedit vitae sed."}],"page":2650884019839767564,"pageSize":4871160491463818035,"sort":{"direction":"Ut ipsam corrupti suscipit aliquid explicabo.","property":"Et maxime explicabo natus."}},"required":["page","pageSize"]},"FilterProperty":{"title":"FilterProperty","type":"object","properties":{"property":{"type":"string","description":"Property name","example":"Soluta omnis et pariatur consequatur accusantium occaecati."},"value":{"type":"string","description":"Property value to match","example":"Harum ipsam impedit vitae sed."}},"example":{"property":"Soluta omnis et pariatur consequatur accusantium occaecati.","value":"Harum ipsam impedit vitae sed."},"required":["property","value"]},"ForgotPasswordPayload":{"title":"ForgotPasswordPayload","type":"object","properties":{"email":{"type":"string","description":"Email of the user","example":"caleb@pagac.org","format":"email"},"password":{"type":"string","description":"New password","example":"mdm045mvqp","minLength":6,"maxLength":30},"token":{"type":"string","description":"Forgot password token","example":"Aut dignissimos dolorem quibusdam."}},"description":"Password Reset payload","example":{"email":"caleb@pagac.org","password":"mdm045mvqp","token":"Aut dignissimos dolorem quibusdam."},"required":["email","password","token"]},"OrderSpec":{"title":"OrderSpec","type":"object","properties":{"direction":{"type":"string","description":"Sort order. Can be 'asc' or 'desc'.","example":"Ut ipsam corrupti suscipit aliquid explicabo."},"property":{"type":"string","description":"Sort by property","example":"Et maxime explicabo natus."}},"example":{"direction":"Ut ipsam corrupti suscipit aliquid explicabo.","property":"Et maxime explicabo natus."},"required":["property","direction"]},"ResetToken":{"title":"Mediatype identifier: resettokenmedia; view=default","type":"object","properties":{"email":{"type":"string","description":"User email","example":"Molestias maxime rem."},"id":{"type":"string","description":"User ID","example":"Consequatur earum aut."},"token":{"type":"string","description":"New token","example":"Harum impedit enim commodi neque voluptatem reprehenderit."}},"description":"ResetToken media type (default view)","example":{"email":"Molestias maxime rem.","id":"Consequatur earum aut.","token":"Harum impedit enim commodi neque voluptatem reprehenderit."},"required":["id","email","token"]},"UpdateUserPayload":{"title":"UpdateUserPayload","type":"object","properties":{"active":{"type":"boolean","description":"Status of user account","default":false,"example":false},"email":{"type":"string","description":"Email of user","example":"marques@spinka.org","format":"email"},"externalId":{"type":"string","description":"External id of user","example":"Voluptas eveniet sunt nemo qui nam."},"namespaces":{"type":"array","items":{"type":"string","example":"Voluptas sunt voluptatem doloremque id."},"description":"List of namespaces this user belongs to","example":["Voluptas sunt voluptatem doloremque id.","Voluptas sunt voluptatem doloremque id.","Voluptas sunt voluptatem doloremque id."]},"organizations":{"type":"array","items":{"type":"string","example":"Facere vel."},"description":"List of organizations to which this user belongs to","example":["Facere vel.","Facere vel.","Facere vel."]},"password":{"type":"string","description":"Password of user","example":"y9m9wo4go9","minLength":6,"maxLength":30},"roles":{"type":"array","items":{"type":"string","example":"Labore facere quasi et perspiciatis."},"description":"Roles of user","example":["Labore facere quasi et perspiciatis."]},"token":{"type":"string","description":"Token for email verification","example":"Nihil libero."}},"description":"UpdateUserPayload","example":{"active":false,"email":"marques@spinka.org","externalId":"Voluptas eveniet sunt nemo qui nam.","namespaces":["Voluptas sunt voluptatem doloremque id.","Voluptas sunt voluptatem doloremque id.","Voluptas sunt voluptatem doloremque id."],"organizations":["Facere vel.","Facere vel.","Facere vel."],"password":"y9m9wo4go9","roles":["Labore facere quasi et perspiciatis."],"token":"Nihil libero."}},"UsersPage":{"title":"Mediatype identifier: application/mt.ckan.users-page+json; view=default","type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/definitions/users"},"description":"Users list","example":[{"active":false,"email":"maximillia.funk@skiles.name","externalId":"Occaecati quae odio rerum aliquid in sit.","id":"Ea quam optio placeat reprehenderit similique.","namespaces":["Amet occaecati.","Amet occaecati.","Amet occaecati."],"organizations":["Et deleniti quis et consequuntur officiis.","Et deleniti quis et consequuntur officiis."],"roles":["Nam velit incidunt sunt sed provident.","Nam velit incidunt sunt sed provident.","Nam velit incidunt sunt sed provident."]},{"active":false,"email":"maximillia.funk@skiles.name","externalId":"Occaecati quae odio rerum aliquid in sit.","id":"Ea quam optio placeat reprehenderit similique.","namespaces":["Amet occaecati.","Amet occaecati.","Amet occaecati."],"organizations":["Et deleniti quis et consequuntur officiis.","Et deleniti quis et consequuntur officiis."],"roles":["Nam velit incidunt sunt sed provident.","Nam velit incidunt sunt sed provident.","Nam velit incidunt sunt sed provident."]}]},"page":{"type":"integer","description":"Page number (1-based).","example":8637512787445997841,"format":"int64"},"pageSize":{"type":"integer","description":"Items per page.","example":1216021488875908955,"format":"int64"}},"description":"UsersPage media type (default view)","example":{"items":[{"active":false,"email":"maximillia.funk@skiles.name","externalId":"Occaecati quae odio rerum aliquid in sit.","id":"Ea quam optio placeat reprehenderit similique.","namespaces":["Amet occaecati.","Amet occaecati.","Amet occaecati."],"organizations":["Et deleniti quis et consequuntur officiis.","Et deleniti quis et consequuntur officiis."],"roles":["Nam velit incidunt sunt sed provident.","Nam velit incidunt sunt sed provident.","Nam velit incidunt sunt sed provident."]},{"active":false,"email":"maximillia.funk@skiles.name","externalId":"Occaecati quae odio rerum aliquid in sit.","id":"Ea quam optio placeat reprehenderit similique.","namespaces":["Amet occaecati.","Amet occaecati.","Amet occaecati."],"organizations":["Et deleniti quis et consequuntur officiis.","Et deleniti quis et consequuntur officiis."],"roles":["Nam velit incidunt sunt sed provident.","Nam velit incidunt sunt sed provident.","Nam velit incidunt sunt sed provident."]}],"page":8637512787445997841,"pageSize":1216021488875908955}},"error":{"title":"Mediatype identifier: application/vnd.goa.error; view=default","type":"object","properties":{"code":{"type":"string","description":"an application-specific error code, expressed as a string value.","example":"invalid_value"},"detail":{"type":"string","description":"a human-readable explanation specific to this occurrence of the problem.","example":"Value of ID must be an integer"},"id":{"type":"string","description":"a unique identifier for this particular occurrence of the problem.","example":"3F1FKVRR"},"meta":{"type":"object","description":"a meta object containing non-standard meta-information about the error.","example":{"timestamp":1458609066},"additionalProperties":true},"status":{"type":"string","description":"the HTTP status code applicable to this problem, expressed as a string value.","example":"400"}},"description":"Error response media type (default view)","example":{"code":"invalid_value","detail":"Value of ID must be an integer","id":"3F1FKVRR","meta":{"timestamp":1458609066},"status":"400"}},"users":{"title":"Mediatype identifier: application/vnd.goa.user+json; view=default","type":"object","properties":{"active":{"type":"boolean","description":"Status of user account","default":false,"example":false},"email":{"type":"string","description":"Email of user","example":"maximillia.funk@skiles.name","format":"email"},"externalId":{"type":"string","description":"External id of user","example":"Occaecati quae odio rerum aliquid in sit."},"id":{"type":"string","description":"Unique user ID","example":"Ea quam optio placeat reprehenderit similique."},"namespaces":{"type":"array","items":{"type":"string","example":"Amet occaecati."},"description":"List of namespaces this user belongs to","example":["Amet occaecati.","Amet occaecati.","Amet occaecati."]},"organizations":{"type":"array","items":{"type":"string","example":"Et deleniti quis et consequuntur officiis."},"description":"List of organizations to which this user belongs to","example":["Et deleniti quis et consequuntur officiis.","Et deleniti quis et consequuntur officiis."]},"roles":{"type":"array","items":{"type":"string","example":"Nam velit incidunt sunt sed provident."},"description":"Roles of user","example":["Nam velit incidunt sunt sed provident.","Nam velit incidunt sunt sed provident.","Nam velit incidunt sunt sed provident."]}},"description":"users media type (default view)","example":{"active":false,"email":"maximillia.funk@skiles.name","externalId":"Occaecati quae odio rerum aliquid in sit.","id":"Ea quam optio placeat reprehenderit similique.","namespaces":["Amet occaecati.","Amet occaecati.","Amet occaecati."],"organizations":["Et deleniti quis et consequuntur officiis.","Et deleniti quis et consequuntur officiis."],"roles":["Nam velit incidunt sunt sed provident.","Nam velit incidunt sunt sed provident.","Nam velit incidunt sunt sed provident."]},"required":["id","email","roles","externalId","active"]}},"responses":{"OK":{"description":"OK"}}} \ No newline at end of file diff --git a/swagger/swagger.yaml b/swagger/swagger.yaml index 9251339..87dcac7 100644 --- a/swagger/swagger.yaml +++ b/swagger/swagger.yaml @@ -8,18 +8,19 @@ definitions: description: CreateUserPayload example: active: true - email: vinnie.d'amore@kessler.net - externalId: Voluptatum debitis iusto et. + email: myles_lemke@gaylord.name + externalId: Tenetur animi a sunt deserunt tempora quam. namespaces: - - Quo quo amet occaecati ut. - - Quo quo amet occaecati ut. - - Quo quo amet occaecati ut. + - Amet occaecati. + - Amet occaecati. organizations: - - Deleniti quis et. - password: 789xuu49q + - Et deleniti quis et consequuntur officiis. + - Et deleniti quis et consequuntur officiis. + - Et deleniti quis et consequuntur officiis. + password: 34i2en7cd7 roles: - - Velit quaerat nam velit incidunt sunt. - token: Reprehenderit quisquam maxime. + - Nam velit incidunt sunt sed provident. + token: Et omnis neque consequatur. properties: active: default: false @@ -28,48 +29,49 @@ definitions: type: boolean email: description: Email of user - example: vinnie.d'amore@kessler.net + example: myles_lemke@gaylord.name format: email type: string externalId: description: External id of user - example: Voluptatum debitis iusto et. + example: Tenetur animi a sunt deserunt tempora quam. type: string namespaces: description: List of namespaces this user belongs to example: - - Quo quo amet occaecati ut. - - Quo quo amet occaecati ut. - - Quo quo amet occaecati ut. + - Amet occaecati. + - Amet occaecati. items: - example: Quo quo amet occaecati ut. + example: Amet occaecati. type: string type: array organizations: description: List of organizations to which this user belongs to example: - - Deleniti quis et. + - Et deleniti quis et consequuntur officiis. + - Et deleniti quis et consequuntur officiis. + - Et deleniti quis et consequuntur officiis. items: - example: Deleniti quis et. + example: Et deleniti quis et consequuntur officiis. type: string type: array password: description: Password of user - example: 789xuu49q + example: 34i2en7cd7 maxLength: 30 minLength: 6 type: string roles: description: Roles of user example: - - Velit quaerat nam velit incidunt sunt. + - Nam velit incidunt sunt sed provident. items: - example: Velit quaerat nam velit incidunt sunt. + example: Nam velit incidunt sunt sed provident. type: string type: array token: description: Token for email verification - example: Reprehenderit quisquam maxime. + example: Et omnis neque consequatur. type: string required: - email @@ -78,17 +80,17 @@ definitions: Credentials: description: Email and password credentials example: - email: myles@mooreschneider.name - password: vhhtvx6y8 + email: alva@grahamklein.org + password: kzsr2yeiek properties: email: description: Email of user - example: myles@mooreschneider.name + example: alva@grahamklein.org format: email type: string password: description: Password of user - example: vhhtvx6y8 + example: kzsr2yeiek maxLength: 30 minLength: 6 type: string @@ -100,38 +102,92 @@ definitions: EmailPayload: description: Email payload example: - email: marianna_hartmann@oreillylebsack.org + email: janie@gaylord.info properties: email: description: Email of user - example: marianna_hartmann@oreillylebsack.org + example: janie@gaylord.info format: email type: string required: - email title: EmailPayload type: object + FilterPayload: + example: + filter: + - property: Soluta omnis et pariatur consequatur accusantium occaecati. + value: Harum ipsam impedit vitae sed. + page: 2650884019839767564 + pageSize: 4871160491463818035 + sort: + direction: Ut ipsam corrupti suscipit aliquid explicabo. + property: Et maxime explicabo natus. + properties: + filter: + description: Users filter. + example: + - property: Soluta omnis et pariatur consequatur accusantium occaecati. + value: Harum ipsam impedit vitae sed. + items: + $ref: '#/definitions/FilterProperty' + type: array + page: + description: Page number (1-based). + example: 2650884019839767564 + format: int64 + type: integer + pageSize: + description: Items per page. + example: 4871160491463818035 + format: int64 + type: integer + sort: + $ref: '#/definitions/OrderSpec' + required: + - page + - pageSize + title: FilterPayload + type: object + FilterProperty: + example: + property: Soluta omnis et pariatur consequatur accusantium occaecati. + value: Harum ipsam impedit vitae sed. + properties: + property: + description: Property name + example: Soluta omnis et pariatur consequatur accusantium occaecati. + type: string + value: + description: Property value to match + example: Harum ipsam impedit vitae sed. + type: string + required: + - property + - value + title: FilterProperty + type: object ForgotPasswordPayload: description: Password Reset payload example: - email: delaney@ziemeruecker.com - password: vmrl0fmhxw - token: Quidem corrupti reprehenderit sit aut molestiae. + email: caleb@pagac.org + password: mdm045mvqp + token: Aut dignissimos dolorem quibusdam. properties: email: description: Email of the user - example: delaney@ziemeruecker.com + example: caleb@pagac.org format: email type: string password: description: New password - example: vmrl0fmhxw + example: mdm045mvqp maxLength: 30 minLength: 6 type: string token: description: Forgot password token - example: Quidem corrupti reprehenderit sit aut molestiae. + example: Aut dignissimos dolorem quibusdam. type: string required: - email @@ -139,24 +195,42 @@ definitions: - token title: ForgotPasswordPayload type: object + OrderSpec: + example: + direction: Ut ipsam corrupti suscipit aliquid explicabo. + property: Et maxime explicabo natus. + properties: + direction: + description: Sort order. Can be 'asc' or 'desc'. + example: Ut ipsam corrupti suscipit aliquid explicabo. + type: string + property: + description: Sort by property + example: Et maxime explicabo natus. + type: string + required: + - property + - direction + title: OrderSpec + type: object ResetToken: description: ResetToken media type (default view) example: - email: Provident fugit corrupti dignissimos nisi voluptatum. - id: Mollitia sint. - token: Officia ipsum voluptatem. + email: Molestias maxime rem. + id: Consequatur earum aut. + token: Harum impedit enim commodi neque voluptatem reprehenderit. properties: email: description: User email - example: Provident fugit corrupti dignissimos nisi voluptatum. + example: Molestias maxime rem. type: string id: description: User ID - example: Mollitia sint. + example: Consequatur earum aut. type: string token: description: New token - example: Officia ipsum voluptatem. + example: Harum impedit enim commodi neque voluptatem reprehenderit. type: string required: - id @@ -168,19 +242,20 @@ definitions: description: UpdateUserPayload example: active: false - email: isaac@mclaughlin.net - externalId: Eveniet doloribus minus laudantium rerum soluta unde. + email: marques@spinka.org + externalId: Voluptas eveniet sunt nemo qui nam. namespaces: - - Assumenda ut quam in dolorem. + - Voluptas sunt voluptatem doloremque id. + - Voluptas sunt voluptatem doloremque id. + - Voluptas sunt voluptatem doloremque id. organizations: - - Voluptatum rem eos voluptatibus. - - Voluptatum rem eos voluptatibus. - password: 2s23h8wwm4 + - Facere vel. + - Facere vel. + - Facere vel. + password: y9m9wo4go9 roles: - - Autem sit sit inventore neque qui. - - Autem sit sit inventore neque qui. - - Autem sit sit inventore neque qui. - token: Voluptas cumque. + - Labore facere quasi et perspiciatis. + token: Nihil libero. properties: active: default: false @@ -189,52 +264,138 @@ definitions: type: boolean email: description: Email of user - example: isaac@mclaughlin.net + example: marques@spinka.org format: email type: string externalId: description: External id of user - example: Eveniet doloribus minus laudantium rerum soluta unde. + example: Voluptas eveniet sunt nemo qui nam. type: string namespaces: description: List of namespaces this user belongs to example: - - Assumenda ut quam in dolorem. + - Voluptas sunt voluptatem doloremque id. + - Voluptas sunt voluptatem doloremque id. + - Voluptas sunt voluptatem doloremque id. items: - example: Assumenda ut quam in dolorem. + example: Voluptas sunt voluptatem doloremque id. type: string type: array organizations: description: List of organizations to which this user belongs to example: - - Voluptatum rem eos voluptatibus. - - Voluptatum rem eos voluptatibus. + - Facere vel. + - Facere vel. + - Facere vel. items: - example: Voluptatum rem eos voluptatibus. + example: Facere vel. type: string type: array password: description: Password of user - example: 2s23h8wwm4 + example: y9m9wo4go9 maxLength: 30 minLength: 6 type: string roles: description: Roles of user example: - - Autem sit sit inventore neque qui. - - Autem sit sit inventore neque qui. - - Autem sit sit inventore neque qui. + - Labore facere quasi et perspiciatis. items: - example: Autem sit sit inventore neque qui. + example: Labore facere quasi et perspiciatis. type: string type: array token: description: Token for email verification - example: Voluptas cumque. + example: Nihil libero. type: string title: UpdateUserPayload type: object + UsersPage: + description: UsersPage media type (default view) + example: + items: + - active: false + email: maximillia.funk@skiles.name + externalId: Occaecati quae odio rerum aliquid in sit. + id: Ea quam optio placeat reprehenderit similique. + namespaces: + - Amet occaecati. + - Amet occaecati. + - Amet occaecati. + organizations: + - Et deleniti quis et consequuntur officiis. + - Et deleniti quis et consequuntur officiis. + roles: + - Nam velit incidunt sunt sed provident. + - Nam velit incidunt sunt sed provident. + - Nam velit incidunt sunt sed provident. + - active: false + email: maximillia.funk@skiles.name + externalId: Occaecati quae odio rerum aliquid in sit. + id: Ea quam optio placeat reprehenderit similique. + namespaces: + - Amet occaecati. + - Amet occaecati. + - Amet occaecati. + organizations: + - Et deleniti quis et consequuntur officiis. + - Et deleniti quis et consequuntur officiis. + roles: + - Nam velit incidunt sunt sed provident. + - Nam velit incidunt sunt sed provident. + - Nam velit incidunt sunt sed provident. + page: 8637512787445997841 + pageSize: 1216021488875908955 + properties: + items: + description: Users list + example: + - active: false + email: maximillia.funk@skiles.name + externalId: Occaecati quae odio rerum aliquid in sit. + id: Ea quam optio placeat reprehenderit similique. + namespaces: + - Amet occaecati. + - Amet occaecati. + - Amet occaecati. + organizations: + - Et deleniti quis et consequuntur officiis. + - Et deleniti quis et consequuntur officiis. + roles: + - Nam velit incidunt sunt sed provident. + - Nam velit incidunt sunt sed provident. + - Nam velit incidunt sunt sed provident. + - active: false + email: maximillia.funk@skiles.name + externalId: Occaecati quae odio rerum aliquid in sit. + id: Ea quam optio placeat reprehenderit similique. + namespaces: + - Amet occaecati. + - Amet occaecati. + - Amet occaecati. + organizations: + - Et deleniti quis et consequuntur officiis. + - Et deleniti quis et consequuntur officiis. + roles: + - Nam velit incidunt sunt sed provident. + - Nam velit incidunt sunt sed provident. + - Nam velit incidunt sunt sed provident. + items: + $ref: '#/definitions/users' + type: array + page: + description: Page number (1-based). + example: 8637512787445997841 + format: int64 + type: integer + pageSize: + description: Items per page. + example: 1216021488875908955 + format: int64 + type: integer + title: 'Mediatype identifier: application/mt.ckan.users-page+json; view=default' + type: object error: description: Error response media type (default view) example: @@ -275,67 +436,67 @@ definitions: users: description: users media type (default view) example: - active: true - email: thad@herman.name - externalId: Ullam occaecati quae odio rerum aliquid in. - id: Reprehenderit ea quam optio placeat. + active: false + email: maximillia.funk@skiles.name + externalId: Occaecati quae odio rerum aliquid in sit. + id: Ea quam optio placeat reprehenderit similique. namespaces: - - Quo quo amet occaecati ut. - - Quo quo amet occaecati ut. - - Quo quo amet occaecati ut. + - Amet occaecati. + - Amet occaecati. + - Amet occaecati. organizations: - - Deleniti quis et. - - Deleniti quis et. + - Et deleniti quis et consequuntur officiis. + - Et deleniti quis et consequuntur officiis. roles: - - Velit quaerat nam velit incidunt sunt. - - Velit quaerat nam velit incidunt sunt. - - Velit quaerat nam velit incidunt sunt. + - Nam velit incidunt sunt sed provident. + - Nam velit incidunt sunt sed provident. + - Nam velit incidunt sunt sed provident. properties: active: default: false description: Status of user account - example: true + example: false type: boolean email: description: Email of user - example: thad@herman.name + example: maximillia.funk@skiles.name format: email type: string externalId: description: External id of user - example: Ullam occaecati quae odio rerum aliquid in. + example: Occaecati quae odio rerum aliquid in sit. type: string id: description: Unique user ID - example: Reprehenderit ea quam optio placeat. + example: Ea quam optio placeat reprehenderit similique. type: string namespaces: description: List of namespaces this user belongs to example: - - Quo quo amet occaecati ut. - - Quo quo amet occaecati ut. - - Quo quo amet occaecati ut. + - Amet occaecati. + - Amet occaecati. + - Amet occaecati. items: - example: Quo quo amet occaecati ut. + example: Amet occaecati. type: string type: array organizations: description: List of organizations to which this user belongs to example: - - Deleniti quis et. - - Deleniti quis et. + - Et deleniti quis et consequuntur officiis. + - Et deleniti quis et consequuntur officiis. items: - example: Deleniti quis et. + example: Et deleniti quis et consequuntur officiis. type: string type: array roles: description: Roles of user example: - - Velit quaerat nam velit incidunt sunt. - - Velit quaerat nam velit incidunt sunt. - - Velit quaerat nam velit incidunt sunt. + - Nam velit incidunt sunt sed provident. + - Nam velit incidunt sunt sed provident. + - Nam velit incidunt sunt sed provident. items: - example: Velit quaerat nam velit incidunt sunt. + example: Nam velit incidunt sunt sed provident. type: string type: array required: @@ -604,6 +765,37 @@ paths: summary: findByEmail user tags: - user + /users/list: + post: + description: Find (filter) users by some filter. + operationId: user#findUsers + parameters: + - in: body + name: payload + required: true + schema: + $ref: '#/definitions/FilterPayload' + produces: + - application/mt.ckan.users-page+json + - application/vnd.goa.error + responses: + "200": + description: OK + schema: + $ref: '#/definitions/UsersPage' + "400": + description: Bad Request + schema: + $ref: '#/definitions/error' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/error' + schemes: + - http + summary: findUsers user + tags: + - user /users/me: get: description: Retrieves the user information for the authenticated user diff --git a/tool/cli/commands.go b/tool/cli/commands.go index 5c1e1fd..7b72107 100644 --- a/tool/cli/commands.go +++ b/tool/cli/commands.go @@ -50,6 +50,13 @@ type ( PrettyPrint bool } + // FindUsersUserCommand is the command line data structure for the findUsers action of user + FindUsersUserCommand struct { + Payload string + ContentType string + PrettyPrint bool + } + // ForgotPasswordUserCommand is the command line data structure for the forgotPassword action of user ForgotPasswordUserCommand struct { Payload string @@ -135,21 +142,22 @@ Payload example: { "active": true, - "email": "vinnie.d'amore@kessler.net", - "externalId": "Voluptatum debitis iusto et.", + "email": "myles_lemke@gaylord.name", + "externalId": "Tenetur animi a sunt deserunt tempora quam.", "namespaces": [ - "Quo quo amet occaecati ut.", - "Quo quo amet occaecati ut.", - "Quo quo amet occaecati ut." + "Amet occaecati.", + "Amet occaecati." ], "organizations": [ - "Deleniti quis et." + "Et deleniti quis et consequuntur officiis.", + "Et deleniti quis et consequuntur officiis.", + "Et deleniti quis et consequuntur officiis." ], - "password": "789xuu49q", + "password": "34i2en7cd7", "roles": [ - "Velit quaerat nam velit incidunt sunt." + "Nam velit incidunt sunt sed provident." ], - "token": "Reprehenderit quisquam maxime." + "token": "Et omnis neque consequatur." }`, RunE: func(cmd *cobra.Command, args []string) error { return tmp1.Run(c, args) }, } @@ -170,8 +178,8 @@ Payload example: Payload example: { - "email": "myles@mooreschneider.name", - "password": "vhhtvx6y8" + "email": "alva@grahamklein.org", + "password": "kzsr2yeiek" }`, RunE: func(cmd *cobra.Command, args []string) error { return tmp2.Run(c, args) }, } @@ -192,7 +200,7 @@ Payload example: Payload example: { - "email": "marianna_hartmann@oreillylebsack.org" + "email": "janie@gaylord.info" }`, RunE: func(cmd *cobra.Command, args []string) error { return tmp3.Run(c, args) }, } @@ -201,19 +209,30 @@ Payload example: command.AddCommand(sub) app.AddCommand(command) command = &cobra.Command{ - Use: "forgot-password", - Short: `Forgot password action (sending email to user with link for resseting password)`, + Use: "find-users", + Short: `Find (filter) users by some filter.`, } - tmp4 := new(ForgotPasswordUserCommand) + tmp4 := new(FindUsersUserCommand) sub = &cobra.Command{ - Use: `user ["/users/password/forgot"]`, + Use: `user ["/users/list"]`, Short: ``, Long: ` Payload example: { - "email": "marianna_hartmann@oreillylebsack.org" + "filter": [ + { + "property": "Soluta omnis et pariatur consequatur accusantium occaecati.", + "value": "Harum ipsam impedit vitae sed." + } + ], + "page": 2650884019839767564, + "pageSize": 4871160491463818035, + "sort": { + "direction": "Ut ipsam corrupti suscipit aliquid explicabo.", + "property": "Et maxime explicabo natus." + } }`, RunE: func(cmd *cobra.Command, args []string) error { return tmp4.Run(c, args) }, } @@ -222,10 +241,10 @@ Payload example: command.AddCommand(sub) app.AddCommand(command) command = &cobra.Command{ - Use: "forgot-password-update", - Short: `Password token validation & password update`, + Use: "forgot-password", + Short: `Forgot password action (sending email to user with link for resseting password)`, } - tmp5 := new(ForgotPasswordUpdateUserCommand) + tmp5 := new(ForgotPasswordUserCommand) sub = &cobra.Command{ Use: `user ["/users/password/forgot"]`, Short: ``, @@ -234,9 +253,7 @@ Payload example: Payload example: { - "email": "delaney@ziemeruecker.com", - "password": "vmrl0fmhxw", - "token": "Quidem corrupti reprehenderit sit aut molestiae." + "email": "janie@gaylord.info" }`, RunE: func(cmd *cobra.Command, args []string) error { return tmp5.Run(c, args) }, } @@ -244,53 +261,76 @@ Payload example: sub.PersistentFlags().BoolVar(&tmp5.PrettyPrint, "pp", false, "Pretty print response body") command.AddCommand(sub) app.AddCommand(command) + command = &cobra.Command{ + Use: "forgot-password-update", + Short: `Password token validation & password update`, + } + tmp6 := new(ForgotPasswordUpdateUserCommand) + sub = &cobra.Command{ + Use: `user ["/users/password/forgot"]`, + Short: ``, + Long: ` + +Payload example: + +{ + "email": "caleb@pagac.org", + "password": "mdm045mvqp", + "token": "Aut dignissimos dolorem quibusdam." +}`, + RunE: func(cmd *cobra.Command, args []string) error { return tmp6.Run(c, args) }, + } + tmp6.RegisterFlags(sub, c) + sub.PersistentFlags().BoolVar(&tmp6.PrettyPrint, "pp", false, "Pretty print response body") + command.AddCommand(sub) + app.AddCommand(command) command = &cobra.Command{ Use: "get", Short: `Get user by id`, } - tmp6 := new(GetUserCommand) + tmp7 := new(GetUserCommand) sub = &cobra.Command{ Use: `user ["/users/USERID"]`, Short: ``, - RunE: func(cmd *cobra.Command, args []string) error { return tmp6.Run(c, args) }, + RunE: func(cmd *cobra.Command, args []string) error { return tmp7.Run(c, args) }, } - tmp6.RegisterFlags(sub, c) - sub.PersistentFlags().BoolVar(&tmp6.PrettyPrint, "pp", false, "Pretty print response body") + tmp7.RegisterFlags(sub, c) + sub.PersistentFlags().BoolVar(&tmp7.PrettyPrint, "pp", false, "Pretty print response body") command.AddCommand(sub) app.AddCommand(command) command = &cobra.Command{ Use: "get-all", Short: `Retrieves all active users`, } - tmp7 := new(GetAllUserCommand) + tmp8 := new(GetAllUserCommand) sub = &cobra.Command{ Use: `user ["/users"]`, Short: ``, - RunE: func(cmd *cobra.Command, args []string) error { return tmp7.Run(c, args) }, + RunE: func(cmd *cobra.Command, args []string) error { return tmp8.Run(c, args) }, } - tmp7.RegisterFlags(sub, c) - sub.PersistentFlags().BoolVar(&tmp7.PrettyPrint, "pp", false, "Pretty print response body") + tmp8.RegisterFlags(sub, c) + sub.PersistentFlags().BoolVar(&tmp8.PrettyPrint, "pp", false, "Pretty print response body") command.AddCommand(sub) app.AddCommand(command) command = &cobra.Command{ Use: "get-me", Short: `Retrieves the user information for the authenticated user`, } - tmp8 := new(GetMeUserCommand) + tmp9 := new(GetMeUserCommand) sub = &cobra.Command{ Use: `user ["/users/me"]`, Short: ``, - RunE: func(cmd *cobra.Command, args []string) error { return tmp8.Run(c, args) }, + RunE: func(cmd *cobra.Command, args []string) error { return tmp9.Run(c, args) }, } - tmp8.RegisterFlags(sub, c) - sub.PersistentFlags().BoolVar(&tmp8.PrettyPrint, "pp", false, "Pretty print response body") + tmp9.RegisterFlags(sub, c) + sub.PersistentFlags().BoolVar(&tmp9.PrettyPrint, "pp", false, "Pretty print response body") command.AddCommand(sub) app.AddCommand(command) command = &cobra.Command{ Use: "reset-verification-token", Short: `Reset verification token`, } - tmp9 := new(ResetVerificationTokenUserCommand) + tmp10 := new(ResetVerificationTokenUserCommand) sub = &cobra.Command{ Use: `user ["/users/verification/reset"]`, Short: ``, @@ -299,19 +339,19 @@ Payload example: Payload example: { - "email": "marianna_hartmann@oreillylebsack.org" + "email": "janie@gaylord.info" }`, - RunE: func(cmd *cobra.Command, args []string) error { return tmp9.Run(c, args) }, + RunE: func(cmd *cobra.Command, args []string) error { return tmp10.Run(c, args) }, } - tmp9.RegisterFlags(sub, c) - sub.PersistentFlags().BoolVar(&tmp9.PrettyPrint, "pp", false, "Pretty print response body") + tmp10.RegisterFlags(sub, c) + sub.PersistentFlags().BoolVar(&tmp10.PrettyPrint, "pp", false, "Pretty print response body") command.AddCommand(sub) app.AddCommand(command) command = &cobra.Command{ Use: "update", Short: `Update user`, } - tmp10 := new(UpdateUserCommand) + tmp11 := new(UpdateUserCommand) sub = &cobra.Command{ Use: `user ["/users/USERID"]`, Short: ``, @@ -321,41 +361,42 @@ Payload example: { "active": false, - "email": "isaac@mclaughlin.net", - "externalId": "Eveniet doloribus minus laudantium rerum soluta unde.", + "email": "marques@spinka.org", + "externalId": "Voluptas eveniet sunt nemo qui nam.", "namespaces": [ - "Assumenda ut quam in dolorem." + "Voluptas sunt voluptatem doloremque id.", + "Voluptas sunt voluptatem doloremque id.", + "Voluptas sunt voluptatem doloremque id." ], "organizations": [ - "Voluptatum rem eos voluptatibus.", - "Voluptatum rem eos voluptatibus." + "Facere vel.", + "Facere vel.", + "Facere vel." ], - "password": "2s23h8wwm4", + "password": "y9m9wo4go9", "roles": [ - "Autem sit sit inventore neque qui.", - "Autem sit sit inventore neque qui.", - "Autem sit sit inventore neque qui." + "Labore facere quasi et perspiciatis." ], - "token": "Voluptas cumque." + "token": "Nihil libero." }`, - RunE: func(cmd *cobra.Command, args []string) error { return tmp10.Run(c, args) }, + RunE: func(cmd *cobra.Command, args []string) error { return tmp11.Run(c, args) }, } - tmp10.RegisterFlags(sub, c) - sub.PersistentFlags().BoolVar(&tmp10.PrettyPrint, "pp", false, "Pretty print response body") + tmp11.RegisterFlags(sub, c) + sub.PersistentFlags().BoolVar(&tmp11.PrettyPrint, "pp", false, "Pretty print response body") command.AddCommand(sub) app.AddCommand(command) command = &cobra.Command{ Use: "verify", Short: `Verify a user by token`, } - tmp11 := new(VerifyUserCommand) + tmp12 := new(VerifyUserCommand) sub = &cobra.Command{ Use: `user ["/users/verify"]`, Short: ``, - RunE: func(cmd *cobra.Command, args []string) error { return tmp11.Run(c, args) }, + RunE: func(cmd *cobra.Command, args []string) error { return tmp12.Run(c, args) }, } - tmp11.RegisterFlags(sub, c) - sub.PersistentFlags().BoolVar(&tmp11.PrettyPrint, "pp", false, "Pretty print response body") + tmp12.RegisterFlags(sub, c) + sub.PersistentFlags().BoolVar(&tmp12.PrettyPrint, "pp", false, "Pretty print response body") command.AddCommand(sub) app.AddCommand(command) @@ -670,6 +711,39 @@ func (cmd *FindByEmailUserCommand) RegisterFlags(cc *cobra.Command, c *client.Cl cc.Flags().StringVar(&cmd.ContentType, "content", "", "Request content type override, e.g. 'application/x-www-form-urlencoded'") } +// Run makes the HTTP request corresponding to the FindUsersUserCommand command. +func (cmd *FindUsersUserCommand) Run(c *client.Client, args []string) error { + var path string + if len(args) > 0 { + path = args[0] + } else { + path = "/users/list" + } + var payload client.FilterPayload + if cmd.Payload != "" { + err := json.Unmarshal([]byte(cmd.Payload), &payload) + if err != nil { + return fmt.Errorf("failed to deserialize payload: %s", err) + } + } + logger := goa.NewLogger(log.New(os.Stderr, "", log.LstdFlags)) + ctx := goa.WithLogger(context.Background(), logger) + resp, err := c.FindUsersUser(ctx, path, &payload, cmd.ContentType) + if err != nil { + goa.LogError(ctx, "failed", "err", err) + return err + } + + goaclient.HandleResponse(c.Client, resp, cmd.PrettyPrint) + return nil +} + +// RegisterFlags registers the command flags with the command line. +func (cmd *FindUsersUserCommand) RegisterFlags(cc *cobra.Command, c *client.Client) { + cc.Flags().StringVar(&cmd.Payload, "payload", "", "Request body encoded in JSON") + cc.Flags().StringVar(&cmd.ContentType, "content", "", "Request content type override, e.g. 'application/x-www-form-urlencoded'") +} + // Run makes the HTTP request corresponding to the ForgotPasswordUserCommand command. func (cmd *ForgotPasswordUserCommand) Run(c *client.Client, args []string) error { var path string diff --git a/user.go b/user.go index a97c8e7..a6e4ead 100644 --- a/user.go +++ b/user.go @@ -12,12 +12,15 @@ import ( "github.com/Microkubes/microservice-security/auth" "github.com/Microkubes/microservice-tools/rabbitmq" "github.com/Microkubes/microservice-user/app" + "github.com/Microkubes/microservice-user/helpers" "github.com/Microkubes/microservice-user/store" "github.com/keitaroinc/goa" "golang.org/x/crypto/bcrypt" ) +const MaxResultsPerPage = 500 + // Email holds info for the email template type Email struct { ID string `json:"id,omitempty"` @@ -76,6 +79,7 @@ func (c *UserController) Create(ctx *app.CreateUserContext) error { Organizations: ctx.Payload.Organizations, Password: *ctx.Payload.Password, Roles: ctx.Payload.Roles, + CreatedAt: helpers.CurrentTimeMilliseconds(), //Token: ctx.Payload.Token, } @@ -206,6 +210,7 @@ func (c *UserController) Update(ctx *app.UpdateUserContext) error { payload := map[string]interface{}{} payload["active"] = ctx.Payload.Active + payload["modifiedAt"] = helpers.CurrentTimeMilliseconds() if ctx.Payload.Email != nil { payload["email"] = ctx.Payload.Email @@ -254,6 +259,67 @@ func (c *UserController) Update(ctx *app.UpdateUserContext) error { return ctx.OK(user) } +// FindUsers find users matching a filter +func (c *UserController) FindUsers(ctx *app.FindUsersUserContext) error { + + authObj := auth.GetAuth(ctx) + if authObj == nil { + return ctx.BadRequest(goa.ErrBadRequest("auth not set")) + } + + page := ctx.Payload.Page + if page < 1 { + return ctx.BadRequest(goa.ErrBadRequest("invalid page number")) + } + page-- + pageSize := ctx.Payload.PageSize + if pageSize > MaxResultsPerPage { + pageSize = MaxResultsPerPage + } + + var bf backends.Filter + if ctx.Payload.Filter != nil && len(ctx.Payload.Filter) > 0 { + bf = backends.NewFilter() + for _, filterProp := range ctx.Payload.Filter { + bf.Match(filterProp.Property, filterProp.Value) + } + } + + sortBy := "createdAt" + sortDir := "asc" + + if ctx.Payload.Sort != nil { + sortBy = ctx.Payload.Sort.Property + sortDir = ctx.Payload.Sort.Direction + } + + result, err := c.Store.Users.GetAll(bf, &store.UserRecord{}, sortBy, sortDir, pageSize, page) + if err != nil { + if backends.IsErrInvalidInput(err) { + return ctx.BadRequest(goa.ErrBadRequest(err)) + } + return ctx.InternalServerError(goa.ErrInternal(err)) + } + + users := *(result.(*[]*store.UserRecord)) + + usersPage := &app.UsersPage{ + Page: &page, + PageSize: &pageSize, + Items: []*app.Users{}, + } + + for _, userItem := range users { + user := userItem + if !user.Active { + continue + } + usersPage.Items = append(usersPage.Items, user.ToAppUsers()) + } + + return ctx.OK(usersPage) +} + // Find looks up a user by its email and password. Intended for internal use. func (c *UserController) Find(ctx *app.FindUserContext) error { @@ -318,7 +384,8 @@ func (c *UserController) Verify(ctx *app.VerifyUserContext) error { } update := map[string]interface{}{ - "active": true, + "active": true, + "modifiedAt": helpers.CurrentTimeMilliseconds(), } _, err = c.Store.Users.Save(&update, backends.NewFilter().Match("email", user.Email)) if err != nil { @@ -406,7 +473,8 @@ func (c *UserController) ForgotPassword(ctx *app.ForgotPasswordUserContext) erro fpToken.Token = generateToken(42) fpToken.ExpDate = generateExpDate() userRecord.FPToken = fpToken - _, err = c.Store.Users.Save(userRecord, backends.NewFilter().Match("id", userRecord.ID)) + userRecord.ModifiedAt = helpers.CurrentTimeMilliseconds() + _, err = c.Store.Users.Save(userRecord, backends.NewFilter().Match("id", userRecord.ID.Hex())) if err != nil { return ctx.InternalServerError(goa.ErrInternal(err)) } @@ -462,8 +530,9 @@ func (c *UserController) ForgotPasswordUpdate(ctx *app.ForgotPasswordUpdateUserC userRecord.FPToken.ExpDate = "0" userRecord.Password = hashedPassword + userRecord.ModifiedAt = helpers.CurrentTimeMilliseconds() - _, err = c.Store.Users.Save(userRecord, backends.NewFilter().Match("id", userRecord.ID)) + _, err = c.Store.Users.Save(userRecord, backends.NewFilter().Match("id", userRecord.ID.Hex())) if err != nil { return ctx.InternalServerError(goa.ErrInternal(err)) } diff --git a/user_test.go b/user_test.go index dd81b62..279e964 100644 --- a/user_test.go +++ b/user_test.go @@ -17,8 +17,8 @@ var db = store.NewDB() var ( service = goa.New("user-test") ctrl = NewUserController(service, db, nil) - ID = "b8cfa84f-bb6c-4c84-b39b-76dd32653921" - notFoundID = "dxcfa84f-bb6c-4c84-b39b-76dd32653900" + ID = "5df2103b5f1b640001142d3c" + notFoundID = "5df2103b5f1b640001142d4c" notFonundEmail = "not-found@gmail.com" notFoundToken = "not-found-token" badID = "bad-id"