/ 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*)