asteroid/parenscript/users.lisp

222 lines
11 KiB
Common Lisp

;;;; users.lisp - ParenScript version of users.js
;;;; User management page for admins
(in-package #:asteroid)
(defparameter *users-js*
(ps:ps*
'(progn
;; Load user stats
(defun load-user-stats ()
(ps:chain
(fetch "/api/asteroid/user-stats")
(then (lambda (response) (ps:chain response (json))))
(then (lambda (result)
(let ((data (or (ps:@ result data) result)))
(when (and (= (ps:@ data status) "success") (ps:@ data stats))
(let ((stats (ps:@ data stats)))
(setf (ps:@ (ps:chain document (get-element-by-id "total-users")) text-content)
(or (ps:getprop stats "total-users") 0))
(setf (ps:@ (ps:chain document (get-element-by-id "active-users")) text-content)
(or (ps:getprop stats "active-users") 0))
(setf (ps:@ (ps:chain document (get-element-by-id "admin-users")) text-content)
(or (ps:getprop stats "admins") 0))
(setf (ps:@ (ps:chain document (get-element-by-id "dj-users")) text-content)
(or (ps:getprop stats "djs") 0)))))))
(catch (lambda (error)
(ps:chain console (error "Error loading user stats:" error))))))
;; Load users list
(defun load-users ()
(ps:chain
(fetch "/api/asteroid/users")
(then (lambda (response) (ps:chain response (json))))
(then (lambda (result)
(let ((data (or (ps:@ result data) result)))
(when (= (ps:@ data status) "success")
(show-users-table (ps:@ data users))
(setf (ps:@ (ps:chain document (get-element-by-id "users-list-section")) style display) "block")))))
(catch (lambda (error)
(ps:chain console (error "Error loading users:" error))
(alert "Error loading users. Please try again.")))))
;; Show users table
(defun show-users-table (users)
(let ((container (ps:chain document (get-element-by-id "users-container")))
(users-html (ps:chain users
(map (lambda (user)
(+ "<tr>"
"<td>" (ps:@ user username) "</td>"
"<td>" (ps:@ user email) "</td>"
"<td>"
"<select onchange=\"updateUserRole('" (ps:@ user id) "', this.value)\">"
"<option value=\"listener\" " (if (= (ps:@ user role) "listener") "selected" "") ">Listener</option>"
"<option value=\"dj\" " (if (= (ps:@ user role) "dj") "selected" "") ">DJ</option>"
"<option value=\"admin\" " (if (= (ps:@ user role) "admin") "selected" "") ">Admin</option>"
"</select>"
"</td>"
"<td>" (if (ps:@ user active) "✅ Active" "❌ Inactive") "</td>"
"<td>" (if (ps:getprop user "last-login")
(ps:chain (ps:new (-date (* (ps:getprop user "last-login") 1000))) (to-locale-string))
"Never") "</td>"
"<td class=\"user-actions\">"
(if (ps:@ user active)
(+ "<button class=\"btn btn-danger\" onclick=\"deactivateUser('" (ps:@ user id) "')\">Deactivate</button>")
(+ "<button class=\"btn btn-success\" onclick=\"activateUser('" (ps:@ user id) "')\">Activate</button>"))
"</td>"
"</tr>")))
(join ""))))
(setf (ps:@ container inner-h-t-m-l)
(+ "<table class=\"users-table\">"
"<thead>"
"<tr>"
"<th>Username</th>"
"<th>Email</th>"
"<th>Role</th>"
"<th>Status</th>"
"<th>Last Login</th>"
"<th>Actions</th>"
"</tr>"
"</thead>"
"<tbody>"
users-html
"</tbody>"
"</table>"
"<button class=\"btn btn-secondary\" onclick=\"hideUsersTable()\">Close</button>"))))
(defun hide-users-table ()
(setf (ps:@ (ps:chain document (get-element-by-id "users-list-section")) style display) "none"))
;; Update user role
(defun update-user-role (user-id new-role)
(let ((form-data (ps:new (-form-data))))
(ps:chain form-data (append "user-id" user-id))
(ps:chain form-data (append "role" new-role))
(ps:chain
(fetch "/api/asteroid/user/role"
(ps:create :method "POST" :body form-data))
(then (lambda (response) (ps:chain response (json))))
(then (lambda (result)
;; Handle Radiance API data wrapping
(let ((data (or (ps:@ result data) result)))
(if (= (ps:@ data status) "success")
(progn
(load-user-stats)
(alert (ps:@ data message)))
(alert (+ "Error updating user role: " (ps:@ data message)))))))
(catch (lambda (error)
(ps:chain console (error "Error updating user role:" error))
(alert "Error updating user role. Please try again."))))))
;; Deactivate user
(defun deactivate-user (user-id)
(when (not (confirm "Are you sure you want to deactivate this user?"))
(return))
(let ((form-data (ps:new (-form-data))))
(ps:chain form-data (append "user-id" user-id))
(ps:chain form-data (append "active" 0))
(ps:chain
(fetch "/api/asteroid/user/activate"
(ps:create :method "POST" :body form-data))
(then (lambda (response) (ps:chain response (json))))
(then (lambda (result)
;; Handle Radiance API data wrapping
(let ((data (or (ps:@ result data) result)))
(if (= (ps:@ data status) "success")
(progn
(load-users)
(load-user-stats)
(alert (ps:@ data message)))
(alert (+ "Error deactivating user: " (ps:@ data message)))))))
(catch (lambda (error)
(ps:chain console (error "Error deactivating user:" error))
(alert "Error deactivating user. Please try again."))))))
;; Activate user
(defun activate-user (user-id)
(when (not (confirm "Are you sure you want to activate this user?"))
(return))
(let ((form-data (ps:new (-form-data))))
(ps:chain form-data (append "user-id" user-id))
(ps:chain form-data (append "active" 1))
(ps:chain
(fetch "/api/asteroid/user/activate"
(ps:create :method "POST" :body form-data))
(then (lambda (response) (ps:chain response (json))))
(then (lambda (result)
;; Handle Radiance API data wrapping
(let ((data (or (ps:@ result data) result)))
(if (= (ps:@ data status) "success")
(progn
(load-users)
(load-user-stats)
(alert (ps:@ data message)))
(alert (+ "Error activating user: " (ps:@ data message)))))))
(catch (lambda (error)
(ps:chain console (error "Error activating user:" error))
(alert "Error activating user. Please try again."))))))
;; Toggle create user form
(defun toggle-create-user-form ()
(let ((form (ps:chain document (get-element-by-id "create-user-form"))))
(if (= (ps:@ form style display) "none")
(progn
(setf (ps:@ form style display) "block")
(setf (ps:@ (ps:chain document (get-element-by-id "new-username")) value) "")
(setf (ps:@ (ps:chain document (get-element-by-id "new-email")) value) "")
(setf (ps:@ (ps:chain document (get-element-by-id "new-password")) value) "")
(setf (ps:@ (ps:chain document (get-element-by-id "new-role")) value) "listener"))
(setf (ps:@ form style display) "none"))))
;; Create new user
(defun create-new-user (event)
(ps:chain event (prevent-default))
(let ((username (ps:@ (ps:chain document (get-element-by-id "new-username")) value))
(email (ps:@ (ps:chain document (get-element-by-id "new-email")) value))
(password (ps:@ (ps:chain document (get-element-by-id "new-password")) value))
(role (ps:@ (ps:chain document (get-element-by-id "new-role")) value))
(form-data (ps:new (-form-data))))
(ps:chain form-data (append "username" username))
(ps:chain form-data (append "email" email))
(ps:chain form-data (append "password" password))
(ps:chain form-data (append "role" role))
(ps:chain
(fetch "/api/asteroid/users/create"
(ps:create :method "POST" :body form-data))
(then (lambda (response) (ps:chain response (json))))
(then (lambda (result)
(let ((data (or (ps:@ result data) result)))
(if (= (ps:@ data status) "success")
(progn
(alert (+ "User \"" username "\" created successfully!"))
(toggle-create-user-form)
(load-user-stats)
(load-users))
(alert (+ "Error creating user: " (or (ps:@ data message) (ps:@ result message))))))))
(catch (lambda (error)
(ps:chain console (error "Error creating user:" error))
(alert "Error creating user. Please try again."))))))
;; Initialize on page load
(ps:chain document
(add-event-listener
"DOMContentLoaded"
load-user-stats))
;; Update user stats every 30 seconds
(set-interval load-user-stats 30000)))
"Compiled JavaScript for users management - generated at load time")
(defun generate-users-js ()
"Return the pre-compiled JavaScript for users page"
*users-js*)