This commit is contained in:
parent
9972551239
commit
97ea29d043
@ -47,6 +47,9 @@ func (a *App) SetupRoutes() {
|
|||||||
e.GET("/signup", handlers.Signup())
|
e.GET("/signup", handlers.Signup())
|
||||||
e.POST("/signup", handlers.SignupPost(a.db))
|
e.POST("/signup", handlers.SignupPost(a.db))
|
||||||
e.GET("/home", handlers.Home(a.db))
|
e.GET("/home", handlers.Home(a.db))
|
||||||
|
|
||||||
|
e.GET("/manage/users", handlers.ManageUsers(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
2
bs.js
2
bs.js
@ -22,7 +22,7 @@ module.exports = {
|
|||||||
"port": 3003
|
"port": 3003
|
||||||
},
|
},
|
||||||
/* "files": true, */
|
/* "files": true, */
|
||||||
"files": ["templates/*.tmpl", "assets/input/css/*.css", "assets/public/js/*.js", "assets/public/css/*.css"],
|
"files": ["templates/*.tmpl", "templates/*/*.tmpl", "assets/input/css/*.css", "assets/public/js/*.js", "assets/public/css/*.css"],
|
||||||
"watchEvents": [
|
"watchEvents": [
|
||||||
"change"
|
"change"
|
||||||
],
|
],
|
||||||
|
136
handlers/manage-user.go
Normal file
136
handlers/manage-user.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Copyright 2023 wanderer <a_mirre at utb dot cz>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.dotya.ml/mirre-mt/pcmt/ent"
|
||||||
|
moduser "git.dotya.ml/mirre-mt/pcmt/modules/user"
|
||||||
|
"github.com/labstack/echo-contrib/session"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ManageUsers(client *ent.Client) echo.HandlerFunc {
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var allUsers []*moduser.User
|
||||||
|
|
||||||
|
if users, err := moduser.ListAll(ctx, client); err == nil && users != nil {
|
||||||
|
for _, u := range users {
|
||||||
|
usr := &moduser.User{
|
||||||
|
Username: u.Username,
|
||||||
|
Email: u.Email,
|
||||||
|
ID: u.ID,
|
||||||
|
IsActive: u.IsActive,
|
||||||
|
IsAdmin: u.IsAdmin,
|
||||||
|
CreatedAt: u.CreatedAt,
|
||||||
|
UpdatedAt: u.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
allUsers = append(allUsers, usr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.Logger().Error(http.StatusText(http.StatusInternalServerError) + " - " + err.Error())
|
||||||
|
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
http.StatusText(http.StatusInternalServerError),
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[string]any)
|
||||||
|
|
||||||
|
data["allusers"] = allUsers
|
||||||
|
|
||||||
|
err := c.Render(
|
||||||
|
http.StatusOK,
|
||||||
|
"manage/user.tmpl",
|
||||||
|
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 err != nil {
|
||||||
|
return renderErrorPage(
|
||||||
|
c,
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
http.StatusText(http.StatusInternalServerError),
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
@ -17,4 +17,5 @@ type page struct {
|
|||||||
Error string
|
Error string
|
||||||
Status string
|
Status string
|
||||||
StatusText string
|
StatusText string
|
||||||
|
Data map[string]any
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
type User struct {
|
type User struct {
|
||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
Username string
|
Username string
|
||||||
|
Email string
|
||||||
IsActive bool
|
IsActive bool
|
||||||
IsAdmin bool
|
IsAdmin bool
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
@ -224,6 +225,40 @@ func EmailExists(ctx context.Context, client *ent.Client, email string) (bool, e
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ListAll(ctx context.Context, client *ent.Client) ([]*ent.User, error) {
|
||||||
|
users, err := client.User.
|
||||||
|
Query().All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListAllRegular(ctx context.Context, client *ent.Client) ([]*ent.User, error) {
|
||||||
|
users, err := client.User.
|
||||||
|
Query().
|
||||||
|
Where(user.IsAdminEQ(false)).
|
||||||
|
All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListAllAdmins(ctx context.Context, client *ent.Client) ([]*ent.User, error) {
|
||||||
|
admins, err := client.User.
|
||||||
|
Query().
|
||||||
|
Where(user.IsAdminEQ(true)).
|
||||||
|
All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return admins, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NoUsers checks whether there are any users at all in the db.
|
// NoUsers checks whether there are any users at all in the db.
|
||||||
func NoUsers(ctx context.Context, client *ent.Client) (bool, error) {
|
func NoUsers(ctx context.Context, client *ent.Client) (bool, error) {
|
||||||
count, err := client.User.
|
count, err := client.User.
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./templates/**.{tmpl,html}"],
|
content: ["./templates/**.{tmpl,html}", "./templates/**/**.tmpl"],
|
||||||
/* darkMode: 'class', */
|
/* darkMode: 'class', */
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
|
55
templates/manage/user.tmpl
Normal file
55
templates/manage/user.tmpl
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{{ template "head.tmpl" . }}
|
||||||
|
<body class="h-screen bg-white dark:bg-gray-900">
|
||||||
|
{{ template "navbar.tmpl" . }}
|
||||||
|
<main class="grow">
|
||||||
|
<div class="container mx-auto place-items-center px-8">
|
||||||
|
<div class="flex justify-between place-items-center">
|
||||||
|
<h1 class="text-xl text-pink-600 dark:text-pink-500 capitalize py-2">
|
||||||
|
Manage users
|
||||||
|
</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">
|
||||||
|
New user
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="p-2 mt-3 border-2 dark:border-slate-500 rounded-sm">
|
||||||
|
<table class="text-center text-gray-500 dark:text-gray-300 w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="p-2 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="p-2 text-slate-600 dark:text-slate-400">Admin</th>
|
||||||
|
<th scope="col" class="p-2 text-slate-600 dark:text-slate-400">Active</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ $users := index .Data.allusers }}
|
||||||
|
{{ range $_, $u := $users }}
|
||||||
|
<tr class="border-t border-gray-300 dark:border-slate-600 hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||||
|
<td class="text-center text-ellipsis">
|
||||||
|
<span class="p-2 font-bold text-purple-500 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-500 hover:underline">
|
||||||
|
<a href="/manage/users/{{ $u.ID }}">{{ $u.Username }}</a>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-left max-w-1 text-ellipsis overflow-hidden whitespace-nowrap">
|
||||||
|
<span class="p-2 font-mono max-w-1 text-ellipsis truncate">
|
||||||
|
{{ $u.Email }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center text-slate-400">
|
||||||
|
<span class="p-2">
|
||||||
|
{{- if $u.IsAdmin -}}✓{{- else -}}✗{{- end -}}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center text-slate-400">
|
||||||
|
<span class="p-2">
|
||||||
|
{{- if $u.IsActive -}}✓{{- else -}}✗{{- end -}}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{{ template "footer.tmpl" . }}
|
@ -25,10 +25,10 @@
|
|||||||
</li>
|
</li>
|
||||||
{{ if and .User .User.IsLoggedIn }}
|
{{ if and .User .User.IsLoggedIn }}
|
||||||
<li>
|
<li>
|
||||||
{{ if pageIs .Current "admin-users" }}
|
{{ if pageIs .Current "manage-users" }}
|
||||||
<a href="/users" class="block py-2 pl-3 pr-4 text-white bg-blue-500 rounded md:bg-transparent md:text-blue-700 md:p-0 md:dark:text-blue-500 dark:bg-blue-500 md:dark:bg-transparent" aria-current="page">
|
<a href="/manage/users" class="block py-2 pl-3 pr-4 text-white bg-blue-500 rounded md:bg-transparent md:text-blue-700 md:p-0 md:dark:text-blue-500 dark:bg-blue-500 md:dark:bg-transparent" aria-current="page">
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a href="/users" class="block py-2 pl-3 pr-4 text-gray-900 rounded hover:bg-gray-300 md:hover:bg-transparent md:border-0 md:hover:text-blue-500 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">
|
<a href="/manage/users" class="block py-2 pl-3 pr-4 text-gray-900 rounded hover:bg-gray-300 md:hover:bg-transparent md:border-0 md:hover:text-blue-500 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">
|
||||||
{{ end }}
|
{{ end }}
|
||||||
User management
|
User management
|
||||||
</a>
|
</a>
|
||||||
|
Loading…
Reference in New Issue
Block a user