This commit is contained in:
parent
cca2b360f4
commit
547f6e7b3c
@ -49,6 +49,8 @@ func (a *App) SetupRoutes() {
|
|||||||
e.GET("/home", handlers.Home(a.db))
|
e.GET("/home", handlers.Home(a.db))
|
||||||
|
|
||||||
e.GET("/manage/users", handlers.ManageUsers(a.db))
|
e.GET("/manage/users", handlers.ManageUsers(a.db))
|
||||||
|
e.GET("/manage/users/new", handlers.ManageUsers(a.db))
|
||||||
|
e.POST("/manage/users/create", handlers.CreateUser(a.db))
|
||||||
|
|
||||||
e.GET("/logout", handlers.Logout())
|
e.GET("/logout", handlers.Logout())
|
||||||
e.POST("/logout", handlers.Logout())
|
e.POST("/logout", handlers.Logout())
|
||||||
|
File diff suppressed because one or more lines are too long
@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ManageUsers(client *ent.Client) echo.HandlerFunc {
|
func ManageUsers(client *ent.Client) echo.HandlerFunc { //nolint:gocognit
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
addHeaders(c)
|
addHeaders(c)
|
||||||
|
|
||||||
@ -77,6 +77,46 @@ func ManageUsers(client *ent.Client) echo.HandlerFunc {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data := make(map[string]any)
|
||||||
|
flash := sess.Values["flash"]
|
||||||
|
path := c.Request().URL.Path
|
||||||
|
|
||||||
|
if path == "/manage/users/new" {
|
||||||
|
p := page{
|
||||||
|
AppName: setting.AppName(),
|
||||||
|
AppVer: appver,
|
||||||
|
Title: "Manage Users - New User",
|
||||||
|
CSRF: c.Get("csrf").(string),
|
||||||
|
DevelMode: setting.IsDevel(),
|
||||||
|
Current: "manage-users-new",
|
||||||
|
User: u,
|
||||||
|
}
|
||||||
|
|
||||||
|
if flash != nil {
|
||||||
|
data["flash"] = flash.(string)
|
||||||
|
|
||||||
|
delete(sess.Values, "flash")
|
||||||
|
|
||||||
|
_ = sess.Save(c.Request(), c.Response())
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.Render(
|
||||||
|
http.StatusOK,
|
||||||
|
"manage/user-new.tmpl",
|
||||||
|
p,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
http.StatusText(http.StatusInternalServerError),
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var allUsers []*moduser.User
|
var allUsers []*moduser.User
|
||||||
|
|
||||||
if users, err := moduser.ListAll(ctx, client); err == nil && users != nil {
|
if users, err := moduser.ListAll(ctx, client); err == nil && users != nil {
|
||||||
@ -104,23 +144,31 @@ func ManageUsers(client *ent.Client) echo.HandlerFunc {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := make(map[string]any)
|
|
||||||
|
|
||||||
data["allusers"] = allUsers
|
data["allusers"] = allUsers
|
||||||
|
|
||||||
|
p := page{
|
||||||
|
AppName: setting.AppName(),
|
||||||
|
AppVer: appver,
|
||||||
|
Title: "Manage Users",
|
||||||
|
CSRF: c.Get("csrf").(string),
|
||||||
|
DevelMode: setting.IsDevel(),
|
||||||
|
Current: "manage-users",
|
||||||
|
User: u,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
if flash != nil {
|
||||||
|
data["flash"] = flash.(string)
|
||||||
|
|
||||||
|
delete(sess.Values, "flash")
|
||||||
|
|
||||||
|
_ = sess.Save(c.Request(), c.Response())
|
||||||
|
}
|
||||||
|
|
||||||
err := c.Render(
|
err := c.Render(
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
"manage/user.tmpl",
|
"manage/user.tmpl",
|
||||||
page{
|
p,
|
||||||
AppName: setting.AppName(),
|
|
||||||
AppVer: appver,
|
|
||||||
Title: "Manage Users",
|
|
||||||
CSRF: c.Get("csrf").(string),
|
|
||||||
DevelMode: setting.IsDevel(),
|
|
||||||
Current: "manage-users",
|
|
||||||
User: u,
|
|
||||||
Data: data,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return renderErrorPage(
|
return renderErrorPage(
|
||||||
@ -134,3 +182,146 @@ func ManageUsers(client *ent.Client) echo.HandlerFunc {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateUser(client *ent.Client) echo.HandlerFunc { //nolint:gocognit
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
addHeaders(c)
|
||||||
|
|
||||||
|
sess, _ := session.Get(setting.SessionCookieName(), c)
|
||||||
|
if sess == nil {
|
||||||
|
c.Logger().Info("No session found, unauthorised.")
|
||||||
|
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
http.StatusText(http.StatusUnauthorized),
|
||||||
|
"No session found, please log in",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
uname := sess.Values["username"]
|
||||||
|
if uname == nil {
|
||||||
|
c.Logger().Debugf("%d - %s", http.StatusUnauthorized, "session expired or invalid")
|
||||||
|
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
http.StatusText(http.StatusUnauthorized)+": Log in again",
|
||||||
|
"Session expired, log in again.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("gorilla session", "username", sess.Values["username"].(string))
|
||||||
|
|
||||||
|
username := sess.Values["username"].(string)
|
||||||
|
|
||||||
|
var u moduser.User
|
||||||
|
|
||||||
|
ctx := context.WithValue(context.Background(), moduser.CtxKey{}, slogger)
|
||||||
|
if usr, err := moduser.QueryUser(ctx, client, username); err == nil && usr != nil {
|
||||||
|
u.ID = usr.ID
|
||||||
|
u.Username = usr.Username
|
||||||
|
u.IsAdmin = usr.IsAdmin
|
||||||
|
u.CreatedAt = usr.CreatedAt
|
||||||
|
u.IsActive = usr.IsActive
|
||||||
|
u.IsLoggedIn = true
|
||||||
|
} else {
|
||||||
|
c.Logger().Error(http.StatusText(http.StatusInternalServerError) + " - " + err.Error())
|
||||||
|
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
http.StatusText(http.StatusInternalServerError),
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !u.IsAdmin {
|
||||||
|
c.Logger().Debug("this is a restricted endpoint")
|
||||||
|
|
||||||
|
status := http.StatusUnauthorized
|
||||||
|
msg := http.StatusText(status)
|
||||||
|
|
||||||
|
return renderErrorPage(
|
||||||
|
c, status, msg+": You should not be here", "Restricted endpoint",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
uc := new(userCreate)
|
||||||
|
|
||||||
|
if err := c.Bind(uc); err != nil {
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
http.StatusText(http.StatusBadRequest),
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if uc.Username == "" || uc.Password == "" || uc.RepeatPassword == "" || uc.Password != uc.RepeatPassword {
|
||||||
|
c.Logger().Error("username or password not set, returning to /manage/users/new")
|
||||||
|
|
||||||
|
msg := "Error: both username and password need to be set"
|
||||||
|
|
||||||
|
if uc.Password != uc.RepeatPassword {
|
||||||
|
msg += "; password needs to be passed the same twice"
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[string]any)
|
||||||
|
|
||||||
|
data["flash"] = msg
|
||||||
|
data["form"] = uc
|
||||||
|
|
||||||
|
return c.Render(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"manage/user-new.tmpl",
|
||||||
|
page{
|
||||||
|
AppName: setting.AppName(),
|
||||||
|
AppVer: appver,
|
||||||
|
Title: "Manage Users - New User",
|
||||||
|
DevelMode: setting.IsDevel(),
|
||||||
|
Current: "manage-user-new",
|
||||||
|
Data: data,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg string
|
||||||
|
|
||||||
|
usr, err := moduser.CreateUser(ctx, client, uc.Email, uc.Username, uc.Password, uc.IsAdmin)
|
||||||
|
if err == nil && usr != nil {
|
||||||
|
msg = "created user '" + usr.Username + "'!"
|
||||||
|
|
||||||
|
sess.Values["flash"] = msg
|
||||||
|
_ = sess.Save(c.Request(), c.Response())
|
||||||
|
|
||||||
|
return c.Redirect(http.StatusSeeOther, "/manage/users")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ent.IsNotSingular(err) {
|
||||||
|
c.Logger().Error("user exists")
|
||||||
|
|
||||||
|
msg = "Error: user already exists: " + err.Error()
|
||||||
|
} else {
|
||||||
|
msg = "Error: " + err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[string]any)
|
||||||
|
|
||||||
|
data["flash"] = msg
|
||||||
|
data["form"] = uc
|
||||||
|
|
||||||
|
return c.Render(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"manage/user-new.tmpl",
|
||||||
|
page{
|
||||||
|
AppName: setting.AppName(),
|
||||||
|
AppVer: appver,
|
||||||
|
Title: "Manage Users",
|
||||||
|
DevelMode: setting.IsDevel(),
|
||||||
|
Current: "manage-user",
|
||||||
|
Data: data,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,3 +13,12 @@ type userSignup struct {
|
|||||||
Email string `form:"email" json:"email"`
|
Email string `form:"email" json:"email"`
|
||||||
Password string `form:"password" json:"password"`
|
Password string `form:"password" json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type userCreate struct {
|
||||||
|
Username string `form:"username" json:"username" validate:"required,username"`
|
||||||
|
Email string `form:"email" json:"email" validate:"required,email"`
|
||||||
|
Password string `form:"password" json:"password" validate:"required,password"`
|
||||||
|
RepeatPassword string `form:"repeatPassword" json:"repeatPassword" validate:"required,repeatPassword"`
|
||||||
|
IsAdmin bool `form:"isAdmin" json:"isAdmin" validate:"required,isAdmin"`
|
||||||
|
IsActive *bool `form:"isActive" json:"isActive" validate:"omitempty,isActive"`
|
||||||
|
}
|
||||||
|
88
templates/manage/user-new.tmpl
Normal file
88
templates/manage/user-new.tmpl
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
{{ template "head.tmpl" . }}
|
||||||
|
<body class="min-h-screen bg-white dark:bg-gray-900">
|
||||||
|
{{ template "navbar.tmpl" . }}
|
||||||
|
<main class="grow min-w-[300px]">
|
||||||
|
<section class="bg-white dark:bg-gray-900">
|
||||||
|
<div class="container mx-auto md:mx-4 px-6 sm:px-0:py-0 md:py-16 lg:py-32">
|
||||||
|
<div class="lg:flex">
|
||||||
|
<div class="lg:w-1/2">
|
||||||
|
<h1 class="mt-4 text-2xl font-medium text-gray-800 capitalize lg:text-3xl dark:text-white">
|
||||||
|
Create new user
|
||||||
|
</h1>
|
||||||
|
{{ if and .Data .Data.flash }}
|
||||||
|
<h2 class="text-xl text-pink-600 dark:text-pink-500 capitalize py-2">
|
||||||
|
{{- .Data.flash -}}
|
||||||
|
</h2>
|
||||||
|
{{- end }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-8 lg:w-1/2 lg:mt-0">
|
||||||
|
<form method="post" action="/manage/users/create" class="w-full lg:max-w-xl">
|
||||||
|
<input type="hidden" name="_csrf" value="{{- .CSRF -}}">
|
||||||
|
<div class="relative flex items-center">
|
||||||
|
<span class="absolute" role="img" aria-label="person outline icon for username">
|
||||||
|
{{ template "svg-user.tmpl" }}
|
||||||
|
</span>
|
||||||
|
<input name="username" type="text" {{if and .Data.form .Data.form.Username}}value="{{.Data.form.Username}}"{{end}} placeholder="Username" required class="block w-full py-3 valid:border-green-300 required:border-blue-300 text-gray-700 bg-white border rounded-lg px-11 dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-blue-300 focus:outline-none focus:ring focus:ring-opacity-40">
|
||||||
|
</div>
|
||||||
|
<div class="relative flex items-center mt-4">
|
||||||
|
<span class="absolute" role="img" aria-label="email icon">
|
||||||
|
{{ template "svg-email.tmpl" }}
|
||||||
|
</span>
|
||||||
|
<input name="email" type="email" {{if and .Data.form .Data.form.Email}}value="{{.Data.form.Email}}"{{end}} placeholder="Email address" required class="peer block w-full px-10 py-3 required:border-blue-300 text-gray-700 bg-white border rounded-lg dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-blue-300 focus:outline-none focus:ring focus:ring-opacity-40">
|
||||||
|
<p class="mt-2 mx-4 peer-placeholder-shown:collapse peer-invalid:block text-pink-600 text-sm">
|
||||||
|
Please provide a valid email address.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex items-center mt-4">
|
||||||
|
<span class="absolute" role="img" aria-label="password lock icon">
|
||||||
|
{{ template "svg-password.tmpl" }}
|
||||||
|
</span>
|
||||||
|
<input name="password" type="password" {{if and .Data.form .Data.form.Password}}value="{{.Data.form.Password}}"{{end}} placeholder="Password" required class="block w-full px-10 py-3 required:border-blue-300 text-gray-700 bg-white border rounded-lg dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-blue-300 focus:outline-none focus:ring focus:ring-opacity-40">
|
||||||
|
</div>
|
||||||
|
<div class="relative flex items-center mt-4">
|
||||||
|
<span class="absolute" role="img" aria-label="password lock icon">
|
||||||
|
{{ template "svg-password.tmpl" }}
|
||||||
|
</span>
|
||||||
|
<input name="repeatPassword" type="password" {{if and .Data.form .Data.form.RepeatPassword}}value="{{.Data.form.RepeatPassword}}"{{end}} placeholder="Repeat Password" required class="block w-full px-10 py-3 required:border-blue-300 text-gray-700 bg-white border rounded-lg dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 focus:border-blue-400 dark:focus:border-blue-300 focus:ring-blue-300 focus:outline-none focus:ring focus:ring-opacity-40">
|
||||||
|
</div>
|
||||||
|
<div class="flex pt-2 px-2 items-center justify-center gap-6">
|
||||||
|
<div class="mb-1 block min-h-3">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
value="true"
|
||||||
|
id="isAdmin"
|
||||||
|
name="isAdmin"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
class="inline-block pl-1 hover:cursor-pointer text-gray-700 dark:text-gray-300"
|
||||||
|
for="isAdmin">
|
||||||
|
Is admin?
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="mb-1 block min-h-3">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
value="true"
|
||||||
|
id="isActive"
|
||||||
|
name="isActive"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
class="inline-block pl-1 hover:cursor-pointer text-gray-700 dark:text-gray-300"
|
||||||
|
for="isActive">
|
||||||
|
Is active?
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8 md:flex md:items-center">
|
||||||
|
<button class="w-full px-6 py-3 text-sm font-medium tracking-wide text-white capitalize transition-colors duration-300 transform bg-blue-500 rounded-lg md:w-1/2 hover:bg-blue-400 focus:outline-none focus:ring focus:ring-blue-300 focus:ring-opacity-50">
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{{ template "footer.tmpl" . }}
|
@ -3,22 +3,27 @@
|
|||||||
{{ template "navbar.tmpl" . }}
|
{{ template "navbar.tmpl" . }}
|
||||||
<main class="grow">
|
<main class="grow">
|
||||||
<div class="container mx-auto place-items-center px-8">
|
<div class="container mx-auto place-items-center px-8">
|
||||||
|
{{ if and .Data .Data.flash }}
|
||||||
|
<h1 class="text-xl text-pink-600 dark:text-pink-500 capitalize py-2">
|
||||||
|
{{ .Data.flash }}
|
||||||
|
</h1>
|
||||||
|
{{- end }}
|
||||||
<div class="flex justify-between place-items-center">
|
<div class="flex justify-between place-items-center">
|
||||||
<h1 class="text-xl text-pink-600 dark:text-pink-500 capitalize py-2">
|
<h1 class="text-xl text-pink-600 dark:text-pink-500 capitalize py-2">
|
||||||
Manage users
|
Manage users
|
||||||
</h1>
|
</h1>
|
||||||
<a href="/manage/users/new" class="w-auto py-1 mt-2 text-center text-blue-500 md:mt-0 md:mx-6 lg:mx-auto hover:underline dark:text-blue-400">
|
<a href="/manage/users/new" class="w-auto py-1 mt-2 text-center text-blue-500 md:mt-0 md:mx-6 lg:mx-4 hover:underline dark:text-blue-400">
|
||||||
New user
|
New user
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2 mt-3 border-2 dark:border-slate-500 rounded-sm">
|
<div class="p-2 mt-3 border-2 dark:border-slate-500 rounded-sm overflow-x-auto">
|
||||||
<table class="text-center text-gray-500 dark:text-gray-300 w-full">
|
<table class="text-center text-gray-500 dark:text-gray-300 w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="p-2 text-slate-600 dark:text-slate-400">Username</th>
|
<th scope="col" class="md:p-2 sm:p-0 text-slate-600 dark:text-slate-400">Username</th>
|
||||||
<th scope="col" class="p-2 text-left text-slate-600 dark:text-slate-400">Email</th>
|
<th scope="col" class="md:p-2 sm:p-0 text-left text-slate-600 dark:text-slate-400">Email</th>
|
||||||
<th scope="col" class="p-2 text-slate-600 dark:text-slate-400">Admin</th>
|
<th scope="col" class="md:p-2 sm:p-0 sm:px-1 text-slate-600 dark:text-slate-400">Admin</th>
|
||||||
<th scope="col" class="p-2 text-slate-600 dark:text-slate-400">Active</th>
|
<th scope="col" class="md:p-2 sm:p-0 text-slate-600 dark:text-slate-400">Active</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
Loading…
Reference in New Issue
Block a user