diff --git a/ritherdon-archive.asd b/ritherdon-archive.asd index a76b73f..2e43d0c 100644 --- a/ritherdon-archive.asd +++ b/ritherdon-archive.asd @@ -60,8 +60,9 @@ (:file "utils") (:file "auth") (:file "validation") - (:file "nera") ; (Name of Database) - ;; Caveman Files + (:file "nera") ; Database stuff + (:file "search") ; Meilisearch stuff + ;; Caveman Files (:file "view") (:file "web")) :description "The Nicola Ellis & Ritherdon Archive." diff --git a/src/search.lisp b/src/search.lisp new file mode 100644 index 0000000..a71de19 --- /dev/null +++ b/src/search.lisp @@ -0,0 +1,125 @@ +(defpackage #:search + (:use #:cl + #:app-constants + #:cl-json + #:local-time + #:utils) + (:import-from #:dexador + #:request) + (:export #:build-keywords-string + #:build-payload + #:delete-entry + #:find-entry + #:get-id + #:get-keywords + #:submit-entry + #:set-filter-attributes)) +(in-package #:search) + +;; Explains the "~{~A~^,~}" in the format call below. +;; https://stackoverflow.com/questions/8830888/whats-the-canonical-way-to-join-strings-in-a-list +(defun build-keywords-string (id) + "Gets the keywords for `ID' in meilisearch DB and formats into a string. +The string should look something like: 'art,blog post,testing,whatever'. One +thing to note is meilisearch uses the comma to separate and create tokens of the +string. So, the user can have spaces in their keywords but they cannot separate +the keywords with a space. 'art blog post' is classed as three tokens and +'art,blog post' is classes as two." + (format nil "~{~A~^,~}" (search:get-keywords (search:find-entry id)))) + +(defun build-payload (id-value title-value relative-path-value + thumbnail-path-value created-at-value keywords-value) + "Creates a JSON object which reflects the schema in the meilsearch database. +Note: The JSON object to encoded as a string." + (cl-json:encode-json-to-string + `((("id" . ,id-value) + ("title" . ,title-value) + ("relative-path" . ,relative-path-value) + ("thumbnail-path" . ,thumbnail-path-value) + ("year" . ,(local-time:timestamp-year created-at-value)) + ("month" . ,(local-time:timestamp-month created-at-value)) + ("day" . ,(local-time:timestamp-day created-at-value)) + ("keywords" . ,(cl-ppcre:split "," keywords-value)))))) + +(defun build-search-url (path) + "Constructs the URL to connect to the meilisearch instance (beta or prod.) +The function will check to see which environment the current instance of this +site is running in and use the beta or prod. URL's to connect to meilisearch. +`PATH' is the relative path which this function will concatenate onto the end of +the base URL." + (if (ritherdon-archive.config:developmentp) + (concatenate 'string "http://localhost:7700" path) + (concatenate 'string "https://www.nera.com" path))) + +(defun delete-entry (id) + "Deletes and entry from the meilisearch database based on its `ID'. +This does not affect this website's (nera) database -- only the meilisearch +one." + (dexador:request + (build-search-url + (format nil "/indexes/nera/documents/~a" id)); (get-id (find-entry id)))) + :method :delete + :use-connection-pool nil + :headers `(("Authorization" . ,(meilisearch-api-key))) + :verbose nil)) + + +(defun documents-total () + "Gets the total number of documents in the meilisearch database." + (rest + (second + (second + (assoc :indexes (cl-json:decode-json-from-string + (dexador:request (build-search-url "/stats") + :method :get + :use-connection-pool nil + :headers `(("Content-Type" . "application/json") + ("Authorization" . ,(meilisearch-api-key))) + :verbose nil))))))) + +(defun find-entry (title) + "Finds the entry in the meilisearch database by its `TITLE'." + (cl-json:decode-json-from-string + (dexador:request + (build-search-url "/indexes/nera/search") + :method :post + :use-connection-pool nil + :headers `(("Content-Type" . "application/json") + ("Authorization" . ,(meilisearch-api-key))) + :content (format nil "{ \"q\": \"~a\", \"limit\": 1 }" title) + :verbose nil))) + +(defun get-id (payload) + "Gets the id from the JSON `PAYLOAD', make sure limit is set to 1." + (rest (third (second (first payload))))) + +(defun get-keywords (payload) + "Get the keywords from the JSON `PAYLOAD'." + (rest (first (last (last (second (first payload))))))) + +(defun meilisearch-api-key () + "Returns either the beta or prod. API key for meilisearch. +The API key is determined based on which environment this website is +currently running in." + (if (ritherdon-archive.config:developmentp) + "Bearer meilisearch-beta-key" + "Bearer meilisearch-production-key-nera")) + +(defun submit-entry (payload) + "Adds a new article to the meilisearch database." + (dexador:request ;;"http://127.0.0.1:7700/indexes/nera/documents" + (build-search-url "/indexes/nera/documents") + :method :post + :use-connection-pool nil + :headers `(("Content-Type" . "application/json") + ("Authorization" . ,(meilisearch-api-key))) + :content payload + :verbose nil)) + +(defun set-filter-attributes () + "Sets the filter attributes in the Meilisearch database. +These values are hard-coded into the system because they are based on what Nic +has requested. She would like the filtering to consist on years and months." + (utils:run-bash-command + (format nil "curl -X PATCH \'~a\' -H \'Authorization: ~a\' -H \'Content-Type: application/json\' --data-binary \'{ \"filterableAttributes\": [ \"year\", \"month\", \"keywords\" ]}\'" + (build-search-url "/indexes/nera/settings") (meilisearch-api-key))))