You've already forked golang-saas-starter-kit
mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-08-08 22:36:41 +02:00
Merge branch 'issue16/web-app-signup' of gitlab.com:geeks-accelerator/oss/saas-starter-kit into issue16/web-app-signup
This commit is contained in:
@ -40,7 +40,6 @@ func APP(shutdown chan os.Signal, log *log.Logger, env webcontext.Env, staticDir
|
|||||||
// Construct the web.App which holds all routes as well as common Middleware.
|
// Construct the web.App which holds all routes as well as common Middleware.
|
||||||
app := web.NewApp(shutdown, log, env, middlewares...)
|
app := web.NewApp(shutdown, log, env, middlewares...)
|
||||||
|
|
||||||
|
|
||||||
// Register project management pages.
|
// Register project management pages.
|
||||||
p := Projects{
|
p := Projects{
|
||||||
MasterDB: masterDB,
|
MasterDB: masterDB,
|
||||||
@ -48,7 +47,6 @@ func APP(shutdown chan os.Signal, log *log.Logger, env webcontext.Env, staticDir
|
|||||||
}
|
}
|
||||||
app.Handle("GET", "/projects", p.Index, mid.AuthenticateSessionRequired(authenticator), mid.HasAuth())
|
app.Handle("GET", "/projects", p.Index, mid.AuthenticateSessionRequired(authenticator), mid.HasAuth())
|
||||||
|
|
||||||
|
|
||||||
// Register user management pages.
|
// Register user management pages.
|
||||||
us := Users{
|
us := Users{
|
||||||
MasterDB: masterDB,
|
MasterDB: masterDB,
|
||||||
@ -59,11 +57,13 @@ func APP(shutdown chan os.Signal, log *log.Logger, env webcontext.Env, staticDir
|
|||||||
NotifyEmail: notifyEmail,
|
NotifyEmail: notifyEmail,
|
||||||
SecretKey: secretKey,
|
SecretKey: secretKey,
|
||||||
}
|
}
|
||||||
app.Handle("GET", "/users", us.Index, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
|
||||||
app.Handle("POST", "/users/:user_id/update", us.Update, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
app.Handle("POST", "/users/:user_id/update", us.Update, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
||||||
app.Handle("GET", "/users/:user_id/update", us.Update, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
app.Handle("GET", "/users/:user_id/update", us.Update, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
||||||
app.Handle("GET", "/users/:user_id", us.View, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
app.Handle("POST", "/users/:user_id", us.View, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
||||||
|
app.Handle("GET", "/users/:user_id", us.View, mid.AuthenticateSessionRequired(authenticator), mid.HasAuth())
|
||||||
|
app.Handle("POST", "/users/create", us.Create, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
||||||
|
app.Handle("GET", "/users/create", us.Create, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
||||||
|
app.Handle("GET", "/users", us.Index, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
||||||
|
|
||||||
// Register user management and authentication endpoints.
|
// Register user management and authentication endpoints.
|
||||||
u := User{
|
u := User{
|
||||||
@ -94,7 +94,6 @@ func APP(shutdown chan os.Signal, log *log.Logger, env webcontext.Env, staticDir
|
|||||||
app.Handle("POST", "/user", u.View, mid.AuthenticateSessionRequired(authenticator), mid.HasAuth())
|
app.Handle("POST", "/user", u.View, mid.AuthenticateSessionRequired(authenticator), mid.HasAuth())
|
||||||
app.Handle("GET", "/user", u.View, mid.AuthenticateSessionRequired(authenticator), mid.HasAuth())
|
app.Handle("GET", "/user", u.View, mid.AuthenticateSessionRequired(authenticator), mid.HasAuth())
|
||||||
|
|
||||||
|
|
||||||
// Register account management endpoints.
|
// Register account management endpoints.
|
||||||
acc := Account{
|
acc := Account{
|
||||||
MasterDB: masterDB,
|
MasterDB: masterDB,
|
||||||
@ -106,7 +105,6 @@ func APP(shutdown chan os.Signal, log *log.Logger, env webcontext.Env, staticDir
|
|||||||
app.Handle("POST", "/account", acc.View, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
app.Handle("POST", "/account", acc.View, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
||||||
app.Handle("GET", "/account", acc.View, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
app.Handle("GET", "/account", acc.View, mid.AuthenticateSessionRequired(authenticator), mid.HasRole(auth.RoleAdmin))
|
||||||
|
|
||||||
|
|
||||||
// Register user management and authentication endpoints.
|
// Register user management and authentication endpoints.
|
||||||
s := Signup{
|
s := Signup{
|
||||||
MasterDB: masterDB,
|
MasterDB: masterDB,
|
||||||
@ -125,7 +123,6 @@ func APP(shutdown chan os.Signal, log *log.Logger, env webcontext.Env, staticDir
|
|||||||
app.Handle("GET", "/examples/flash-messages", ex.FlashMessages)
|
app.Handle("GET", "/examples/flash-messages", ex.FlashMessages)
|
||||||
app.Handle("GET", "/examples/images", ex.Images)
|
app.Handle("GET", "/examples/images", ex.Images)
|
||||||
|
|
||||||
|
|
||||||
// Register geo
|
// Register geo
|
||||||
g := Geo{
|
g := Geo{
|
||||||
MasterDB: masterDB,
|
MasterDB: masterDB,
|
||||||
@ -136,7 +133,6 @@ func APP(shutdown chan os.Signal, log *log.Logger, env webcontext.Env, staticDir
|
|||||||
app.Handle("GET", "/geo/geonames/postal_code/:postalCode", g.GeonameByPostalCode)
|
app.Handle("GET", "/geo/geonames/postal_code/:postalCode", g.GeonameByPostalCode)
|
||||||
app.Handle("GET", "/geo/country/:countryCode/timezones", g.CountryTimezones)
|
app.Handle("GET", "/geo/country/:countryCode/timezones", g.CountryTimezones)
|
||||||
|
|
||||||
|
|
||||||
// Register root
|
// Register root
|
||||||
r := Root{
|
r := Root{
|
||||||
MasterDB: masterDB,
|
MasterDB: masterDB,
|
||||||
@ -152,7 +148,6 @@ func APP(shutdown chan os.Signal, log *log.Logger, env webcontext.Env, staticDir
|
|||||||
app.Handle("GET", "/index.html", r.IndexHtml)
|
app.Handle("GET", "/index.html", r.IndexHtml)
|
||||||
app.Handle("GET", "/robots.txt", r.RobotTxt)
|
app.Handle("GET", "/robots.txt", r.RobotTxt)
|
||||||
|
|
||||||
|
|
||||||
// Register health check endpoint. This route is not authenticated.
|
// Register health check endpoint. This route is not authenticated.
|
||||||
check := Check{
|
check := Check{
|
||||||
MasterDB: masterDB,
|
MasterDB: masterDB,
|
||||||
@ -161,7 +156,6 @@ func APP(shutdown chan os.Signal, log *log.Logger, env webcontext.Env, staticDir
|
|||||||
}
|
}
|
||||||
app.Handle("GET", "/v1/health", check.Health)
|
app.Handle("GET", "/v1/health", check.Health)
|
||||||
|
|
||||||
|
|
||||||
// Handle static files/pages. Render a custom 404 page when file not found.
|
// Handle static files/pages. Render a custom 404 page when file not found.
|
||||||
static := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
static := func(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
||||||
err := web.StaticHandler(ctx, w, r, params, staticDir, "")
|
err := web.StaticHandler(ctx, w, r, params, staticDir, "")
|
||||||
|
@ -6,9 +6,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/datatable"
|
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/geonames"
|
"geeks-accelerator/oss/saas-starter-kit/internal/geonames"
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
|
||||||
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/datatable"
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/notify"
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/notify"
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web"
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web"
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
|
||||||
@ -33,10 +33,28 @@ type Users struct {
|
|||||||
SecretKey string
|
SecretKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
func UrlUsersView(userID string) string {
|
func urlUsersIndex() string {
|
||||||
|
return fmt.Sprintf("/users")
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlUsersCreate() string {
|
||||||
|
return fmt.Sprintf("/users/create")
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlUsersView(userID string) string {
|
||||||
return fmt.Sprintf("/users/%s", userID)
|
return fmt.Sprintf("/users/%s", userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func urlUsersUpdate(userID string) string {
|
||||||
|
return fmt.Sprintf("/users/%s/update", userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLoginRequest extends the AuthenicateRequest with the RememberMe flag.
|
||||||
|
type UserCreateRequest struct {
|
||||||
|
user.UserCreateRequest
|
||||||
|
Roles user_account.UserAccountRoles `json:"roles" validate:"required,dive,oneof=admin user" enums:"admin,user" swaggertype:"array,string" example:"admin"`
|
||||||
|
}
|
||||||
|
|
||||||
// Index handles listing all the users for the current account.
|
// Index handles listing all the users for the current account.
|
||||||
func (h *Users) Index(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
func (h *Users) Index(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
||||||
|
|
||||||
@ -77,7 +95,25 @@ func (h *Users) Index(ctx context.Context, w http.ResponseWriter, r *http.Reques
|
|||||||
v.Value = fmt.Sprintf("%d", q.ID)
|
v.Value = fmt.Sprintf("%d", q.ID)
|
||||||
case "name":
|
case "name":
|
||||||
v.Value = q.Name
|
v.Value = q.Name
|
||||||
v.Formatted = fmt.Sprintf("<a href='%s'>%s</a>", UrlUsersView(q.ID), v.Value)
|
v.Formatted = fmt.Sprintf("<a href='%s'>%s</a>", urlUsersView(q.ID), v.Value)
|
||||||
|
case "status":
|
||||||
|
v.Value = q.Status.String()
|
||||||
|
|
||||||
|
var subStatusClass string
|
||||||
|
var subStatusIcon string
|
||||||
|
switch q.Status {
|
||||||
|
case user_account.UserAccountStatus_Active:
|
||||||
|
subStatusClass = "text-green"
|
||||||
|
subStatusIcon = "far fa-dot-circle"
|
||||||
|
case user_account.UserAccountStatus_Invited:
|
||||||
|
subStatusClass = "text-blue"
|
||||||
|
subStatusIcon = "far fa-unicorn"
|
||||||
|
case user_account.UserAccountStatus_Disabled:
|
||||||
|
subStatusClass = "text-orange"
|
||||||
|
subStatusIcon = "far fa-circle"
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Formatted = fmt.Sprintf("<span class='cell-font-status %s'><i class='%s'></i>%s</span>", subStatusClass, subStatusIcon, web.EnumValueTitle(v.Value))
|
||||||
case "created_at":
|
case "created_at":
|
||||||
dt := web.NewTimeResponse(ctx, q.CreatedAt)
|
dt := web.NewTimeResponse(ctx, q.CreatedAt)
|
||||||
v.Value = dt.Local
|
v.Value = dt.Local
|
||||||
@ -97,6 +133,7 @@ func (h *Users) Index(ctx context.Context, w http.ResponseWriter, r *http.Reques
|
|||||||
|
|
||||||
loadFunc := func(ctx context.Context, sorting string, fields []datatable.DisplayField) (resp [][]datatable.ColumnValue, err error) {
|
loadFunc := func(ctx context.Context, sorting string, fields []datatable.DisplayField) (resp [][]datatable.ColumnValue, err error) {
|
||||||
res, err := user_account.UserFindByAccount(ctx, claims, h.MasterDB, user_account.UserFindByAccountRequest{
|
res, err := user_account.UserFindByAccount(ctx, claims, h.MasterDB, user_account.UserFindByAccountRequest{
|
||||||
|
AccountID: claims.Audience,
|
||||||
Order: strings.Split(sorting, ","),
|
Order: strings.Split(sorting, ","),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -133,30 +170,195 @@ func (h *Users) Index(ctx context.Context, w http.ResponseWriter, r *http.Reques
|
|||||||
|
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"datatable": dt.Response(),
|
"datatable": dt.Response(),
|
||||||
|
"urlUsersCreate": urlUsersCreate(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-index.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
|
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-index.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// View handles displaying a user.
|
// Create handles creating a new user for the account.
|
||||||
func (h *Users) View(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
func (h *Users) Create(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
||||||
|
|
||||||
data := make(map[string]interface{})
|
ctxValues, err := webcontext.ContextValues(ctx)
|
||||||
f := func() error {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
claims, err := auth.ClaimsFromContext(ctx)
|
claims, err := auth.ClaimsFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
usr, err := user.ReadByID(ctx, claims, h.MasterDB, claims.Subject)
|
//
|
||||||
|
req := new(UserCreateRequest)
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
f := func() (bool, error) {
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := schema.NewDecoder()
|
||||||
|
decoder.IgnoreUnknownKeys(true)
|
||||||
|
|
||||||
|
if err := decoder.Decode(req, r.PostForm); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass the uniq check on email here for the moment, it will be caught before the user_account is
|
||||||
|
// created by user.Create.
|
||||||
|
ctx = context.WithValue(ctx, webcontext.KeyTagUnique, true)
|
||||||
|
|
||||||
|
// Validate the request.
|
||||||
|
err = webcontext.Validator().StructCtx(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
if verr, ok := weberror.NewValidationError(ctx, err); ok {
|
||||||
|
data["validationErrors"] = verr.(*weberror.Error)
|
||||||
|
return false, nil
|
||||||
|
} else {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usr, err := user.Create(ctx, claims, h.MasterDB, req.UserCreateRequest, ctxValues.Now)
|
||||||
|
if err != nil {
|
||||||
|
switch errors.Cause(err) {
|
||||||
|
default:
|
||||||
|
if verr, ok := weberror.NewValidationError(ctx, err); ok {
|
||||||
|
data["validationErrors"] = verr.(*weberror.Error)
|
||||||
|
return false, nil
|
||||||
|
} else {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uaStatus := user_account.UserAccountStatus_Active
|
||||||
|
_, err = user_account.Create(ctx, claims, h.MasterDB, user_account.UserAccountCreateRequest{
|
||||||
|
UserID: usr.ID,
|
||||||
|
AccountID: claims.Audience,
|
||||||
|
Roles: req.Roles,
|
||||||
|
Status: &uaStatus,
|
||||||
|
}, ctxValues.Now)
|
||||||
|
if err != nil {
|
||||||
|
switch errors.Cause(err) {
|
||||||
|
default:
|
||||||
|
if verr, ok := weberror.NewValidationError(ctx, err); ok {
|
||||||
|
data["validationErrors"] = verr.(*weberror.Error)
|
||||||
|
return false, nil
|
||||||
|
} else {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display a success message to the user.
|
||||||
|
webcontext.SessionFlashSuccess(ctx,
|
||||||
|
"User Created",
|
||||||
|
"User successfully created.")
|
||||||
|
err = webcontext.ContextSession(ctx).Save(r, w)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, urlUsersView(usr.ID), http.StatusFound)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
end, err := f()
|
||||||
|
if err != nil {
|
||||||
|
return web.RenderError(ctx, w, r, err, h.Renderer, TmplLayoutBase, TmplContentErrorGeneric, web.MIMETextHTMLCharsetUTF8)
|
||||||
|
} else if end {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data["timezones"], err = geonames.ListTimezones(ctx, h.MasterDB)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var roleValues []interface{}
|
||||||
|
for _, v := range user_account.UserAccountRole_Values {
|
||||||
|
roleValues = append(roleValues, string(v))
|
||||||
|
}
|
||||||
|
data["roles"] = web.NewEnumResponse(ctx, nil, roleValues...)
|
||||||
|
|
||||||
|
data["form"] = req
|
||||||
|
|
||||||
|
if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(UserCreateRequest{})); ok {
|
||||||
|
data["validationDefaults"] = verr.(*weberror.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-create.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// View handles displaying a user.
|
||||||
|
func (h *Users) View(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
||||||
|
|
||||||
|
userID := params["user_id"]
|
||||||
|
|
||||||
|
ctxValues, err := webcontext.ContextValues(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := auth.ClaimsFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
f := func() (bool, error) {
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.PostForm.Get("action") {
|
||||||
|
case "archive":
|
||||||
|
err = user.Archive(ctx, claims, h.MasterDB, user.UserArchiveRequest{
|
||||||
|
ID: userID,
|
||||||
|
}, ctxValues.Now)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
webcontext.SessionFlashSuccess(ctx,
|
||||||
|
"User Archive",
|
||||||
|
"User successfully archive.")
|
||||||
|
err = webcontext.ContextSession(ctx).Save(r, w)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, urlUsersIndex(), http.StatusFound)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
end, err := f()
|
||||||
|
if err != nil {
|
||||||
|
return web.RenderError(ctx, w, r, err, h.Renderer, TmplLayoutBase, TmplContentErrorGeneric, web.MIMETextHTMLCharsetUTF8)
|
||||||
|
} else if end {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
usr, err := user.ReadByID(ctx, claims, h.MasterDB, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
data["user"] = usr.Response(ctx)
|
data["user"] = usr.Response(ctx)
|
||||||
|
|
||||||
usrAccs, err := user_account.FindByUserID(ctx, claims, h.MasterDB, claims.Subject, false)
|
usrAccs, err := user_account.FindByUserID(ctx, claims, h.MasterDB, userID, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -168,12 +370,7 @@ func (h *Users) View(ctx context.Context, w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
data["urlUsersUpdate"] = urlUsersUpdate(userID)
|
||||||
}
|
|
||||||
|
|
||||||
if err := f(); err != nil {
|
|
||||||
return web.RenderError(ctx, w, r, err, h.Renderer, TmplLayoutBase, TmplContentErrorGeneric, web.MIMETextHTMLCharsetUTF8)
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-view.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
|
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-view.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
|
||||||
}
|
}
|
||||||
@ -181,6 +378,8 @@ func (h *Users) View(ctx context.Context, w http.ResponseWriter, r *http.Request
|
|||||||
// Update handles updating a user for the account.
|
// Update handles updating a user for the account.
|
||||||
func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
||||||
|
|
||||||
|
userID := params["user_id"]
|
||||||
|
|
||||||
ctxValues, err := webcontext.ContextValues(ctx)
|
ctxValues, err := webcontext.ContextValues(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -207,7 +406,7 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque
|
|||||||
if err := decoder.Decode(req, r.PostForm); err != nil {
|
if err := decoder.Decode(req, r.PostForm); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
req.ID = claims.Subject
|
req.ID = userID
|
||||||
|
|
||||||
err = user.Update(ctx, claims, h.MasterDB, *req, ctxValues.Now)
|
err = user.Update(ctx, claims, h.MasterDB, *req, ctxValues.Now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -228,7 +427,7 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque
|
|||||||
if err := decoder.Decode(pwdReq, r.PostForm); err != nil {
|
if err := decoder.Decode(pwdReq, r.PostForm); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
pwdReq.ID = claims.Subject
|
pwdReq.ID = userID
|
||||||
|
|
||||||
err = user.UpdatePassword(ctx, claims, h.MasterDB, *pwdReq, ctxValues.Now)
|
err = user.UpdatePassword(ctx, claims, h.MasterDB, *pwdReq, ctxValues.Now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -253,7 +452,7 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/users/"+req.ID, http.StatusFound)
|
http.Redirect(w, r, urlUsersView(req.ID), http.StatusFound)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +466,7 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
usr, err := user.ReadByID(ctx, claims, h.MasterDB, claims.Subject)
|
usr, err := user.ReadByID(ctx, claims, h.MasterDB, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -298,4 +497,3 @@ func (h *Users) Update(ctx context.Context, w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-update.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
|
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "users-update.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
91
cmd/web-app/templates/content/users-create.gohtml
Normal file
91
cmd/web-app/templates/content/users-create.gohtml
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
{{define "title"}}Create User{{end}}
|
||||||
|
{{define "style"}}
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
{{define "content"}}
|
||||||
|
<form class="user" method="post" novalidate>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputFirstName">First Name</label>
|
||||||
|
<input type="text" class="form-control {{ ValidationFieldClass $.validationErrors "UserCreateRequest.FirstName" }}" placeholder="enter first name" name="FirstName" value="{{ .form.FirstName }}" required>
|
||||||
|
{{template "invalid-feedback" dict "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors "fieldName" "UserCreateRequest.FirstName" }}
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputLastName">Last Name</label>
|
||||||
|
<input type="text" class="form-control {{ ValidationFieldClass $.validationErrors "UserCreateRequest.LastName" }}" placeholder="enter last name" name="LastName" value="{{ .form.LastName }}" required>
|
||||||
|
{{template "invalid-feedback" dict "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors "fieldName" "UserCreateRequest.LastName" }}
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputEmail">Email</label>
|
||||||
|
<input type="text" class="form-control {{ ValidationFieldClass $.validationErrors "UserCreateRequest.Email" }}" placeholder="enter email" name="Email" value="{{ .form.Email }}" required>
|
||||||
|
{{template "invalid-feedback" dict "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors "fieldName" "UserCreateRequest.Email" }}
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputTimezone">Timezone</label>
|
||||||
|
<select class="form-control {{ ValidationFieldClass $.validationErrors "UserCreateRequest.Timezone" }}" id="inputTimezone" name="Timezone">
|
||||||
|
<option value="">Not set</option>
|
||||||
|
{{ range $idx, $t := .timezones }}
|
||||||
|
<option value="{{ $t }}" {{ if CmpString $t $.form.Timezone }}selected="selected"{{ end }}>{{ $t }}</option>
|
||||||
|
{{ end }}
|
||||||
|
</select>
|
||||||
|
{{template "invalid-feedback" dict "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors "fieldName" "UserCreateRequest.Timezone" }}
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputPassword">Password</label>
|
||||||
|
<input type="password" class="form-control {{ ValidationFieldClass $.validationErrors "UserCreateRequest.Password" }}" id="inputPassword" placeholder="" name="Password" value="{{ .form.Password }}" required>
|
||||||
|
<span class="help-block "><small><a a href="javascript:void(0)" id="btnGeneratePassword"><i class="fal fa-random"></i>Generate random password </a></small></span>
|
||||||
|
{{template "invalid-feedback" dict "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors "fieldName" "UserCreateRequest.Password" }}
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputPasswordConfirm">Confirm Password</label>
|
||||||
|
<input type="password" class="form-control {{ ValidationFieldClass $.validationErrors "UserCreateRequest.PasswordConfirm" }}" id="inputPasswordConfirm" placeholder="" name="PasswordConfirm" value="{{ .form.PasswordConfirm }}" required>
|
||||||
|
{{template "invalid-feedback" dict "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors "fieldName" "UserCreateRequest.PasswordConfirm" }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputRoles">Roles</label>
|
||||||
|
<select class="form-control {{ ValidationFieldClass $.validationErrors "Roles" }}" id="inputRoles" name="Roles" multiple="multiple">
|
||||||
|
{{ range $r := .roles.Options }}
|
||||||
|
{{ $selectRole := false }}
|
||||||
|
{{ range $fr := $.form.Roles }}
|
||||||
|
{{ if eq $r.Value $fr }}{{ $selectRole = true }}{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
<option value="{{ $r.Value }}" {{ if $selectRole }}selected="selected"{{ end }}>{{ $r.Title }}</option>
|
||||||
|
{{ end }}
|
||||||
|
</select>
|
||||||
|
{{template "invalid-feedback" dict "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors "fieldName" "Roles" }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="spacer-30"></div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<input id="btnSubmit" type="submit" name="action" value="Save" class="btn btn-primary"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
{{define "js"}}
|
||||||
|
<script>
|
||||||
|
function randomPassword(length) {
|
||||||
|
var chars = "abcdefghijklmnopqrstuvwxyz!@#&*()-+<>ABCDEFGHIJKLMNOP1234567890";
|
||||||
|
var pass = "";
|
||||||
|
for (var x = 0; x < length; x++) {
|
||||||
|
var i = Math.floor(Math.random() * chars.length);
|
||||||
|
pass += chars.charAt(i);
|
||||||
|
}
|
||||||
|
return pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
$("#btnGeneratePassword").on("click", function() {
|
||||||
|
pwd = randomPassword(12);
|
||||||
|
$("#inputPassword").attr('type', 'text').val(pwd)
|
||||||
|
$("#inputPasswordConfirm").attr('type', 'text').val(pwd)
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{end}}
|
@ -1,5 +1,6 @@
|
|||||||
{{define "title"}}Users{{end}}
|
{{define "title"}}Users{{end}}
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
|
<a href="{{ .urlUsersCreate }}">Create User</a>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
|
@ -20,7 +20,13 @@
|
|||||||
<p class="font-10"><a href="https://gravatar.com" target="_blank">Update Avatar</a></p>
|
<p class="font-10"><a href="https://gravatar.com" target="_blank">Update Avatar</a></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<a href="/user/update" class="btn btn-outline-success"><i class="fal fa-edit"></i>Edit Details</a>
|
<a href="{{ .urlUsersUpdate }}" class="btn btn-outline-success"><i class="fal fa-edit"></i>Edit Details</a>
|
||||||
|
{{ $ctxUser := ContextUser $._Ctx }}
|
||||||
|
{{ if $ctxUser }}
|
||||||
|
{{ if ne .user.ID $ctxUser.ID }}
|
||||||
|
<form method="post"><input type="hidden" name="action" value="archive" /><input type="submit" value="Archive"></form>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -28,7 +28,11 @@
|
|||||||
serverSide: true,
|
serverSide: true,
|
||||||
ordering: true,
|
ordering: true,
|
||||||
searching: true,
|
searching: true,
|
||||||
ajax: "{{ .datatable.AjaxUrl }}",
|
ajax: {
|
||||||
|
"url": "{{ .datatable.AjaxUrl }}",
|
||||||
|
"contentType": "application/json; charset=utf-8",
|
||||||
|
"dataType": "json"
|
||||||
|
},
|
||||||
scrollY: 300,
|
scrollY: 300,
|
||||||
scroller: {
|
scroller: {
|
||||||
loadingIndicator: true
|
loadingIndicator: true
|
||||||
|
@ -122,8 +122,8 @@ func (r Request) CacheKey() string {
|
|||||||
func ParseQueryValues(vals url.Values) (Request, error) {
|
func ParseQueryValues(vals url.Values) (Request, error) {
|
||||||
|
|
||||||
req := Request{
|
req := Request{
|
||||||
Columns:make(map[int]Column),
|
Columns: make(map[int]Column),
|
||||||
Order: make(map[int]Order) ,
|
Order: make(map[int]Order),
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@ -182,7 +182,7 @@ func ParseQueryValues(vals url.Values) (Request, error) {
|
|||||||
return req, errors.WithMessagef(ErrInvalidColumn, "Unable to map query Column Search %s for %s", svn, kn)
|
return req, errors.WithMessagef(ErrInvalidColumn, "Unable to map query Column Search %s for %s", svn, kn)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return req, errors.WithMessagef(ErrInvalidColumn,"Unable to map query Column %s for %s", sn, kn)
|
return req, errors.WithMessagef(ErrInvalidColumn, "Unable to map query Column %s for %s", sn, kn)
|
||||||
}
|
}
|
||||||
req.Columns[idx] = curCol
|
req.Columns[idx] = curCol
|
||||||
case "order":
|
case "order":
|
||||||
@ -235,7 +235,6 @@ func ParseQueryValues(vals url.Values) (Request, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
case "search":
|
case "search":
|
||||||
sn := strings.Split(pts[1], "]")[0]
|
sn := strings.Split(pts[1], "]")[0]
|
||||||
switch sn {
|
switch sn {
|
||||||
@ -354,7 +353,7 @@ func New(ctx context.Context, w http.ResponseWriter, r *http.Request, redisClien
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dt.cacheKey = fmt.Sprintf("%x", md5.Sum([]byte(dt.resp.AjaxUrl + dt.req.CacheKey() + dt.stateId)))
|
dt.cacheKey = fmt.Sprintf("%x", md5.Sum([]byte(dt.resp.AjaxUrl+dt.req.CacheKey()+dt.stateId)))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//for idx, f := range fields {
|
//for idx, f := range fields {
|
||||||
@ -513,7 +512,6 @@ func (dt *Datatable) Render() (rendered bool, err error) {
|
|||||||
match = strings.Contains(l[i].Value, cn.Search.Value)
|
match = strings.Contains(l[i].Value, cn.Search.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if !match {
|
if !match {
|
||||||
//fmt.Println("-> no match")
|
//fmt.Println("-> no match")
|
||||||
skip = true
|
skip = true
|
||||||
|
@ -18,13 +18,10 @@ type ctxKeyTagUniqueName int
|
|||||||
|
|
||||||
const KeyTagUniqueName ctxKeyTagUniqueName = 1
|
const KeyTagUniqueName ctxKeyTagUniqueName = 1
|
||||||
|
|
||||||
|
|
||||||
type ctxKeyTagUniqueEmail int
|
type ctxKeyTagUniqueEmail int
|
||||||
|
|
||||||
const KeyTagUniqueEmail ctxKeyTagUniqueEmail = 1
|
const KeyTagUniqueEmail ctxKeyTagUniqueEmail = 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// validate holds the settings and caches for validating request struct values.
|
// validate holds the settings and caches for validating request struct values.
|
||||||
var validate *validator.Validate
|
var validate *validator.Validate
|
||||||
|
|
||||||
@ -70,7 +67,6 @@ func Validator() *validator.Validate {
|
|||||||
return validate
|
return validate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Signup performs the steps needed to create a new account, new user and then associate
|
// Signup performs the steps needed to create a new account, new user and then associate
|
||||||
// both records with a new user_account entry.
|
// both records with a new user_account entry.
|
||||||
func Signup(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req SignupRequest, now time.Time) (*SignupResult, error) {
|
func Signup(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req SignupRequest, now time.Time) (*SignupResult, error) {
|
||||||
|
@ -647,6 +647,8 @@ func Archive(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req UserA
|
|||||||
err = CanModifyUser(ctx, claims, dbConn, req.ID)
|
err = CanModifyUser(ctx, claims, dbConn, req.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
} else if claims.Subject != "" && claims.Subject == req.ID {
|
||||||
|
return errors.WithStack(ErrForbidden)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If now empty set it to the current time.
|
// If now empty set it to the current time.
|
||||||
@ -770,6 +772,8 @@ func Delete(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, req UserDe
|
|||||||
err = CanModifyUser(ctx, claims, dbConn, req.ID)
|
err = CanModifyUser(ctx, claims, dbConn, req.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
} else if claims.Subject != "" && claims.Subject == req.ID {
|
||||||
|
return errors.WithStack(ErrForbidden)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start a new transaction to handle rollbacks on error.
|
// Start a new transaction to handle rollbacks on error.
|
||||||
|
@ -3,9 +3,11 @@ package user_account
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
|
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
|
||||||
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
|
||||||
|
"github.com/huandu/go-sqlbuilder"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,6 +24,124 @@ func UserFindByAccount(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
timezone,
|
||||||
|
account_id,
|
||||||
|
status,
|
||||||
|
roles,
|
||||||
|
created_at,
|
||||||
|
updated_at,
|
||||||
|
archived_at FROM (
|
||||||
|
SELECT
|
||||||
|
u.id,
|
||||||
|
u.first_name,
|
||||||
|
u.last_name,
|
||||||
|
concat(u.first_name, ' ',u.last_name) as name,
|
||||||
|
u.email,
|
||||||
|
u.timezone,
|
||||||
|
ua.account_id,
|
||||||
|
ua.status,
|
||||||
|
ua.roles,
|
||||||
|
CASE WHEN ua.created_at > u.created_at THEN ua.created_at ELSE u.created_at END AS created_at,
|
||||||
|
CASE WHEN ua.updated_at > u.updated_at THEN ua.updated_at ELSE u.updated_at END AS updated_at,
|
||||||
|
CASE WHEN ua.archived_at > u.archived_at THEN ua.archived_at ELSE u.archived_at END AS archived_at
|
||||||
|
FROM users u
|
||||||
|
JOIN users_accounts ua
|
||||||
|
ON u.id = ua.user_id AND ua.account_id = 'df1a8a65-b00b-4640-9a64-66c1a355b17c'
|
||||||
|
WHERE
|
||||||
|
(u.archived_at IS NULL AND ua.archived_at IS NULL) AND
|
||||||
|
account_id IN (SELECT account_id FROM users_accounts WHERE (account_id = ? OR user_id = ?))
|
||||||
|
) res ORDER BY id asc
|
||||||
|
|
||||||
return nil , nil
|
*/
|
||||||
|
|
||||||
|
subQuery := sqlbuilder.NewSelectBuilder().
|
||||||
|
Select("u.id,u.first_name,u.last_name,concat(u.first_name, ' ',u.last_name) as name,u.email,u.timezone,ua.account_id,ua.status,ua.roles,"+
|
||||||
|
"CASE WHEN ua.created_at > u.created_at THEN ua.created_at ELSE u.created_at END AS created_at,"+
|
||||||
|
"CASE WHEN ua.updated_at > u.updated_at THEN ua.updated_at ELSE u.updated_at END AS updated_at,"+
|
||||||
|
"CASE WHEN ua.archived_at > u.archived_at THEN ua.archived_at ELSE u.archived_at END AS archived_at").
|
||||||
|
From(userTableName+" u").
|
||||||
|
Join(userAccountTableName+" ua", "u.id = ua.user_id", "ua.account_id = '"+req.AccountID+"'")
|
||||||
|
|
||||||
|
if !req.IncludeArchived {
|
||||||
|
subQuery.Where(subQuery.And(
|
||||||
|
subQuery.IsNull("u.archived_at"),
|
||||||
|
subQuery.IsNull("ua.archived_at")))
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims.Audience != "" || claims.Subject != "" {
|
||||||
|
// Build select statement for users_accounts table
|
||||||
|
authQuery := sqlbuilder.NewSelectBuilder().Select("account_id").From(userAccountTableName)
|
||||||
|
|
||||||
|
var or []string
|
||||||
|
if claims.Audience != "" {
|
||||||
|
or = append(or, authQuery.Equal("account_id", claims.Audience))
|
||||||
|
}
|
||||||
|
if claims.Subject != "" {
|
||||||
|
or = append(or, authQuery.Equal("user_id", claims.Subject))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append sub query
|
||||||
|
if len(or) > 0 {
|
||||||
|
authQuery.Where(authQuery.Or(or...))
|
||||||
|
subQuery.Where(subQuery.In("account_id", authQuery))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subQueryStr, queryArgs := subQuery.Build()
|
||||||
|
|
||||||
|
query := sqlbuilder.NewSelectBuilder().
|
||||||
|
Select("id,first_name,last_name,name,email,timezone,account_id,status,roles,created_at,updated_at,archived_at").
|
||||||
|
From("(" + subQueryStr + ") res")
|
||||||
|
if req.Where != "" {
|
||||||
|
query.Where(query.And(req.Where))
|
||||||
|
}
|
||||||
|
if len(req.Order) > 0 {
|
||||||
|
query.OrderBy(req.Order...)
|
||||||
|
}
|
||||||
|
if req.Limit != nil {
|
||||||
|
query.Limit(int(*req.Limit))
|
||||||
|
}
|
||||||
|
if req.Offset != nil {
|
||||||
|
query.Offset(int(*req.Offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
queryStr, moreQueryArgs := query.Build()
|
||||||
|
queryStr = dbConn.Rebind(queryStr)
|
||||||
|
|
||||||
|
queryArgs = append(queryArgs, moreQueryArgs...)
|
||||||
|
|
||||||
|
// fetch all places from the db
|
||||||
|
rows, err := dbConn.QueryContext(ctx, queryStr, queryArgs...)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "query - %s", query.String())
|
||||||
|
err = errors.WithMessage(err, "find users failed")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate over each row
|
||||||
|
resp := []*User{}
|
||||||
|
for rows.Next() {
|
||||||
|
|
||||||
|
var (
|
||||||
|
u User
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
err = rows.Scan(&u.ID, &u.FirstName, &u.LastName, &u.Name, &u.Email, &u.Timezone, &u.AccountID, &u.Status,
|
||||||
|
&u.Roles, &u.CreatedAt, &u.UpdatedAt, &u.ArchivedAt)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "query - %s", query.String())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = append(resp, &u)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
10
internal/user_account/user_test.go
Normal file
10
internal/user_account/user_test.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package user_account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestUserFindByAccount validates that find users by account works.
|
||||||
|
func TestUserFindByAccount(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user