/ parenscript / users.lisp
users.lisp
1 ;;;; users.lisp - ParenScript version of users.js 2 ;;;; User management page for admins 3 4 (in-package #:asteroid) 5 6 (defparameter *users-js* 7 (ps:ps* 8 '(progn 9 10 ;; Load user stats 11 (defun load-user-stats () 12 (ps:chain 13 (fetch "/api/asteroid/user-stats") 14 (then (lambda (response) (ps:chain response (json)))) 15 (then (lambda (result) 16 (let ((data (or (ps:@ result data) result))) 17 (when (and (= (ps:@ data status) "success") (ps:@ data stats)) 18 (let ((stats (ps:@ data stats))) 19 (setf (ps:@ (ps:chain document (get-element-by-id "total-users")) text-content) 20 (or (ps:getprop stats "total-users") 0)) 21 (setf (ps:@ (ps:chain document (get-element-by-id "active-users")) text-content) 22 (or (ps:getprop stats "active-users") 0)) 23 (setf (ps:@ (ps:chain document (get-element-by-id "admin-users")) text-content) 24 (or (ps:getprop stats "admins") 0)) 25 (setf (ps:@ (ps:chain document (get-element-by-id "dj-users")) text-content) 26 (or (ps:getprop stats "djs") 0))))))) 27 (catch (lambda (error) 28 (ps:chain console (error "Error loading user stats:" error)))))) 29 30 ;; Load users list 31 (defun load-users () 32 (ps:chain 33 (fetch "/api/asteroid/users") 34 (then (lambda (response) (ps:chain response (json)))) 35 (then (lambda (result) 36 (let ((data (or (ps:@ result data) result))) 37 (when (= (ps:@ data status) "success") 38 (show-users-table (ps:@ data users)) 39 (setf (ps:@ (ps:chain document (get-element-by-id "users-list-section")) style display) "block"))))) 40 (catch (lambda (error) 41 (ps:chain console (error "Error loading users:" error)) 42 (alert "Error loading users. Please try again."))))) 43 44 ;; Show users table 45 (defun show-users-table (users) 46 (let ((container (ps:chain document (get-element-by-id "users-container"))) 47 (users-html (ps:chain users 48 (map (lambda (user) 49 (+ "<tr>" 50 "<td>" (ps:@ user username) "</td>" 51 "<td>" (ps:@ user email) "</td>" 52 "<td>" 53 "<select onchange=\"updateUserRole('" (ps:@ user id) "', this.value)\">" 54 "<option value=\"listener\" " (if (= (ps:@ user role) "listener") "selected" "") ">Listener</option>" 55 "<option value=\"dj\" " (if (= (ps:@ user role) "dj") "selected" "") ">DJ</option>" 56 "<option value=\"admin\" " (if (= (ps:@ user role) "admin") "selected" "") ">Admin</option>" 57 "</select>" 58 "</td>" 59 "<td>" (if (ps:@ user active) "✅ Active" "❌ Inactive") "</td>" 60 "<td>" (if (ps:getprop user "last-login") 61 (ps:chain (ps:new (-date (* (ps:getprop user "last-login") 1000))) (to-locale-string)) 62 "Never") "</td>" 63 "<td class=\"user-actions\">" 64 (if (ps:@ user active) 65 (+ "<button class=\"btn btn-danger\" onclick=\"deactivateUser('" (ps:@ user id) "')\">Deactivate</button>") 66 (+ "<button class=\"btn btn-success\" onclick=\"activateUser('" (ps:@ user id) "')\">Activate</button>")) 67 "</td>" 68 "</tr>"))) 69 (join "")))) 70 (setf (ps:@ container inner-h-t-m-l) 71 (+ "<table class=\"users-table\">" 72 "<thead>" 73 "<tr>" 74 "<th>Username</th>" 75 "<th>Email</th>" 76 "<th>Role</th>" 77 "<th>Status</th>" 78 "<th>Last Login</th>" 79 "<th>Actions</th>" 80 "</tr>" 81 "</thead>" 82 "<tbody>" 83 users-html 84 "</tbody>" 85 "</table>" 86 "<button class=\"btn btn-secondary\" onclick=\"hideUsersTable()\">Close</button>")))) 87 88 (defun hide-users-table () 89 (setf (ps:@ (ps:chain document (get-element-by-id "users-list-section")) style display) "none")) 90 91 ;; Update user role 92 (defun update-user-role (user-id new-role) 93 (let ((form-data (ps:new (-form-data)))) 94 (ps:chain form-data (append "user-id" user-id)) 95 (ps:chain form-data (append "role" new-role)) 96 97 (ps:chain 98 (fetch "/api/asteroid/user/role" 99 (ps:create :method "POST" :body form-data)) 100 (then (lambda (response) (ps:chain response (json)))) 101 (then (lambda (result) 102 ;; Handle Radiance API data wrapping 103 (let ((data (or (ps:@ result data) result))) 104 (if (= (ps:@ data status) "success") 105 (progn 106 (load-user-stats) 107 (alert (ps:@ data message))) 108 (alert (+ "Error updating user role: " (ps:@ data message))))))) 109 (catch (lambda (error) 110 (ps:chain console (error "Error updating user role:" error)) 111 (alert "Error updating user role. Please try again.")))))) 112 113 ;; Deactivate user 114 (defun deactivate-user (user-id) 115 (when (not (confirm "Are you sure you want to deactivate this user?")) 116 (return)) 117 118 (let ((form-data (ps:new (-form-data)))) 119 (ps:chain form-data (append "user-id" user-id)) 120 (ps:chain form-data (append "active" 0)) 121 122 (ps:chain 123 (fetch "/api/asteroid/user/activate" 124 (ps:create :method "POST" :body form-data)) 125 (then (lambda (response) (ps:chain response (json)))) 126 (then (lambda (result) 127 ;; Handle Radiance API data wrapping 128 (let ((data (or (ps:@ result data) result))) 129 (if (= (ps:@ data status) "success") 130 (progn 131 (load-users) 132 (load-user-stats) 133 (alert (ps:@ data message))) 134 (alert (+ "Error deactivating user: " (ps:@ data message))))))) 135 (catch (lambda (error) 136 (ps:chain console (error "Error deactivating user:" error)) 137 (alert "Error deactivating user. Please try again.")))))) 138 139 ;; Activate user 140 (defun activate-user (user-id) 141 (when (not (confirm "Are you sure you want to activate this user?")) 142 (return)) 143 144 (let ((form-data (ps:new (-form-data)))) 145 (ps:chain form-data (append "user-id" user-id)) 146 (ps:chain form-data (append "active" 1)) 147 148 (ps:chain 149 (fetch "/api/asteroid/user/activate" 150 (ps:create :method "POST" :body form-data)) 151 (then (lambda (response) (ps:chain response (json)))) 152 (then (lambda (result) 153 ;; Handle Radiance API data wrapping 154 (let ((data (or (ps:@ result data) result))) 155 (if (= (ps:@ data status) "success") 156 (progn 157 (load-users) 158 (load-user-stats) 159 (alert (ps:@ data message))) 160 (alert (+ "Error activating user: " (ps:@ data message))))))) 161 (catch (lambda (error) 162 (ps:chain console (error "Error activating user:" error)) 163 (alert "Error activating user. Please try again.")))))) 164 165 ;; Toggle create user form 166 (defun toggle-create-user-form () 167 (let ((form (ps:chain document (get-element-by-id "create-user-form")))) 168 (if (= (ps:@ form style display) "none") 169 (progn 170 (setf (ps:@ form style display) "block") 171 (setf (ps:@ (ps:chain document (get-element-by-id "new-username")) value) "") 172 (setf (ps:@ (ps:chain document (get-element-by-id "new-email")) value) "") 173 (setf (ps:@ (ps:chain document (get-element-by-id "new-password")) value) "") 174 (setf (ps:@ (ps:chain document (get-element-by-id "new-role")) value) "listener")) 175 (setf (ps:@ form style display) "none")))) 176 177 ;; Create new user 178 (defun create-new-user (event) 179 (ps:chain event (prevent-default)) 180 181 (let ((username (ps:@ (ps:chain document (get-element-by-id "new-username")) value)) 182 (email (ps:@ (ps:chain document (get-element-by-id "new-email")) value)) 183 (password (ps:@ (ps:chain document (get-element-by-id "new-password")) value)) 184 (role (ps:@ (ps:chain document (get-element-by-id "new-role")) value)) 185 (form-data (ps:new (-form-data)))) 186 187 (ps:chain form-data (append "username" username)) 188 (ps:chain form-data (append "email" email)) 189 (ps:chain form-data (append "password" password)) 190 (ps:chain form-data (append "role" role)) 191 192 (ps:chain 193 (fetch "/api/asteroid/users/create" 194 (ps:create :method "POST" :body form-data)) 195 (then (lambda (response) (ps:chain response (json)))) 196 (then (lambda (result) 197 (let ((data (or (ps:@ result data) result))) 198 (if (= (ps:@ data status) "success") 199 (progn 200 (alert (+ "User \"" username "\" created successfully!")) 201 (toggle-create-user-form) 202 (load-user-stats) 203 (load-users)) 204 (alert (+ "Error creating user: " (or (ps:@ data message) (ps:@ result message)))))))) 205 (catch (lambda (error) 206 (ps:chain console (error "Error creating user:" error)) 207 (alert "Error creating user. Please try again.")))))) 208 209 ;; Initialize on page load 210 (ps:chain document 211 (add-event-listener 212 "DOMContentLoaded" 213 load-user-stats)) 214 215 ;; Update user stats every 30 seconds 216 (set-interval load-user-stats 30000))) 217 "Compiled JavaScript for users management - generated at load time") 218 219 (defun generate-users-js () 220 "Return the pre-compiled JavaScript for users page" 221 *users-js*)