|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "net/http" |
| 5 | + "time" |
| 6 | +) |
| 7 | + |
| 8 | +// adminEmail is the only account allowed to view admin pages. Hard-coded |
| 9 | +// because there is exactly one operator and no plan to expand the role. |
| 10 | +const adminEmail = "james67@gmail.com" |
| 11 | + |
| 12 | +func isAdmin(u *currentUser) bool { |
| 13 | + return u != nil && u.Email == adminEmail |
| 14 | +} |
| 15 | + |
| 16 | +// requireAdmin layers on top of requireAuth: a non-admin authenticated user |
| 17 | +// gets a 404 (not 403) so the page is indistinguishable from a missing route. |
| 18 | +func requireAdmin(next http.Handler) http.Handler { |
| 19 | + return requireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 20 | + if !isAdmin(userFrom(r)) { |
| 21 | + handle404(w, r) |
| 22 | + return |
| 23 | + } |
| 24 | + next.ServeHTTP(w, r) |
| 25 | + })) |
| 26 | +} |
| 27 | + |
| 28 | +type viewAdminUserRow struct { |
| 29 | + ID int64 |
| 30 | + Email string |
| 31 | + Name string |
| 32 | + Created string |
| 33 | + LastLogin string |
| 34 | + WorkoutCount int64 |
| 35 | +} |
| 36 | + |
| 37 | +type viewAdminUsers struct { |
| 38 | + UserName string |
| 39 | + ThemeMode string |
| 40 | + Users []viewAdminUserRow |
| 41 | +} |
| 42 | + |
| 43 | +func handleAdminUsers(w http.ResponseWriter, r *http.Request) { |
| 44 | + user := userFrom(r) |
| 45 | + rows, err := queries.ListUsersForAdmin(r.Context()) |
| 46 | + if err != nil { |
| 47 | + serverError(w, "admin: list users", err) |
| 48 | + return |
| 49 | + } |
| 50 | + out := make([]viewAdminUserRow, 0, len(rows)) |
| 51 | + for _, u := range rows { |
| 52 | + out = append(out, viewAdminUserRow{ |
| 53 | + ID: u.ID, |
| 54 | + Email: u.Email, |
| 55 | + Name: u.Name, |
| 56 | + Created: formatAdminTimestamp(u.CreatedAt), |
| 57 | + LastLogin: formatAdminTimestamp(u.LastLoginAt), |
| 58 | + WorkoutCount: u.WorkoutCount, |
| 59 | + }) |
| 60 | + } |
| 61 | + renderHTML(w, "admin_users.html", viewAdminUsers{ |
| 62 | + UserName: user.Name, |
| 63 | + ThemeMode: themeFromRequest(r), |
| 64 | + Users: out, |
| 65 | + }) |
| 66 | +} |
| 67 | + |
| 68 | +// formatAdminTimestamp parses an RFC3339 string (created_at / last_login_at |
| 69 | +// are stored that way) and renders it in the configured app timezone. Falls |
| 70 | +// back to the raw value so a malformed row stays visible rather than blank. |
| 71 | +func formatAdminTimestamp(s string) string { |
| 72 | + t, err := time.Parse(time.RFC3339, s) |
| 73 | + if err != nil { |
| 74 | + return s |
| 75 | + } |
| 76 | + return t.In(appLocation).Format("2 Jan 2006 15:04") |
| 77 | +} |
0 commit comments