prompt 8
This commit is contained in:
parent
c60ff109a4
commit
70e34465de
|
|
@ -0,0 +1,11 @@
|
||||||
|
Implementa area admin.
|
||||||
|
|
||||||
|
Routes protette (RequireAuth + RequireAdmin):
|
||||||
|
- GET /admin -> admin/dashboard.html
|
||||||
|
- GET /admin/users -> pagina elenco utenti (server-rendered semplice)
|
||||||
|
|
||||||
|
Templates:
|
||||||
|
- admin/dashboard.html
|
||||||
|
- admin/users.html
|
||||||
|
|
||||||
|
Navbar nel layout deve mostrare link Admin solo se role=admin.
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
|
||||||
|
"trustcontact/internal/services"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdminController struct {
|
||||||
|
usersService *services.UsersService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminController(usersService *services.UsersService) *AdminController {
|
||||||
|
return &AdminController{usersService: usersService}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *AdminController) Dashboard(c *fiber.Ctx) error {
|
||||||
|
viewData := map[string]any{
|
||||||
|
"Title": "Admin Dashboard",
|
||||||
|
"NavSection": "admin",
|
||||||
|
}
|
||||||
|
for k, v := range localsTemplateData(c) {
|
||||||
|
viewData[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.ParseFiles(
|
||||||
|
"web/templates/layout.html",
|
||||||
|
"web/templates/public/_flash.html",
|
||||||
|
"web/templates/admin/dashboard.html",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return executeLayout(c, tmpl, viewData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *AdminController) Users(c *fiber.Ctx) error {
|
||||||
|
pageData, err := ac.usersService.List(services.UsersQuery{
|
||||||
|
Q: c.Query("q"),
|
||||||
|
Sort: c.Query("sort", "id"),
|
||||||
|
Dir: c.Query("dir", "asc"),
|
||||||
|
Page: parseIntOrDefault(c.Query("page"), 1),
|
||||||
|
PageSize: parseIntOrDefault(c.Query("pageSize"), 20),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
viewData := map[string]any{
|
||||||
|
"Title": "Admin Users",
|
||||||
|
"NavSection": "admin",
|
||||||
|
"PageData": pageData,
|
||||||
|
}
|
||||||
|
for k, v := range localsTemplateData(c) {
|
||||||
|
viewData[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.ParseFiles(
|
||||||
|
"web/templates/layout.html",
|
||||||
|
"web/templates/public/_flash.html",
|
||||||
|
"web/templates/admin/users.html",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return executeLayout(c, tmpl, viewData)
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,9 @@ func RegisterRoutes(app *fiber.App, store *session.Store, database *gorm.DB, cfg
|
||||||
return fmt.Errorf("init auth service: %w", err)
|
return fmt.Errorf("init auth service: %w", err)
|
||||||
}
|
}
|
||||||
authController := controllers.NewAuthController(authService)
|
authController := controllers.NewAuthController(authService)
|
||||||
usersController := controllers.NewUsersController(services.NewUsersService(database))
|
usersService := services.NewUsersService(database)
|
||||||
|
usersController := controllers.NewUsersController(usersService)
|
||||||
|
adminController := controllers.NewAdminController(usersService)
|
||||||
|
|
||||||
app.Get("/healthz", func(c *fiber.Ctx) error {
|
app.Get("/healthz", func(c *fiber.Ctx) error {
|
||||||
return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
|
@ -50,9 +52,8 @@ func RegisterRoutes(app *fiber.App, store *session.Store, database *gorm.DB, cfg
|
||||||
})
|
})
|
||||||
|
|
||||||
admin := app.Group("/admin", httpmw.RequireAuth(), httpmw.RequireAdmin())
|
admin := app.Group("/admin", httpmw.RequireAuth(), httpmw.RequireAdmin())
|
||||||
admin.Get("/", func(c *fiber.Ctx) error {
|
admin.Get("/", adminController.Dashboard)
|
||||||
return c.SendString("admin area")
|
admin.Get("/users", adminController.Users)
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{{define "content"}}
|
||||||
|
<h1>Admin Dashboard</h1>
|
||||||
|
<p class="muted">Area amministrazione.</p>
|
||||||
|
<div class="row">
|
||||||
|
<a href="/admin/users">Gestione utenti</a>
|
||||||
|
<a href="/users">Vista utenti (private)</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
{{define "content"}}
|
||||||
|
<h1>Admin - Users</h1>
|
||||||
|
<p class="muted">Elenco utenti server-rendered.</p>
|
||||||
|
|
||||||
|
<form class="row" method="get" action="/admin/users">
|
||||||
|
<input type="text" name="q" placeholder="Cerca nome o email" value="{{.PageData.Q}}">
|
||||||
|
<select name="sort" style="padding:10px;border:1px solid #d1d5db;border-radius:8px;">
|
||||||
|
<option value="id" {{if eq .PageData.Sort "id"}}selected{{end}}>ID</option>
|
||||||
|
<option value="name" {{if eq .PageData.Sort "name"}}selected{{end}}>Name</option>
|
||||||
|
<option value="email" {{if eq .PageData.Sort "email"}}selected{{end}}>Email</option>
|
||||||
|
</select>
|
||||||
|
<select name="dir" style="padding:10px;border:1px solid #d1d5db;border-radius:8px;">
|
||||||
|
<option value="asc" {{if eq .PageData.Dir "asc"}}selected{{end}}>ASC</option>
|
||||||
|
<option value="desc" {{if eq .PageData.Dir "desc"}}selected{{end}}>DESC</option>
|
||||||
|
</select>
|
||||||
|
<input type="number" name="pageSize" min="1" max="100" value="{{.PageData.PageSize}}" style="max-width:120px;">
|
||||||
|
<input type="hidden" name="page" value="1">
|
||||||
|
<button type="submit">Filtra</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<table style="width:100%;border-collapse:collapse;margin-top:16px;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="text-align:left;border-bottom:1px solid #e5e7eb;padding:8px;">ID</th>
|
||||||
|
<th style="text-align:left;border-bottom:1px solid #e5e7eb;padding:8px;">Name</th>
|
||||||
|
<th style="text-align:left;border-bottom:1px solid #e5e7eb;padding:8px;">Email</th>
|
||||||
|
<th style="text-align:left;border-bottom:1px solid #e5e7eb;padding:8px;">Role</th>
|
||||||
|
<th style="text-align:left;border-bottom:1px solid #e5e7eb;padding:8px;">Verified</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .PageData.Users}}
|
||||||
|
<tr>
|
||||||
|
<td style="border-bottom:1px solid #f1f5f9;padding:8px;">{{.ID}}</td>
|
||||||
|
<td style="border-bottom:1px solid #f1f5f9;padding:8px;">{{if .Name}}{{.Name}}{{else}}-{{end}}</td>
|
||||||
|
<td style="border-bottom:1px solid #f1f5f9;padding:8px;">{{.Email}}</td>
|
||||||
|
<td style="border-bottom:1px solid #f1f5f9;padding:8px;">{{.Role}}</td>
|
||||||
|
<td style="border-bottom:1px solid #f1f5f9;padding:8px;">{{if .EmailVerified}}yes{{else}}no{{end}}</td>
|
||||||
|
</tr>
|
||||||
|
{{else}}
|
||||||
|
<tr><td colspan="5" style="padding:12px;">Nessun utente trovato.</td></tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="row" style="margin-top:12px;align-items:center;justify-content:space-between;">
|
||||||
|
<div class="muted">Totale: {{.PageData.Total}} utenti. Pagina {{.PageData.Page}}{{if gt .PageData.TotalPages 0}} / {{.PageData.TotalPages}}{{end}}</div>
|
||||||
|
<div class="row">
|
||||||
|
<a {{if not .PageData.HasPrev}}style="pointer-events:none;opacity:.5;"{{end}} href="/admin/users?q={{.PageData.Q}}&sort={{.PageData.Sort}}&dir={{.PageData.Dir}}&page={{.PageData.PrevPage}}&pageSize={{.PageData.PageSize}}">Prev</a>
|
||||||
|
<a {{if not .PageData.HasNext}}style="pointer-events:none;opacity:.5;"{{end}} href="/admin/users?q={{.PageData.Q}}&sort={{.PageData.Sort}}&dir={{.PageData.Dir}}&page={{.PageData.NextPage}}&pageSize={{.PageData.PageSize}}">Next</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
@ -23,7 +23,9 @@
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/" class="{{if eq .NavSection "public"}}active{{end}}">Public</a>
|
<a href="/" class="{{if eq .NavSection "public"}}active{{end}}">Public</a>
|
||||||
<a href="/private" class="{{if eq .NavSection "private"}}active{{end}}">Private</a>
|
<a href="/private" class="{{if eq .NavSection "private"}}active{{end}}">Private</a>
|
||||||
|
{{if and .CurrentUser (eq .CurrentUser.Role "admin")}}
|
||||||
<a href="/admin" class="{{if eq .NavSection "admin"}}active{{end}}">Admin</a>
|
<a href="/admin" class="{{if eq .NavSection "admin"}}active{{end}}">Admin</a>
|
||||||
|
{{end}}
|
||||||
{{if .CurrentUser}}
|
{{if .CurrentUser}}
|
||||||
<form action="/logout" method="post" style="margin-left:auto;">
|
<form action="/logout" method="post" style="margin-left:auto;">
|
||||||
<button type="submit">Logout</button>
|
<button type="submit">Logout</button>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue