Public archive for the Return to Ritherdon project.
https://www.nicolaellisandritherdon.com
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.
180 lines
7.5 KiB
180 lines
7.5 KiB
(defpackage #:search |
|
(:use #:cl |
|
#:app-constants |
|
#:archive |
|
#: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 |
|
#:delete-all-entries |
|
#:create-dump |
|
#:update-ranking-rules |
|
#:repopulate-database |
|
#:delete-index)) |
|
(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 publish-month-value publish-year-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" . ,publish-year-value) |
|
("month" . ,publish-month-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) |
|
(utils:build-url-root) |
|
(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)))) |
|
|
|
(defun delete-all-entries () |
|
"Deletes all the archive entries in the Meilisearch database -- not the DB." |
|
(utils:run-bash-command |
|
(format nil "curl -X DELETE \'~a\'" |
|
(build-search-url "/indexes/nera/documents")))) |
|
|
|
(defun create-dump () |
|
"Creates a dump of the Meilisearch database." |
|
(utils:run-bash-command |
|
(format nil "curl -X POST \'~a\'" (build-search-url "/dumps")))) |
|
|
|
(defun update-ranking-rules () |
|
"Updates the way Meilisearch ranks and orders the search results. |
|
The main intention for this function is to show the latest entries into the |
|
database first (when no search term is entered by the user)." |
|
(utils:run-bash-command |
|
(format nil "curl -X PATCH \'~a\' -H \'Authorization: ~a\' -H \'Content-Type: application/json\' --data-binary \'[ \"words\", \"typo\", \"proximity\", \"attribute\", \"sort\", \"exactness\",\"rank:asc\", \"year:desc\" ]\'" |
|
(build-search-url "/indexes/nera/settings") (meilisearch-api-key)))) |
|
|
|
(defun repopulate-database (archive-entries) |
|
"Empties the Meilisearch database and populates it with `ARCHIVE-ENTRIES'." |
|
(delete-all-entries) |
|
(loop for entry in archive-entries |
|
do |
|
(submit-entry |
|
(build-payload (archive::search-id-of entry) |
|
(archive::title-of entry) |
|
(format nil "view/archive/~a" |
|
(archive::slug-of entry)) |
|
(format nil "storage/thumb/archive/~a" |
|
(archive::slug-of entry)) |
|
(archive::month-of entry) |
|
(archive::year-of entry) |
|
(archive::keywords-of entry))))) |
|
|
|
(defun delete-index (index-name) |
|
"Deletes `INDEX-NAME' in Meilisearch DB, doesn't need to be this project. |
|
Because Meilisearch is a seperate service running alongside this website, it can |
|
host searchable databases for other projects on the system. `INDEX-NAME' refers |
|
to those other databases. |
|
|
|
I have not hard-coded this project's database name into this function out of |
|
convenience. I can call this function from SLIME/SLY and clean-up my Meilisearch |
|
dev. instance. It, also, helps if I've made botched this project's DB with an |
|
incorrect name and need to quickly delete it." |
|
(utils:run-bash-command |
|
(format nil "curl -X DELETE \'~a\'" |
|
(build-search-url (format nil "/indexes/~a" index-name)))))
|
|
|