Yu-Gi-Oh! Deck Building and Card Inventory Management web interface written in Common Lisp, utilizing HTMX.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

245 lines
9.7KB

  1. #|
  2. src/web/inventory.lisp
  3. Crystal Commerce-style Inventory Management User Interface and helpers.
  4. TODO pass over the results of (make-search-query) such that
  5. :total-qty is the aggregate of:
  6. (select 'ygo-cc-item (where (:= :item_id xxx)))
  7. (reduce #'+
  8. (mapcar (alexandria:compose #'read-from-string #'cc-sell-price-of)
  9. (retrieve-dao 'ygo-cc-item :item-id 48883)))
  10. (reduce #'+ (mapcar #'cc-qty-of (retrieve-dao 'ygo-cc-item :item-id 48883)))
  11. |#
  12. (in-package #:cl-deck-builder2.web)
  13. (defun render-results (&key (active "/inventory") (class 'cc-item) (params nil) (tpl #P"inventory/search-results.html"))
  14. "Mega helper function. Render function for anything that currently queries the databse. We search both PRODUCT-NAME and NAME, as well as querying a list of VARIANTs, with a LIMIT on the number of results and an OFFSET into those search results. You may also specify a DIRECTION, and SORT-BY options.
  15. TODO Major rewrite candidate right here.
  16. TODO Integrate new SEARCH-SESSION object after that's done."
  17. (let ((direction (or (query-param "direction" params) "desc"))
  18. (variant (or (query-param "variant" params)
  19. (find-dao 'variant-condition :name "Near Mint")))
  20. (variants (select-variant-condition))
  21. (limit (or (query-param "limit" params) "10"))
  22. (name (or
  23. (query-param "name" params)
  24. (query-param "product-name" params)))
  25. (offset (or (query-param "offset" params) "0"))
  26. (sort-by (or (query-param "sort-by" params) "id")))
  27. (handler-case
  28. (ratify:with-parsed-forms
  29. ((:integer limit)
  30. (:integer offset)
  31. (:string direction)
  32. (:string sort-by))
  33. ;; NAME can be blank, so...
  34. (let* ((filtered-cards (make-search-query class params))
  35. (length (make-count-query class params))
  36. (pages (generate-pages length offset limit)))
  37. (render-with-env tpl
  38. `(:active ,active
  39. :cards ,filtered-cards
  40. :direction ,direction
  41. :last-page ,(car (last pages))
  42. :length ,length
  43. :limit ,limit
  44. :name ,name
  45. :offset ,offset
  46. :opposite-direction ,(get-opposite-direction direction)
  47. :pages ,pages
  48. :search-params ,+search-params+
  49. :sort-by ,sort-by
  50. :total ,(count-dao class)
  51. :variant ,variant
  52. :variants ,variants))))
  53. (ratify:combined-error (e)
  54. (flash-error (format nil "~a/search.error => ~a~%" active e))))))
  55. ;; USE REINITIALIZE-INSTANCE to update the existing YGO-SET-ITEM from _PARSED
  56. (defun patch-card (card _parsed)
  57. (let ((set-id (query-param "set-id" card))
  58. (variant-id (query-param "variant-id" card)))
  59. (handler-case
  60. (ratify-parsing:with-parsed-forms
  61. ((:integer set-id)
  62. (:integer variant-id))
  63. (let ((found (find-or-create-instance 'ygo-set-item :item-id set-id :variant-id variant-id))
  64. (clean (filter-alist _parsed)))
  65. (v:info :inventory "PATCH-CARD: ~a" found)
  66. (if found
  67. ;; TODO redraw item based on context? Like patching from the inventory list vs patching from the editor?
  68. ;; (redirect (format nil "/inventory/~d/edit" (mito:object-id found)) 303)
  69. (progn
  70. (apply #'reinitialize-instance found (assoc-utils:alist-plist clean))
  71. (update-dao found)
  72. (_ "Save"))
  73. (progn
  74. (flash-error (format nil "Error udpating id ~d:~d" set-id variant-id))))))
  75. (ratify:combined-error (e)
  76. (flash-error e)))))
  77. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  78. ;; Inventory Viewer
  79. (defroute ("/inventory" :method :GET) (&key _parsed)
  80. "Inventory Viewer GET route. Will display index with search results."
  81. (v:info :inventory "GET /inventory ~a" _parsed)
  82. ;; (with-logged-in-user
  83. ;; (render-results :params _parsed :tpl #P"inventory/index.html"))
  84. (redirect "/cards"))
  85. #|
  86. (defroute ("/inventory" :method :POST) (&key _parsed)
  87. "Inventory Viewer search POST route. Will display index with search results."
  88. (v:info :inventory "POST /inventory ~a" _parsed)
  89. (with-logged-in-user
  90. (render-results :params _parsed :tpl #P"inventory/index.html")))
  91. (defroute ("/inventory/search" :method :POST) (&key _parsed)
  92. "Inventory Viewer search POST route. Will display index with search results."
  93. (v:info :inventory "POST /inventory/search ~a" _parsed)
  94. (with-logged-in-user
  95. (render-results :params _parsed)))
  96. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  97. ;; Inventory Item Editor
  98. ;;
  99. ;; TODO PUT?? POST?? GET??
  100. ;; New Item
  101. (defroute ("/inventory/new" :method :GET) ()
  102. "Display the page for creation of a new inventory item"
  103. (v:info :inventory "GET /inventory/new")
  104. (with-logged-in-user
  105. (render-with-env #P"inventory/new.html"
  106. `(:active "/inventory"
  107. :card ,(make-instance 'cc-item)))))
  108. (defroute ("/inventory/new" :method :POST) (&key _parsed)
  109. "POST method for the processing the information from page for creation of a new inventory item."
  110. (v:info :inventory "POST /inventory/new => ~a" _parsed)
  111. (with-logged-in-user
  112. (let ((clean (filter-alist _parsed)))
  113. (let ((new (apply #'cc-create (assoc-utils:alist-plist clean))))
  114. (when (mito:object-id new)
  115. (redirect (format nil "/inventory/~d/edit" (mito:object-id new))))))))
  116. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  117. ;; Inventory Item Importer
  118. (defroute ("/inventory/import" :method :GET) ()
  119. "Inventory Item Importer. TODO"
  120. (v:info :inventory "GET /inventory/import")
  121. (with-logged-in-user
  122. (render-with-env #P"inventory/import.html"
  123. `(:active "/inventory"))))
  124. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  125. ;; Seems like this one needs to be last. It has something to do with
  126. ;; the wildcard :ID parameter. Maybe we ought to add the /edit back?
  127. (defroute ("/inventory/:id/edit" :method :GET) (&key id)
  128. "Edit Route for Inventory Item.
  129. ARGUMENTS
  130. ID The ID of the CC-ITEM inventory item you wish to edit."
  131. (v:info :inventory "GET /inventory/~d/edit" id)
  132. (with-logged-in-user
  133. (handler-case
  134. (ratify-parsing:with-parsed-forms
  135. ((:integer id))
  136. (render-with-env #P"inventory/edit.html"
  137. `(:active "/inventory"
  138. :errors ,(flash-gethash :errors)
  139. :messages ,(flash-gethash :messages)
  140. :card ,(cc-select-by-id id))))
  141. (ratify:combined-error (e)
  142. (flash-error e)))))
  143. (defroute ("/inventory/:id/edit" :method :POST) (&key id _parsed)
  144. "CC-ITEM Update POST route. If the item exists, update it using REINITIALIZE-INSTANCE, after filtering _PARSED through FILTER-ALIST.
  145. TODO Differentiate between CC-ITEM and YGO-CC-ITEM?"
  146. (v:info :inventory "POST /inventory/~d/edit => ~a" id _parsed)
  147. (with-logged-in-user
  148. (handler-case
  149. (ratify-parsing:with-parsed-forms
  150. ((:integer id))
  151. (with-connection (db)
  152. (let ((found (mito:find-dao 'cc-item :id id))
  153. (clean (filter-alist _parsed)))
  154. (when found
  155. (apply #'reinitialize-instance found (assoc-utils:alist-plist clean))
  156. (mito:save-dao found)
  157. (redirect (format nil "/inventory/~d/edit" (mito:object-id found)) 303)))))
  158. (ratify:combined-error (e)
  159. (flash-error e)))))
  160. (defroute ("/inventory/:id/delete" :method :DELETE) (&key id)
  161. "DELETE an Inventory Item specified by ID."
  162. (v:info :inventory "DELETE /inventory/~d/delete" id)
  163. (with-logged-in-user
  164. (handler-case
  165. (ratify-parsing:with-parsed-forms
  166. ((:integer id))
  167. (cc-delete-by-id id))
  168. (ratify:combined-error (e)
  169. (flash-error e)))))
  170. |#
  171. (defroute ("/inventory/patch" :method :PATCH) (&key _parsed)
  172. "YGO-CC-ITEM PATCH route. This appears to be functionally identical to EDIT route for ID, but this is using YGO-CC-ITEM as class. The POST route appears to be used for CC-ITEM object."
  173. (v:info :inventory "PATCH /inventory/patch => ~a" _parsed)
  174. (with-logged-in-user
  175. (alexandria:if-let ((cards (query-param "cards" _parsed)))
  176. (dolist (card cards)
  177. (patch-card card _parsed))
  178. (patch-card _parsed _parsed))))
  179. ;; Huh? It works??
  180. (defroute ("/inventory/variants/:id" :method :GET) (&key id)
  181. "Display the VARIANTs for this inventory item using SELECT-YGO-CC-ITEM-VARIANTS."
  182. (v:info :inventory "GET /inventory/variants/~d" id)
  183. (with-logged-in-user
  184. (handler-case
  185. (ratify-parsing:with-parsed-forms
  186. ((:integer id))
  187. (render-with-env #P"inventory/variant-results.html"
  188. (list :active "/inventory"
  189. :id id
  190. :cards (select-ygo-cc-item-variants id))))
  191. ;; :cards ,filtered-cards
  192. ;; :variant ,variant
  193. ;; :variants ,variants
  194. ;; :direction ,direction
  195. ;; :length ,length
  196. ;; :total ,(count-dao class)
  197. ;; :limit ,limit
  198. ;; :name ,name
  199. ;; :offset ,offset
  200. ;; :opposite-direction ,(get-opposite-direction direction)
  201. ;; :pages ,(generate-pages length offset limit)
  202. ;; :search-params ,+search-params+
  203. ;; :sort-by ,sort-by)
  204. (ratify:combined-error (e)
  205. (flash-error e)))))