diff --git a/data/dk.db b/data/dk.db index f795a41..82f9fa8 100644 Binary files a/data/dk.db and b/data/dk.db differ diff --git a/internal/routes/admin.go b/internal/routes/admin.go index 6225a38..74c57f4 100644 --- a/internal/routes/admin.go +++ b/internal/routes/admin.go @@ -2,14 +2,19 @@ package routes import ( "dk/internal/components" + "dk/internal/database" + "dk/internal/helpers" "dk/internal/models/news" "dk/internal/models/users" + "fmt" "runtime" + "strconv" "strings" "time" sushi "git.sharkk.net/Sharkk/Sushi" "git.sharkk.net/Sharkk/Sushi/auth" + "git.sharkk.net/Sharkk/Sushi/password" ) func RegisterAdminRoutes(app *sushi.App) { @@ -26,6 +31,9 @@ func RegisterAdminRoutes(app *sushi.App) { group.Get("/", adminIndex) group.Get("/news", adminNewsForm) group.Post("/news", adminNewsCreate) + group.Get("/users", adminUsersIndex) + group.Get("/users/:id", adminUserEdit) + group.Post("/users/:id", adminUserUpdate) } func adminIndex(ctx sushi.Ctx) { @@ -78,6 +86,131 @@ func adminNewsCreate(ctx sushi.Ctx) { ctx.Redirect("/admin") } +func adminUsersIndex(ctx sushi.Ctx) { + pagination := helpers.Pagination{ + Page: max(int(ctx.QueryArgs().GetUintOrZero("page")), 1), + PerPage: 30, + } + + type UserData struct { + ID int + Username string + Email string + Level int + Auth int + ClassID int + ClassName string + HP int + MaxHP int + Registered int64 + LastOnline int64 + } + + var userList []*UserData + err := database.Select(&userList, ` + SELECT u.id, u.username, u.email, u.level, u.auth, u.class_id, + COALESCE(c.name, 'Unknown') as class_name, + u.hp, u.max_hp, u.registered, u.last_online + FROM users u + LEFT JOIN classes c ON u.class_id = c.id + ORDER BY u.id ASC + LIMIT %d OFFSET %d`, pagination.PerPage, pagination.Offset()) + + if err != nil { + fmt.Printf("Error getting user list for admin index: %s", err.Error()) + userList = make([]*UserData, 0) + } + + type CountResult struct{ Count int } + var result CountResult + database.Get(&result, "SELECT COUNT(*) as count FROM users") + pagination.Total = result.Count + + components.RenderAdminPage(ctx, "User Management", "admin/users/index.html", map[string]any{ + "users": userList, + "currentPage": pagination.Page, + "totalPages": pagination.TotalPages(), + "hasNext": pagination.HasNext(), + "hasPrev": pagination.HasPrev(), + }) +} + +func adminUserEdit(ctx sushi.Ctx) { + sess := ctx.GetCurrentSession() + id := ctx.Param("id").Int() + + user, err := users.Find(id) + if err != nil { + sess.SetFlash("error", fmt.Sprintf("User %d not found", id)) + ctx.Redirect("/admin/users") + return + } + + components.RenderAdminPage(ctx, fmt.Sprintf("Edit User: %s", user.Username), "admin/users/edit.html", map[string]any{ + "user": user, + }) +} + +func adminUserUpdate(ctx sushi.Ctx) { + sess := ctx.GetCurrentSession() + id := ctx.Param("id").Int() + + user, err := users.Find(id) + if err != nil { + sess.SetFlash("error", fmt.Sprintf("User %d not found", id)) + ctx.Redirect("/admin/users") + return + } + + // Update fields + username := strings.TrimSpace(ctx.Form("username").String()) + email := strings.TrimSpace(ctx.Form("email").String()) + level, _ := strconv.Atoi(ctx.Form("level").String()) + auth, _ := strconv.Atoi(ctx.Form("auth").String()) + hp, _ := strconv.Atoi(ctx.Form("hp").String()) + maxHP, _ := strconv.Atoi(ctx.Form("max_hp").String()) + newPassword := strings.TrimSpace(ctx.Form("new_password").String()) + + if username == "" || email == "" { + sess.SetFlash("error", "Username and email are required") + ctx.Redirect(fmt.Sprintf("/admin/users/%d", id)) + return + } + + user.Username = username + user.Email = email + user.Level = level + user.Auth = auth + user.HP = hp + user.MaxHP = maxHP + + if newPassword != "" { + user.Password = password.HashPassword(newPassword) + } + + fields := map[string]any{ + "username": user.Username, + "email": user.Email, + "level": user.Level, + "auth": user.Auth, + "hp": user.HP, + "max_hp": user.MaxHP, + } + + if newPassword != "" { + fields["password"] = user.Password + } + + if err := database.Update("users", fields, "id", id); err != nil { + sess.SetFlash("error", "Failed to update user") + ctx.Redirect(fmt.Sprintf("/admin/users/%d", id)) + return + } + + sess.SetFlash("success", fmt.Sprintf("User %s updated successfully", user.Username)) + ctx.Redirect("/admin/users") +} + func bToMb(b uint64) uint64 { return b / 1024 / 1024 } diff --git a/templates/admin/users/edit.html b/templates/admin/users/edit.html new file mode 100644 index 0000000..81c5847 --- /dev/null +++ b/templates/admin/users/edit.html @@ -0,0 +1,46 @@ +{include "admin/layout.html"} + +{block "content"} +
+ Total users: {#users} | Page {currentPage} of {totalPages} +
+ +{if #users > 0} +ID | +Username | +Level | +Class | +Auth | +HP | +Actions | +|
---|---|---|---|---|---|---|---|
{user.ID} | +{user.Username} | +{user.Email} | +{user.Level} | +{user.ClassName} | +{user.Auth} | +{user.HP}/{user.MaxHP} | ++ Edit + | +