Squashed commit of the following: commitmaster2256d28850
Author: Craig Oates <craig@craigoates.net> Date: Fri Nov 18 19:15:09 2022 +0000 update README.org file. commit97293d886d
Author: Craig Oates <craig@craigoates.net> Date: Fri Nov 18 18:43:00 2022 +0000 remove unused hostname check from quick-search.js file. This is old code essentially and is no longer used. commitada40dec58
Author: Craig Oates <craig@craigoates.net> Date: Fri Nov 18 18:41:45 2022 +0000 update apiKey for live meilisearch service. commit66b64edc2a
Author: Craig Oates <craig@craigoates.net> Date: Fri Nov 18 18:40:38 2022 +0000 update robots.txt (site's URL). This URL is intended to be a temporary one until Nic decides on the final domain name. commite5c95b77dc
Author: Craig Oates <craig@craigoates.net> Date: Fri Nov 18 18:35:44 2022 +0000 rename README.markdown to README.org and add install/sys info. commitd12b76f87a
Author: Craig Oates <craig@craigoates.net> Date: Fri Nov 18 16:02:43 2022 +0000 update .conf and .service files for meilisearch instance. I fixed typo's and added placeholders (E.G. '<INSERT USERNAME HERE>') in places where you need to add your own data which is used on your server/system. commit5a667149a2
Author: Craig Oates <craig@craigoates.net> Date: Fri Nov 18 16:00:58 2022 +0000 update .conf and .service files for main site (ritherdon-archive). I fixed a typo's and put placeholders (E.G. '<INSERT URL HERE>') in places you need to put your own data when deploying to your system. commit1b9f3e673e
Author: Craig Oates <craig@craigoates.net> Date: Fri Nov 18 15:59:53 2022 +0000 update makefile (remove systemd and nginx stuff, focus on install). commit707d469ded
Author: Craig Oates <craig@craigoates.net> Date: Thu Nov 17 18:05:10 2022 +0000 update makefile. commit5c7782439a
Author: Craig Oates <craig@craigoates.net> Date: Thu Nov 17 17:04:33 2022 +0000 add nginx .conf and systemd .service files for meilisearch. These files are an untested. I just added them to get the ball rolling with them. commit4914f3a9bc
Author: Craig Oates <craig@craigoates.net> Date: Thu Nov 17 16:44:18 2022 +0000 create nginx .conf and systemd .service files. These will need tweaking. The most notable one being the nginx .conf file. The website's URL hasn't been decided yet. commit6283918323
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 29 23:43:53 2022 +0100 update makefile. commitbf407e66bb
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 29 22:36:59 2022 +0100 fix typo in makefile. commit500c25abfb
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 29 20:31:21 2022 +0100 add makefile with basic functionality. This is just an initial commit of the file. I will need to add to this as I get this website close to production-ready status. commitca5fc4e9fc
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 29 20:27:45 2022 +0100 comment out the 'covert' function in storage package. This is tag-along code from porting the storage package over from another project. It's never got in the way so it's never caused any errors -- hence no deletion/dealing with it until now. I've commented it out with the intention of deleting if no use for it develops as it the site gets closer to going into production. commitb99e4b95f9
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 29 20:22:49 2022 +0100 intern stuff with '#:' in ritherdon-archive pack. and change to woo. The first part is just a minor change to get Emacs to indent the defpackage stuff in an orderly fashion. The second part refers to the 'main' function. I've changed the server it uses/specifies from Hunchentoot to Woo. I've been using Woo throughout development so I'm more confident with the system using that when it goes into production. The 'main' is used, instead of 'start' when running the website as a systemd service on the production server. commit757698a7bc
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 29 20:18:18 2022 +0100 intern packages in ritherdon-archive.asd file (default as strings). I used Caveman2's project maker and it adds packages, systems, exports Etc. with strings. I changes how they were called here by replacing the string-quotes with '#:'. It annoys me how Emacs indents/aligns the system and package stuff in a wonky way when you don't use '#:'. I finally had fed-up with it and changed it. Overall, this is a minor change. commit15cc041700
Author: Craig Oates <craig@craigoates.net> Date: Fri Oct 28 16:38:41 2022 +0100 update /layouts/header.html template (reduce size of site header). The <a> tag would stretch across the top of the page and it made it hard to click in an 'empty' space on the page. It was, also, confusing when the page would change to the home page when you were not paying attention whilst clicking. commit973523c01f
Author: Craig Oates <craig@craigoates.net> Date: Fri Oct 28 16:32:36 2022 +0100 update main.css (hover state for 'danger zone' links/buttons). commitccc4398a52
Author: Craig Oates <craig@craigoates.net> Date: Fri Oct 28 00:11:53 2022 +0100 remove/comment out search code (JS files) which declare 'serverURL'. Because the code now establishes the 'server' variable in the various HTML Djula templates (grabbed from the database). The code which generates the old 'server' variable and it's data has been commented out or deleted. commitb89dfd828f
Author: Craig Oates <craig@craigoates.net> Date: Fri Oct 28 00:10:27 2022 +0100 change serverURL to just server (fixes typo -- couple of templates). This is just a typo fix. I put 'serverURL' in places and hadn't realised. 'serverURL' should say 'server'. commitd8ecc23079
Author: Craig Oates <craig@craigoates.net> Date: Fri Oct 28 00:00:29 2022 +0100 create 'server' variable (for search) in page and search templates. This variable contains the URL for the Meilisearch instance this site calls out to. It is grabbed from this site's database and passed to the Djula templates providing Meilisearch-based features. The 'server' variable is called here so it's easy for the dev. to see how and when it's called. commit124252070e
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 27 23:57:54 2022 +0100 insert filter-search.js scripts into pages and archive templates. The filter code was moved to its own file and these two files utilse that code. So, these templates now must call them. The reason for moving the code out is to stop the browser's console printing errors -- because the filter JS code was trying to run on pages which didn't have the correct HTML. commit55f12b3f5b
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 27 23:54:09 2022 +0100 refactor build search URL feat. in search package (site-settings). I've left the old code as a comment just in case I need to reverse course. The new way the function gets the (Meilisearch )search URL is to get it from the database (site-settings table). This should make it easier to pass the URL around between the back-end and front-end. commit08a0a06152
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 27 23:53:20 2022 +0100 remove the filter search code from main.js file. I moved it to filter-serach.js in a previous commit. This commit just gets rid of it in the main.js file. commit0b32687251
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 27 23:50:43 2022 +0100 create filter-search.js file in /static/js directory. This file contains the code for the filtering feature in pages.html and archive.html -- a basic search feature which works by filtering the list of entries on the page. It was originally in main.js but I moved it here because it was producing errors on pages which didn't have the filer stuff on the HTML template. This change allows each template to call it when the template actually uses it. commitfdc0b964e0
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 27 23:16:49 2022 +0100 implement back-end for update-search-url (defroute in web.lisp). This feature is for updating the search-url used by this site to call out to the Meilisearch service (which provides the search database for this website). It doesn't touch or alter anything on the Meilisearch service/instance/server. commit3565fa9315
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 27 23:16:00 2022 +0100 implement update-search-url in nera package. This enables the site's back-end to update the search-url slot in the site-settings class and database. commit2f8c973761
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 27 23:15:38 2022 +0100 fix typo in site-settings.html template (search-url). commit189e87ae0a
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 27 22:29:39 2022 +0100 add Search URL section to /user/site-settings.html template. This is just the front-end for this feature. The back-end has not been implemented at the time of this commit. commit84d0885281
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 27 22:28:33 2022 +0100 add search-url section to initial-setup.html template. This section allows users to point this website to the Meilisearch instance which provides the search features of this site. commite9f679bece
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 27 22:00:48 2022 +0100 add search-url parameter to init-db function in nera package. search-url is part of the site-settings class. It is used to help tell the system which URL to use for the Meilisearch instance this website's search features are utilising/calling out to. commiteeabb843ce
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 27 22:00:15 2022 +0100 add search-url slot is site-settings class (site-settings.lisp). commit14750a6c0c
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 25 04:38:02 2022 +0100 implement /danger/upload-snapshot defroute in web.lisp file. I, also, replaced some 'logged-in' permission checks to 'administrator' in several defroutes (mostly 'danger-zone' routes). This is the back-end functionality which allows users to upload Snapshots (in .zip files) to the /snapshot directory. The route accepts multi-file uploads and ignores files which are not either a .zip file or if a file has the same name as one of the Snapshots already in the /snapshots directory. Technically, the user can upload several files at once which are not .zip files and the alert-message will relay a 'success' message, even when nothing was added to the system. This is because the system is relaying the upload went without errors and not how valid each file was. The system doesn't have anything built-in which allows the multi-faceted alert-message approach to work. Another thing to note here is the lack of checks for the contents within a .zip Snapshot file. Basically, there isn't any. I am unsure how many moving parts are going to be in these Snapshots in the future and hard-coding checks for directories and file names seems a bit premature (maybe unpredictable?). The HTML template responsible for dealing with the front-end of the Snapshot features clearly state it is a 'danger zone' section of the site. So, there is an expectation (hopefully) of 'if you don't know what you're doing, then don't touch it'. Hello, person of the future. I was really wrong with that assumption, wasn't I? commita490c50cf2
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 25 04:35:39 2022 +0100 implement store-snapshot in snapshot package. This feature provides the ability to unzip a .zip file (the expected file format users must upload Snapshots with) and store the contents of the .zip file in the /snapshots directory. commitbaebd89329
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 25 04:33:19 2022 +0100 add mulit-file upload form to /danger/snapshots.html template. This commit is just the front-end. The back-end, at time of this commit has not been implemented. The form allows users to upload 'Snapshots' to the website -- with the intention of then restoring the website from that back-up. commitb731aa06dd
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 25 02:33:48 2022 +0100 state incomplete feature alert in restored-snapsnot route. This is to help me not forget to finish implementing this feature or understand what is happening if/when I forget to finish it whilst trying it out. commit99e507e313
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 25 02:25:44 2022 +0100 start implementing /danger/restore-snapshot defroute (web.lisp). The server needs to be restarted after restoring the website from a Snapshot. This commit has code which establishes if the website is running on localhost and informs the user to restart the server (most likely in SLIME) manaully. If the website is running in prod. and using Systemd, the service will need to be restarted that way (user doesn't have access to SBCL or SLIME in that context). So, a Bash script will need to be written and that script will need to be called using (most likely) utils:run-bash-command. At the moment, I haven't got far enough into developing this website to have established a Systemd service or running outside my local dev. machine. So, I have left a TODO comment here stating the prod. side of the defroute is not implemented yet. commit45dc9fc645
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 25 02:25:00 2022 +0100 implement restore-from-snapshot function in snapshot package. commitae51da0277
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 24 20:12:21 2022 +0100 refactor nera: call string-is-nil-or-empty? from validation package. This is part of a multi-part commit to port the string-is-nil-or-empty? function from the utils package to the validation package. The code has a lot of 'utils:string-is-nil-or-empty?' dotted around so this took a few commits to port. There is a chance I've missed it in some obsure places so don't be surprised if you see a future commit relaying something similar to this one. commit03bb7d7cee
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 24 02:10:17 2022 +0100 implement back-end for /danger/create-snapshot-download defroute. This route zips up the specified Snapshot and moves it to the /storage/media directory. I was originally planning of having the user download the Snapshot at this point but I decided to change how this works. I decided to go with the 'zip up a Snapshot and move it to /storage/media' because I didn't want to re-implement the 'download' functionality outsite of the /storage features. Maintaining two 'download' sections is not something I want to be doing -- that is what the /storage section is for (sort out the downloading). Doing this way, also, adds another chance place for the site's data to be recovered from. commit7207043e2f
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 24 02:05:29 2022 +0100 update form action (create-snapshot-download) /danger/snapshots.html I've changed the approach to how users deal with downloading Snapshots. Instead of re-implementing the functionality for downloading files in the /storage section of the site, the user now prep's the Snapshots (zips them up) and moves them to the /storage/media directory. From there, users can download the zipped file and I haven't had to re-implement or write additional code. commitc9377824dc
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 24 02:04:53 2022 +0100 update restore-snapshot.png icon. commitc38e9de548
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 24 02:03:43 2022 +0100 add zip package to ritherdon-archive.asd file. Used for compressing/zipping up stuff -- most notably the Snapshots so they can be moved to the Storage Files where users can download them. commit94b626c9fa
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 24 02:03:01 2022 +0100 add transfer.png icon to /static/images/icons directory. commit90650a8a5a
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 24 00:24:57 2022 +0100 implement /danger/delete-snapshot defroute (back-end in web.lisp). commit0c652be9fc
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 24 00:24:39 2022 +0100 implement delete-snapshot function in snapshot package. commit782c9aee2c
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 24 00:19:25 2022 +0100 implement raw-directory-exists? function in storage package. This differs from the other file/directory checks because it doesn't create a directory when it can't find one (usual Common Lisp predicate behaviour with files/directories). commite599377158
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 21:33:51 2022 +0100 refactor code in web.lisp (call from validation instead of utils). This is the second part of porting string-is-nil-or-empty? from the utils package to the validation package. The code between the validation and utils package was already sorted in a previous commit. This commit to updates where the web.lisp package calls it. commit0b2980f1d7
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 21:31:49 2022 +0100 remove string-is-nil-or-empty? in utils and export it in validation. This is a port I've been meaning to do for a while now; I just haven't got around to it until now. This commit doesn't refactor the code in web.lisp to call the ported code from validation -- it's just the first step in the port. commit0b53969783
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 21:17:14 2022 +0100 add 'raw directory' functionality to storage package. This commit looks like it has more going on that is acutally does. The biggest mis-directed blobs are the docstring comments I added to get-files-in-directory and get-file-names. I just never got around to adding them until now. I remove the format call in make-raw-path because I forgot to do so in another commit. The code works with or without it which is why I missed it previously. I'm removing here to clean the code up and before I get distracted and forget. The actual code which this commit is mostly for are the get-raw-directories and get-directory-names. You shouldn't need to use these functions in the normal operations of the site. They are mostly intended to be used for dealing with the site's snapshots -- or any other directories outside of the /storage directory. The snapshot management section of the site is part of the 'danger zon' features -- hence the need to break-out of the usual '/storage directory' features. commitfdf43c2b01
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 21:14:55 2022 +0100 add snapshot data passed to template from /danger/manage-snapshots. This data is just a list of the directory names for each snapshot stored in the /snapshots directory -- only deals with HTTP GET here. commit2a456bcdc5
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 21:13:43 2022 +0100 add download, restore and delete controls to /danger/snapshots.html. This is just the front-end. The back-end has not be implemented at time of commit. commit89b4fbacee
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 21:12:56 2022 +0100 add restore-snapshot.png icon to /static/images/icons directory. commitf57d6ef33c
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 19:23:02 2022 +0100 implemented 'Disk Info.' feature on the back-end for /dashboard. commit26882cae6f
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 19:22:04 2022 +0100 add 'Disk Info.' section to /user/dashboard.html template. This is just the front-end. The back-end (web.lisp) has not been implemented at the time of this commit. commita292d245b7
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 19:19:46 2022 +0100 add cl-diskspace package to ritherdon-archive.asd. This package grabs disk information from the OS -- and can display it in human readable form. Looking to create a 'Disk Info.' section on the dashboard page to help users decide when they need to start deleting snapshot data or move to a machine with a bigger disk. At the time of this commit, no features have been implemented yet. commit254500cad7
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 18:52:58 2022 +0100 update .gitignore to ignore the /snapshots directory. This directory is created during the site's first run. This addition to .gitignore stops the commit history from getting clogged up with irrelevant data during development. commit2615dcb892
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 18:52:05 2022 +0100 minor edit to copy text in /user/site-settings.html template. commitcba7675d3d
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 18:51:39 2022 +0100 implement back-end for take-snapshot feature (web.lisp). commit943fc77d92
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 18:51:13 2022 +0100 add take-snapshot.png icon to /static/images/icons directory. commitefbee61366
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 18:50:36 2022 +0100 add form for taking snapshot in /danger/snapshots.html template. The back-end is not implemented at time of commit. commitf629e3d750
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 18:27:11 2022 +0100 implement various 'raw' based functions in storage package. The most notable ones are the 'copy' functions. The main intention here is to provide the functionality needed by the snapshot package to help it take programmatic snapshots of the website's data and database. At the time of writing the 'restore from snapshot' functionality has not been implemented but that is something these 'copy' functions will look to help with. commit8a9354f50f
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 18:24:08 2022 +0100 implement create-timestamp-text function in utils package. This is a variation on the create-timestamp-id used for generating a timestamp-id for the Meilisearch database. This text-based timestamp takes the form of YYYY-MM-DD_HH-MM-SS. The main intention of this function is to use is as part of a directory name when generating a snapshot (of the website's data/DB). commit704e87546e
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 18:21:30 2022 +0100 create /danger/snapshots.html template. This is just the HTML template. The back-end (I.E. defroute in web.lisp) is not implemented at the time of this commmit. commit250305e8b0
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 18:20:14 2022 +0100 add link to /manage-snapshots in site-settings.html template. This is part of the 'danger zone' section in the site-settings.html template. The back-end for this feature is not implemented at the time of this commit. commit736d12074f
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 18:17:24 2022 +0100 add snapshot.lisp, implement take-snapshot and stub-out restore. I will add to this as I add more snapshot features into the main website code (I.E. HTML templates and routes in web.lisp). At the moment, this package is a little island right now and doesn't integrate into the rest of the website at all. commit9ff9711af8
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 18:15:05 2022 +0100 add copy-directory package (quicklisp) and snapshot package to .asd. copy-directory is a package on quicklisp to copy files using the OS's native cp command (useful for taking snapshots/back-ups of directories). The snapshot package is for copying directories (I.E. taking snapshots) in /storage and the site's DB. commit5c90771bc4
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 18:13:16 2022 +0100 delete schema.sql file (from /db). It's never been and won't be used. It came as part of the 'make project' process when generating a Caveman2 website -- just never got around to deleting until now. commit14586efa2a
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 00:21:06 2022 +0100 implement copyToClipBoard functionality (main.js). This feature copies the selected file in /storage/media and builds it full URL. The intention is to make it easier for users to copy the URL when the need to paster it into one of the 'edit' pages (I.E. Pages and Archive sections) text areas. At the minute the code assumes its only copying and building URL for the files listed on the 'Storage Index' page (/storage/manage defroute). If the website's features expand in the future in this area, this function/feature will need to be refactored. commit72d0352c79
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 00:19:38 2022 +0100 add popup section (copy text via JavaScript) in /user/storage.html. This is just the HTML and CSS additions to the HTML template. The JavaScript has not be implemented yet. commit1bec6e8034
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 00:16:44 2022 +0100 add thumbnail path for SVG file in build-thumbnail-path filter. The file-type contains 'image' so the filter assumed it was a .png or .jpg and the image wouldn't render when it was a .svg file. This additional check to the filter makes sure the svg.png (stock image file) is used when a thumbnail is used in a HTML template. commit416d55ce3c
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 00:15:56 2022 +0100 update main.css (popup stuff for coping text via JavaScript). commit4c157b5fda
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 00:13:06 2022 +0100 comment out un-used code in build search url in search package. I was going to start on making this function more robust and remove the hard-coded nature of it but cleary I got distracted and never actually started it. I know it will need to include the 'build-url' functions in the utils package but until I get around to actually refactoring this function, I'm going to leave the code commented out. commit7f463f8d71
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 23 00:12:39 2022 +0100 add copy.png to /static/images/icons directory. commita45747c7d8
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 22:51:13 2022 +0100 implement back-end for 'manage database entries' ('danger zone'). This includes the HTTP GET and HTTP POST requests (defroutes in web.lisp). This is part of the 'danger zone' features because it can leave the website in an un-recoverable state. commitc958bc8621
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 22:49:57 2022 +0100 create and populate /danger/manage-db-entries.html template. This template has the CSS and HTML already included with this commit. It's usable but I'm guessing it will need tweaking when the site gets closer to going into production. commit51a078c8c2
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 22:49:09 2022 +0100 change 'Media Files' to 'Storage Files' (/danger/manage-files.html). commit757f3b0448
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 22:04:42 2022 +0100 implement the manage/delete files back-end features ('danger-zone'). commit7fc8609aed
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 22:03:01 2022 +0100 create and populate the /danger/manage-files.html template. The template is pretty much ready to go with this commit. CSS stuff and data population already added. commitf88686dcd1
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 22:02:28 2022 +0100 add icons (/static/images) for 'delete' links and buttons. commitfa70b7e290
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 21:59:04 2022 +0100 add 'danger zone' links in site-settings.html template. Links point to sections for manaully deleting files in the /storage directory (without database interaction) and deleting database entries (without storage file interaction). These sections are marked as 'danger zone' because they should be used carefully and only if the site's storage files and database have become out-of-sync. with each other. The back-end functionality has not been implemented yet. commit53b2289f7a
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 21:58:20 2022 +0100 update main.css ('danger-zone' stuff). commit3f0b990832
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 19:54:41 2022 +0100 add error-refresh and reset-website images in /static/images/icons/. commit66b84f8de6
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 19:50:44 2022 +0100 implement the /danger/reset-website defroute (back-end feature). This feature deletes all the user created content stored in the /storage directory, the website's database (so User Accounts) and wipes the Meilisearch database clear of the Archive Entries stored in it. This is part of the 'danger zone' features and intension is to allow the site's Admin. to clear out the website and start with a fresh clean install. commit78298c08ee
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 19:48:49 2022 +0100 add remove-file-with-raw-path function in storage package. This function is part of the 'reset website' feature implemention. You should only need to call this when you want to delete something outsite of the /storage directory (I.E. the website's database). commitb0efc40a94
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 19:48:07 2022 +0100 add 'reset website' form to /user/site-settings.html template. This is part of the 'danger zone' section in site's settings. The back-end for this feature is not yet implemented. commit74a6507902
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 18:59:29 2022 +0100 implement the 're-populate search DB' functionality (web.lisp). This feature is part of the 'danger zone' section in the site's settings. This feature clears the Meilisearch database (with this site's Archive Entries) and re-populates it with the data from this website's (nera) database. commit3f21282114
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 18:57:44 2022 +0100 update /user/site-settings.html template (initial 'danger zone'). This commit establishes the 'danger zone' in the site's settings page. This will need to be added to over time. This commit set-ups the 're-populate search DB' feature but the back-end is not yet implemented. commit7771f966a1
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 18:56:19 2022 +0100 update main.css ('danger zone' styling). This is just a initial addition for the 'danger zone' section of the site. commit0596972877
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 17:16:33 2022 +0100 implement 'repopuluate meilisearch DB' functionality (in search). The main feature is the repopulate-database function which clears out the Meilisearch DB index containing this site's searchable information and repopulates it with the site's nera.db information. I, also, added a delete-index function (also in search package) but that is mostly a convenience feature. It allows me to quickly delete any index in the Meilisearch DB which I made by mistake (E.G. wrong name, change of plan) and remove old indexes from other projects which are no longer in use (on local dev. machine mostly). commita046d21dbb
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 17:15:31 2022 +0100 add redirect from /sitemap to /sitemap.xml in web.lisp (defroute). This is just a 'catch-all' defroute. commit50a76f56ba
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 01:42:51 2022 +0100 add robot.txt file to the /static directory. I'm sure I will need to update this at some point. I didn't spend too much time on it, just jotted things down as a I came across routes in web.lisp. The Sitemap URL will need to change, the closer the site goes into production and an domain name has been bought/finalised. commit862b55ec23
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 01:42:13 2022 +0100 implement /sitemap.xml defroute in web.lisp file. commit5f040b642d
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 22 01:39:14 2022 +0100 implement build-url and build-url-root functions in utils package. These functions are mostly aimed at the site's XML-generated site map. The piece together the site's URL from lack's request struct. I don't know if there is a pre-built string containing this information (I.E. http://localhost:5000 and http://localhost:5000/testing) which is why I have written these functions. commit3cfb5d4331
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 20 23:31:29 2022 +0100 add birthday-cat.png to insert-dashboard-cat function (view.lisp). A little Easter Egg... commite336640fa0
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 20 23:14:52 2022 +0100 update quicklist sections in archive-entry.html and page.html. Replaced the 'View Entry' link with an edit entry link instead. commitad58e02f5c
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 18 20:35:57 2022 +0100 add older/newer entries section to archive-entry.html template. commitd6b7e031e7
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 18 20:35:34 2022 +0100 update main.css (mostly for listing older/newer archive entries). commit2b4336bced
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 18 20:34:51 2022 +0100 add older/newer archive entries to data returned from /view/archive. commita4486ad168
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 18 20:34:00 2022 +0100 add get older/newer archive-entries in nera package. I, also, change the order to 'created-at' for getting all archive-entries. commitbb7396d95e
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 18 13:56:10 2022 +0100 fix add-storage-file regression bug (database and file different). When uploading a file to /storage the database stored the un-formatted name and the file was stored with the formated name. This meant when anyone tried to download or rename the file, the website would produce an error. This commit fixes this and has the database and file storage system store the files name in the formatted way (all lowercase with no whitespace). commit2515803ce0
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 17 20:49:54 2022 +0100 update quick-search.js (minor URL and CSS changes). This file has been in the repo. for a while -- I copied it over from my co-web project. But, I hadn't touched it or integrated into this website until now. The changes in this commit are mostly minor changes which bring the URL's for the Meilisearch instance this website connects to and change the CSS classes to match the ones used in this project (and not my co-web) project. commit6d60cfa86a
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 17 20:48:30 2022 +0100 update full-search.js (mostly CSS classes). Because I copied over the code from my co-web project, the code in this file has CSS classes referring to that project. This commit updates those classes to fall in-line with the CSS/design of this website. commitc0ca6c8aa2
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 17 20:47:46 2022 +0100 update search.css (remove co-web code). I will need to update this as I go along. I copied the code over from my co-web project so the webpages have been rendering with that style/design until now. This commit has removed as much of it as possible and added/updated the style rules to fall in-line with the design of this website. commitf23b9b8a85
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 17 20:45:40 2022 +0100 update full-search.css (removed rules for co-web project). I will need to update this as I go along. I copied the code over from my co-web project so the webpages have been rendering with that style/design until now. This commit has removed as much of it as possible and added/updated the style rules to fall in-line with the design of this website. commit89283b7cf7
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 17 20:45:17 2022 +0100 add 'quick search' section to archive-entry.html template. commit07d6607e88
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 17 20:44:27 2022 +0100 add 'quicklist' and 'quick search' sections to page.html template. commit554084f05a
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 17 20:44:08 2022 +0100 remove some HTML from search.html template. commitd2005080b2
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 17 20:41:58 2022 +0100 apply indentation formatting to /layouts/default.html template. I was faffing in this file and had Emacs do a quick re-indentation (with web-mode) enabled. I don't think I have touched this file since installing web-mode. commit2d862e29e2
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 17 20:39:45 2022 +0100 add 'update'ranking-rules' func. to search package. This allows you to change the order the search results are returned in. The main purpose why you would want to use this function in this context is to make sure the list of results is ordered by the year the artworks where published/created. commit73c4a1cd16
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 21:33:57 2022 +0100 update main.css (mostly image sizing in article body). commitac402d333d
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 21:31:55 2022 +0100 add quick-list section to templates/archive-entry.html template. This will need updating. I copied over a quick-list section from another template. I'm off home after this commit (got one more commit to enter after this). Thought I would get something in before I left, though. commit55df7b5161
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 21:17:13 2022 +0100 simplify templates/archive-entry.html template (remove entry dates). commit3ff07064a6
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 20:50:43 2022 +0100 add 'Publish' date section to /user/edit-archive.html template. commit833b125f46
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 20:49:03 2022 +0100 add 'Publish' date section to /user/create-archive.html template. Only 'Month' and 'Year' are used because 'Day' is not relevant -- at time of writing. You might see an change to this form in a later commit if requirements change. commitc9733d5f94
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 20:47:40 2022 +0100 remove database Created/Updated At info. from archive.html template. commit7931d33588
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 20:46:45 2022 +0100 update main.css (input for 'Publish' date mostly). commitb874b3e742
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 20:45:09 2022 +0100 update archive-entry HTTP POST request (include 'publish' date). The publish date refers to when the artwork was publish which differs from the 'Created At' date. That refers to when the entry was added to the database. commitb750649cb2
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 20:43:52 2022 +0100 replace 'Created At' timetamp with 'Month' and 'Year' (search.lisp). 'Month' and 'Year' refer to the artworks publish date. commit36f396b0da
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 20:41:57 2022 +0100 add publish month and year arg's to create-archive-entry func. This update is in the nera package. It is part of a refactoring of functionality to have a seperate 'Publish' date and a 'Created At' date within this site's database. The 'Created at' date refers to when the entry was added to the database. The 'Publish' date is the date of the artwork. commit37ec669980
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 20:40:54 2022 +0100 add (publish) month and year to archive-entry class (archive.lisp). This is mostly for the Meilisearch database. This addition will mean it's stored in this site's database, though. commit2300c9413a
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 18:33:49 2022 +0100 update HTML in archive-entry.html and archive.html templates. commita697b09462
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 18:32:12 2022 +0100 fix link styles in main.css file. Default links were changed in previous commit and had a knock-on effect to some stylised links. commiteba62e0925
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 16:56:34 2022 +0100 update main.css (filter controls for 'Index' pages mostly). I updated the CSS for links (a tags) too. commite6a1b4287f
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 16:56:17 2022 +0100 add filter controls to pages.html template (HTML, CSS and JS). commita610b52ba6
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 16:55:49 2022 +0100 add filter controls to archive.html template (HTML, CSS and JS). commit6db73aac52
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 16:48:53 2022 +0100 add filter list code to main.js file. This code is intended to be used mostly in the 'index' pages (Pages and Archive). These pages are mostly going to act as a back-up for when the Meilisearch service goes down or if the user has a poor internet connection. This filtering behaviour allows the user to filter the entries in the Index they are viewing (Pages or Archive). There are no images and no repeated calls back to the server. Each index lists just the text (Titles, publish info. Etc.) and from there the viewer can filter the results on the page by entering text into the text box in the Index-base HTML templates. From Nic's point-of-view, these Index pages will not be included in the nav. menu but if the Meilisearch service goes down (at this site is still operational), she can swap out the 'search' page for these (really it's just the Archive Index) so people can still have some form of search/filtering ability whilst viewing the website. commit1bb0a92d39
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 15 16:48:32 2022 +0100 add footer tag to footer.html template. commite548ba7993
Author: Craig Oates <craig@craigoates.net> Date: Fri Oct 14 21:51:43 2022 +0100 update CSS and HTML in /templates/sign-up.html template. commitbf007601ea
Author: Craig Oates <craig@craigoates.net> Date: Fri Oct 14 21:51:02 2022 +0100 fix grammar mistake in /templates/user/edit.html template. commitf74ae90e02
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 11 00:01:36 2022 +0100 update artchive-entry.html template. Starting to add extra HTML and CSS tags and rules. This is just the start to building out this template. commitbb4fbe1a00
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 11 00:01:15 2022 +0100 update main.css (front-end archive entry). commit146901fa80
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 11 00:00:38 2022 +0100 set site title dynamically in index.html template. commitd4ab83ef7a
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 11 00:00:00 2022 +0100 clean-up code in web.lisp and view.lisp files. commit94122e7741
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 10 20:18:00 2022 +0100 tidy-up code and leave comment in /edit/archive about Meilisearch. This defroute doesn't change anything relevant in the Meilisearch DB so it doesn't need updating. I've left a comment (mostly for future me) to remind me not to think I've missed a defroute and try to integrate the Meilisearch stuff into it. commitf38e0aef42
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 10 20:11:57 2022 +0100 integrate Meilisearch into edit keywords functionality (web.lisp). commit5d853592bf
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 10 19:57:03 2022 +0100 integrate Meilisearch into /rename/archive-entry defroute. The defroute to update the thumbnail doesn't need to change but I added a comment to make it clear in the future -- I can see me forgetting it doesn't need updating and try to add code which doesn't need to exist. I, also, did a bit of updating to the layout of the code is parts -- mostly when setting the alert message. commite90de5e31a
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 10 17:12:31 2022 +0100 add 'Search' as hard-coded URL in '/' and update delete Arch. Entry. The re-direct to /search when the user has set /search as the site's home page is part of a list of other hard-coded re-directs in this site's '/' defroute. The site now deletes the Archive Entry from the Meilisearch database alongside the files in the /storage directory and the nera database. commit6e4da8dc26
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 10 17:08:53 2022 +0100 implement set-filter-attributes, delete-all-entries and create-dump. These functions are helper functions to manage the Meiliseach database from a Common Lisp perspective. The intention is to work them into the website's back-end so the user can reset or re-populate the database from the website without me (or someone else) having to SSH into the VM and do fix/restore things manually. For now, they allow you to manage the database from SLIME. commitd488b56780
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 10 17:08:13 2022 +0100 add 'Search' to hard-coded links in site's nav. menu. commit0f2067742a
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 10 17:07:39 2022 +0100 add 'Search' page to init-db function in nera package. commit5287e9858e
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 10 16:29:40 2022 +0100 change month number to name when adding archive entry to search DB. This change is so the refinements on the seach page shows the month names instead of thier numbers. This is because most people tend to work with month names and not numbers in this context. commit6389480260
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 10 16:27:47 2022 +0100 create month-number-to-name function in utils package. This is a helper function to convert '1' to 'January', for example. The intended use for this is to work alongside the local-time package when generating the month number from a timestamp. commitad65e0fca1
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 21:48:40 2022 +0100 add 'title' attribute to links in /user/archive.html template. commit4d5a9b7f0c
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 21:40:35 2022 +0100 update /layouts/header.html template with new CSS rules. This commit makes the header (site's name and logo) a link which points back to the site's "/" (I.E. Home page/route). It, also, centralises the header and the navigation menu (in the header). This is so the header doesn't look weird when viewed on a mobile phone. I removed the <hr> at the bottom of the file because the site has enough styling applied to it to make the <hr> look out of place now. I can clearly denote/identify where the site's (front-end) header and footer stops and starts. commit82192113c0
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 21:40:15 2022 +0100 update main.css (front-end header stuff). commit990eb22f6d
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 21:39:52 2022 +0100 remove <hr> from /layouts/footer.html template. commit6a5b6ea838
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 21:15:05 2022 +0100 fix day-of-week timestamp bug in insert-dashboard-cat filter. I didn't realise the local-time system set 'Sunday' to '0' in its timestamp-day-of-week function. I thought it was '7'. This commit changes the check for '7' to '0' and adds a few comments to help identify which days the cond form is checking against. commitad0954e319
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 21:12:31 2022 +0100 add Meilisearch JavaScript and CSS files. These files are provided by the Meilisearch project. This commit is larger than usual because of this. The CSS files are copied over from my personal website's repository so they will need modifying going forward. I've added them here as a starting point. commit0aef87a949
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 21:10:26 2022 +0100 create the search.html template (for Meilisearch). This page is populated in this commit but is broken because it requires several JavaScript files which have not be committed to the repository as of yet. This commit is in preparation of adding those files. commitc2788c43af
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 21:07:44 2022 +0100 integrate the search package into create archive route in web.lisp. The site now populates the Meilisearch database when the user creates an archive entry. The Meilisearch needs to be set-up manually at this moment in time so expect this to break easily if you haven't got Meilisearch working. commit04c79732f7
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 21:05:19 2022 +0100 create search.lisp file and add to ritherdon-archive.asd file. I copied most of this over from my personal website's repository. So, there are bits of code which look a bit out of place in this context. With that said, the code does run and just needs to be integrated in the defroutes in web.lisp. commitcff6bfc1ee
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 17:21:07 2022 +0100 fix rendering errors in 'title' values (/user/pages.html template). commit295ed248c9
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 17:20:47 2022 +0100 update default favicon.png and site-logo.png files. commite705aa67e4
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 17:19:50 2022 +0100 update main.css -- mostly for initial-setup.html template. commita834f54a13
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 17:12:54 2022 +0100 update initial-setup.html template -- massive overhaul. I've removed the 'extends' blocks and built a completely seperated template from the rest of the website. The template was not rendering properly because certain parts of the website had not been set-up yet so the complete isolated template was/is required. I've, also, added extra options for the user to set during this first run process which will be processed by the back-end. I've not put much work into adding validation checks because it is a one-time thing (this page). You'll need access to the server anyway -- at this point -- so a bit of manual correction of mistakes will be easy to achieve. Deleting the database will be enough to trigger the first run process again. commit324229dea4
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 17:11:24 2022 +0100 modify init-db func. in nera package. This is part of a refactoring to allow the user to set more settings in the initial set-up/first run process. commitd35ae529db
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 9 17:10:41 2022 +0100 add power.png image to /static/images/icons directory. commiteb7435f720
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 20:49:39 2022 +0100 add checks to make sure uploaded files are images in site-setting. The conditions are added to the 'Site Logo' and 'Favicon' sections/defroutes. The checks are to make sure a user doesn't try to set an MP4 file as the site's favicon or site's logo. commit85b0d8da9e
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 20:48:22 2022 +0100 add hints to various sections in /user/site-settings.html template. These are just blocks of <p> tags to help explain what the various settings do in each section. commitb9493fb138
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 20:48:11 2022 +0100 fix typo. in main.css. commit7bd5f1dd02
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 20:29:20 2022 +0100 update main.css (section controls for dashboard). commit78cf655f5d
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 20:19:53 2022 +0100 update HTML and CSS in /user/site-settings.html template. I did a lot of re-arranging of the settings alongside adding CSS and HTML stuff to template in-general. commit3d24084e24
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 20:18:56 2022 +0100 add 'administrator' checks to /user/edit.html template. Some links were being shown to non-administrators. This check fixes that. commit7b1a80333c
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 20:18:30 2022 +0100 update main.css (site-setting stuff). commit3274456906
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 20:17:37 2022 +0100 fix typo. when calling set-alert when enabling sign-up option. commitcedab101f1
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 17:18:51 2022 +0100 update icons for 'Manage Account' links/buttons. This is across several HTML templates but they are minor changes so it should be easy enough to follow when viewing the commit's diff. commita522701b58
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 17:10:29 2022 +0100 update /user/index.html (HTML and CSS). commit9d19cdcea9
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 17:10:09 2022 +0100 add 'quicklist' section to /user/edit.html template. commit2a7e133d7e
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 17:09:48 2022 +0100 update main.css (user account stuff). commitc8de64382f
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 17:09:06 2022 +0100 add all-accounts.png and create-account.png images. commitd64ae60c26
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 13:01:45 2022 +0100 update /user/edit.html template (split 'edit' form into two). This update seperates the form previous version of the template (for updating the user's display name and password) into two. This is because of a change in the back-end defroutes. It's makes it easier on the back-end to update the password and display name seperately with different HTTP POST requests (and HTML form data). commit76450d3414
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 12:59:07 2022 +0100 seperate /user/edit route (HTTP POST) into two. There are now two HTTP POST routes which update either the user's display name or password. commit949cee862c
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 12:56:19 2022 +0100 refactor insert-dashboard-cat filter (view.lisp). Changes the time conditions used to display the various cat icons in the dashboard; And, fix a bracket-bug (closed a condition block too early). commitea4520e7d4
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 12:04:32 2022 +0100 update /user/edit.html template (CSS and 'delete account' form). commite688c55cd9
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 12:03:06 2022 +0100 adding padding to alert message container in main.css. The alert message was rendering under the dismiss button when viewed on a small (phone) screen. The extra padding makes sure the message remains clear of the button. commit9430942cb5
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 12:01:21 2022 +0100 update /user/delete defroute: add 'type username input check'. When the user wants to delete their account they must now enter their username as part of the form they submit. This is so they don't accidently delete thier account. commit6c65b9c5a0
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 12:00:50 2022 +0100 add delete-account.png icon. commit661bbbd121
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 11:15:14 2022 +0100 update main.css file (log-in form stuff mostly). commit2730bbaaa1
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 8 11:14:40 2022 +0100 apply CSS classes to /user/login.html template. commit10e354fc9d
Author: Craig Oates <craig@craigoates.net> Date: Thu Oct 6 19:43:37 2022 +0100 update .gitignore and default assets (storage file after first run). The default assets were part of the list in .gitignore so the site would produce errors during the site's initial first run set-up. I've quickly added the files to the repository but they will need work done to them because the files are empty and the wrong default images. This commit is done from my computer in the flat so the files added were to just fix the errors. commitff86cb159a
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 4 11:46:46 2022 +0100 reduce the amount of recent files returned with /dashboard defroute. commit91321b26ec
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 4 11:37:46 2022 +0100 replace dashboard profile img. with insert-dashboard-cat filter. commit130a6d3061
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 4 11:34:42 2022 +0100 implement insert-dashboard-cat filter (view.lisp). This filter is builds the path for the dashboard profile image. The path points to a different icon in the /images/icons directory depending on what time and day it is. There is no major functionality addition with this code. It just a little sprinkle of cuteness for the user of the site. commit318cf88f73
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 4 11:33:02 2022 +0100 add profile (dashboard) cat icons. These images are will be displayed alongside the user's display name on the dashboard. There are several images because different ones will be displayed at different times of the day and week. commit7b24fdca1c
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 4 10:32:06 2022 +0100 add alert type values to all set-alert functions called in web.lisp. This update sets the alert type which adapts the alert message's background colour and image to the type of message the alert is relaying to the user. commit0795a67f86
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 4 10:30:04 2022 +0100 move /user/edit link out of administrator check in dashboard.html. Non-administrator's will now be able to see the 'Account Details' link when viewing the /dashboard page. It was a mistake I hadn't caught. commit5212a9b985
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 4 10:29:21 2022 +0100 add CSS and JavaScript code to alert message section in header.html. commita7aff4b828
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 4 10:28:33 2022 +0100 change site-side-menu.js script element to main.js in default.html. commitfad642822a
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 4 10:28:07 2022 +0100 update main.css (alert message styling). commitd446834565
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 4 10:25:29 2022 +0100 rename site-side-menu.js to main.js. Because the amount of JavaScript I've written, it doesn't make sense to separate things out into their own files yet. So, I've renamed the file to main.js and will add the little sprinkles of JavaScript here. If the amount of JavaScript grows, I will need to move things out of here but that is a future problem. commit2e1e5b59d5
Author: Craig Oates <craig@craigoates.net> Date: Tue Oct 4 10:22:53 2022 +0100 expand alert message types in set-alert function (utils package). Been meaning to do this for a while but never got around to it. commit9df8e363c9
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 08:04:37 2022 +0100 update dashboard.html template (add thumbnails to Storage section). commit5782362136
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 08:01:47 2022 +0100 add 'quicklist' section to multiple HTML templates. This is a multi-file commit because the code is essentially the same. Each HTML template has either had a 'quicklist' section added or had links added to it. 'Quicklist' is just a section with a collection of links to other parts of the website based on the context of the page/user's current location. commitd791b04449
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 08:00:37 2022 +0100 update main.css (mostly fixing rows with thumbnails in). commitc89d1653a9
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 07:08:01 2022 +0100 modify the rel. URL for the images in build-thumbnail-path filter. commite33ac9af0a
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 07:03:38 2022 +0100 add /storage/thumb/media/:slug defroute (re-added). I think I had a version of this route but I deleted it -- can't remember if it wasn't needed or didn't work as intended at the time. Anyway, this commit makes it part of the code-base (again). The function provides the website to use the automatically generated thumbnails -- when a user uploads the an image to /storage/media -- instead of the full-sized image. This should help reduce download times if a list of uploaded image are several Mega Bytes and being viewed at once -- like an index page for example. commit426d404593
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 06:19:02 2022 +0100 update HTML in /user/edit-page.html template. commit5ef81ba4f3
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 06:18:49 2022 +0100 update HTML in /user/edit-archive.html template. commitb6aec7c5ea
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 06:18:34 2022 +0100 update HTML in /user/create-page.html template. commitafaa7268ad
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 06:18:15 2022 +0100 update HTML in /user/create-archive.html template. commit006bd652af
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 06:17:28 2022 +0100 apply minor formatting changes to /user/storage.html template. commit009e0103b8
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 06:16:32 2022 +0100 update main.css -- mostly focus on forms for editing pages/archive. commitd7bbb9ecf2
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 06:14:54 2022 +0100 code clean-up in web.lisp (old comments, formatting and slugify). commitdb4393ddac
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 06:13:47 2022 +0100 implement update-single-nav-menu-item in nera package. This function is so the user can add/remove a single page from the nav. menu whilst editting the page. commit09e44a6364
Author: Craig Oates <craig@craigoates.net> Date: Mon Oct 3 06:13:18 2022 +0100 add save.png icon to /static/images/icons directory. commit936d9034c8
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 2 09:46:18 2022 +0100 end-of-session commit -- update main.css file. commite15b2299ad
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 2 09:42:41 2022 +0100 apply CSS classes to /user/archive.html and /user/pages.html files. Both files are basically the same with minor changes in classes and HTML tags. Commited both files at the same time because of this. commitecfb667c67
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 2 09:42:07 2022 +0100 clean-up and remove unused code (HTML templates). commite33575c74c
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 2 09:41:01 2022 +0100 update main.css (mostly index pages listing/title section). commitfa9bb8cfb2
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 2 07:54:38 2022 +0100 minor padding change for be-gui-link class. commitb4a34c102a
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 2 07:39:35 2022 +0100 delete site-logo.png and favicon.png files from /static/images. These files are generated during the website's first-run process. They don't need to be a actual hard-coded part of the repository. The way the site is set-up, the user will either stick with the defaults (generated on first-run) or upload their own personal files. commitd305d4c110
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 2 07:38:23 2022 +0100 update /user/storage.html template (mostly CSS updates). commit1551e3fc1b
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 2 07:37:47 2022 +0100 update /user/dashboard.html template (mostly for CSS updates). commit7a6353635b
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 2 07:36:53 2022 +0100 update main.css -- focus on dashboard.html and storage.html. commit61e8ef91f8
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 2 07:34:29 2022 +0100 refactor file upload storage routes -- regarding thumbnails. The automatically generated thumbnails are no longer stored in the database. They are created, updated and deleted alongside it's main accompanying image -- as a file in the /storage/media directory. commiteddedc891d
Author: Craig Oates <craig@craigoates.net> Date: Sun Oct 2 07:33:17 2022 +0100 adjust relative URL for images in build-thumbnail Djula filter. commitbaed98ce7a
Author: Craig Oates <craig@craigoates.net> Date: Sat Oct 1 08:04:40 2022 +0100 fix bug when creating thumbnail whilst uploading a file to /storage. commit4647323fda
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 30 08:37:05 2022 +0100 add storage, pages and archive entries to dashboard.html template. The route in web.lisp calls the recently created 'get latest' functions in the nera package. The code is web should be fine but the dashboard.html stuff is buggy and so is the back-end routes for uploading deleting file in the /storage section of the website. This is an end-of-session commit, though, so the bugs will have to be fixed at a later time. commit3b49fbf6e0
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 30 08:35:37 2022 +0100 update dashboard.html template (start expanding the Storage part). This code is buggy but it is a start. This is an end-of-session commit hence the buggy state being left as it is. commitc27ecb9853
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 30 08:33:53 2022 +0100 add get latest editted 'X' functions in nera package. These functions haven't not been used much so I don't know how reliable they are in their current forms. Expect some work needed on them in the future. commit75e05cc4aa
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 30 06:37:11 2022 +0100 update main.css (back-end dashboard buttons and gui-links). commit87f0e81258
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 30 06:36:51 2022 +0100 update /user/dashboard.html template (CSS and icons). commitdd7796fb4e
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 30 06:36:26 2022 +0100 update /layouts/header.html template (CSS and icons). commit01bacffa19
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 30 06:35:56 2022 +0100 update several of the icons in /static/images/icons. commit9a2c933667
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 30 06:34:48 2022 +0100 add loads of icons (.png) and rename a few of them. commit7b6cde549b
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 27 02:37:07 2022 +0100 move alert section to header.html and get side-menu working. I move the alert section to the /layouts/header.html template because it was easier to get it to render between the logged-in user's site header and the site's 'normal' header. The site's side-menu (for logged-in users) still needs stuff adding to it -- and tweaking -- but the base style, layout and whatnot is in place. commitdcd1e76d0e
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 27 02:36:15 2022 +0100 re-organised /layouts/default.html and remove 'alert' section. The alert section will be moved to /layouts/header.html. commite844408d86
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 27 02:34:44 2022 +0100 remove hard-coded '2018' in footer, prints current year (copyright). commit0dc9c91795
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 27 02:33:09 2022 +0100 update .gitignore to ignore favicon and site-logo in /static/images. These files are set during the site's first run and are personal to the deployment. The repo. has no need to track these files. commitf4bf0ba511
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 27 02:30:22 2022 +0100 add icons in /static/images and static/js/site-side-menu.js file. commit51d564fff8
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 27 02:28:35 2022 +0100 update .gitignore to include files in /static directory. I've moved/changed how I was using the /static directory (site-wide snippet) so I can start adding in images and what have you again. I didn't realise I had set Git to ignore the how /static directory. commit8c60ac6031
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 27 02:26:07 2022 +0100 start adding style rules for site side-menu section in main.css. This is just a start. I will need to add to this. commitf60b09bbdd
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 26 01:02:27 2022 +0100 add 'success' optional parameter to set-alert in update nav. menu. I think I will need to swap out the string value 'success' with a constant so things aren't as stringly-typed as this. With that said, this is just a quick proof-of-concept. I can keep this around until I build out this updated feature some more. commita7eb3240bf
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 26 01:01:18 2022 +0100 add optional parameter to set-alert function in utils package. This is so I can define what type of alert message I want the server to send... and include the cat images I found. commitfda81fbb82
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 26 01:00:48 2022 +0100 add safe filter to alert message in /layouts/default.html template. commita72e133f13
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 26 00:24:08 2022 +0100 update / (site's index page) route and nav. menu update feature. I hard-coded redirects to the pages which can't be deleted into the site's index ('/' route). This allows them to be set as the home page in the site's settings. I, also, updated the data which is passed to nera:update-nav-menu. This change is part of the Nav. Menu settings in the site's settings. commit9a30da1a44
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 26 00:22:38 2022 +0100 refactor update-nav-menu in nera package. The function now loops through the values passes to it and sets the 'enable-nav-menu- column in the database for the currently selected page in the loop. commit0a302c2a77
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 26 00:20:27 2022 +0100 refactor /user/site-settings.html template (nav. menu updates). The nav. menu section sends a hidden value if a check box isn't selected. This is in preparation for implementing the back-end part of this feature. I, also, removed the if-statements when selecting the home page in the 'Home Page' settings. Again, this in preparation for adjusting the back-end functionality for this feature. commitf814aef20d
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 26 00:19:07 2022 +0100 update delete button in /user/pages.html template (incorrect value). commit84f3ede736
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 22:42:02 2022 +0100 refactor site-setting route to get snippets from /storage/snippets. commita76f837eea
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 22:41:38 2022 +0100 add insert-snippet functionality to djula:view package. commita1d1042d4c
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 22:40:12 2022 +0100 update first-run code -- refactor site-wide snippet file. commite0a1bf7cb1
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 22:37:19 2022 +0100 update /layout/default.html template (prep. for snippets update). I'm changing the snippet to be stored as HTML instead of JavaScript. I'm, also, storing the snippet in /storage/snippets instead of /static. This makes it easier to hook it up to my Umami instance in the future if Nic wants to do that. commit4156b0eee0
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 22:05:49 2022 +0100 remove single file upload route and refactor multi-upload route. commite8831de9f4
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 22:04:58 2022 +0100 update URL for images in build-thumbnail-path djula filter. commit4b5831452d
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 22:04:28 2022 +0100 add thumbnail to /archive.html template. commitbb35111dc9
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 22:01:18 2022 +0100 remove single file upload from dashboard and storage HTML templates. From an end-user perspective, I don't think Nic will care about the difference. She'll want the easiest option and uploading a single file via the multi-upload form -- with no file name input -- is the easiest way to go about this. I say this without actually speaking to her about this so I might be wrong on this and need to go back in and change things. commit44bdec6570
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 20:07:55 2022 +0100 implement the /edit/archive defroute -- updates the entries text. commitfb8f68e468
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 19:59:53 2022 +0100 implement the edit archive keywords functionality (web.lisp). commitc313b4aff4
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 19:59:33 2022 +0100 update keyword form in /user/edit-archive.html template. commite53a2990c0
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 19:46:09 2022 +0100 refactor update archive thumbnail functionality (change file name). The code now removes the old thumbnail file, stores the new one and updates the database entry. I found it easier to keep track of the changes whilst developing by doing this. commit81a0fb8e40
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 19:44:27 2022 +0100 update /user/edit-archive.html (prep. for title and keywords). commit7328a719fe
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 18:25:37 2022 +0100 implement the edit archive thumbnail functionality (web.lisp). This does not include the Meilisearch integration. That will come in later commits after I've got the base behaviour for the website sorted out. commit82e8428695
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 18:23:51 2022 +0100 refactor and remove code regarding the /storeage routes in web.lisp. I removed old 'format' function called, changed file checks from using the files on disk to the database. commitca4a540141
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 18:21:43 2022 +0100 edit /user/edit-archive.html template (thumbnail section). This is preparation for implementing the update archive entry thumbnail functionality in the web.lisp file. commit379bbdc8b4
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 18:20:05 2022 +0100 implement update-archive-entry-property in nera package. commit8d55ed7d61
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 15:53:59 2022 +0100 refactor /storage (I.E. get files) routes and /edit/archive/:slug. I added a new route to get files from the /archive directory but needed to expand on the original /storage/view/:slug route so I could seperate out the two (/storage/media and /storage/archive) directories. I added a check to make sure the archive entry requested (/edit/archive/:slug) exists and return a 404 if it doesn't. commitc69c165a92
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 25 15:53:15 2022 +0100 add edit thumbnail section to /user/edit-archive.html template. commit57a6710e92
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 22:22:02 2022 +0100 block out the route for editing an archive entry (end-of-session). I'm going home after this commit. I've blocked out the routes I need to work on when I get back to this -- so I know where I am with it. commit93a59aaad4
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 22:18:23 2022 +0100 implement /edit/archive/:slug defroute (HTTP GET request). commit02df53e7f4
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 22:17:18 2022 +0100 add and block out /user/edit-archive.html template. I still need to add a section for managing the archive entries thumbnail but the basics are in place. commitf205dc3252
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 22:16:54 2022 +0100 remove old code from /user/edit-page.html template. commiteb9d2e3290
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 21:57:10 2022 +0100 implement /archive/delete/entry defroute (HTTP POST request). commitee336d3c83
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 21:56:35 2022 +0100 implement delete-archive-entry in nera package. commitf89585ab46
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 21:56:01 2022 +0100 update form to delete archive entry in /user/archive.html template. commitbba7cf4499
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 21:04:49 2022 +0100 implement the /view/archive/:slug route and add archive-entry.html. commita7d0ae54ad
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 20:42:48 2022 +0100 finish implementing the /create/archive-entry (without search). I have left out the Meilisearch integration because I won't to get the base behaviour sorted before I start integrating Meilisearch into the website. commit5542014a1e
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 20:41:58 2022 +0100 sort out code layout in view.lisp file. commitbaf1ec1f39
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 20:38:37 2022 +0100 add format-filename and format-keywords in utils package. These functions are to help with standardising on the naming formats when storing files and keywords (for Meilisearch database). The asciify and slugify functions are not enough. They either leave spaces in the file names or the file extension gets lost in the standardising process. Going forward, file names should look like 'this-is-an-example.png' instead of either 'this is an Example.png' or 'this-is-an-example-png'. commit3dc6a812bf
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 20:34:35 2022 +0100 reorder file load order in ritherdon-archive.asd file. I was getting errors when trying to quickload the system because a custom djula filter (defined in view.lisp) couldn't find one of the specific ritherdon-archive packages/files. I can't remember which one because I made the change earlier in the day and didn't commit the change at the time. commit7d9e29bac8
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 01:25:24 2022 +0100 add the /user/create-archive.html template. This is a rough but working copy. This is part of an end-of-session commit. commit5cc14f74bf
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 01:24:00 2022 +0100 create /user/archive.html and /arctice.html templates. Both templates aim to list out all the archives stored in the system. The /user/archive.html template, also, includes admin/back-end features like edit and delete links/controls. commitc42d169d84
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 01:20:54 2022 +0100 begin implementing CRUD routes for /archive section (web.lisp). This is part of an end-of-session commit. I'm currently working on the /create/archive-entry section (most the HTTP POST request). I still need to start the view archive, edit archive and delete archive parts. I've not even looked at the Meilisearch stuff. For now, the aim is to get the website's main database (nera.db) working and then integrate the Meilisearch stuff in after that. commit23365343b1
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 01:19:20 2022 +0100 update dashboard.html template (add archive links and upload forms). commitffd483411c
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 01:15:41 2022 +0100 start implementing the CRUD functions for the /archive routes. I still need to write the edit and delete functions (I.E. the 'U' and 'D' in 'CRUD') to have a basic CRUD system in place for this section. I'm commiting this as an part of an end-of-session commit, hence the partical amount of work done. commit58b03ae8f2
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 01:13:44 2022 +0100 add create-time-stamp-id function in utils package. The only intended use for this function is to generate an Id. number which will be used in the Meilisearch database and linking it to Nera's database (I.E. this site's main database). commitc1e664b631
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 24 01:13:09 2022 +0100 remove unused code from archive-entry class. commiteff71bcbfe
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 23 18:19:44 2022 +0100 import archive package to nera package. commitd6cc11dc03
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 23 18:17:11 2022 +0100 create archive package, archive-entry model and add to .asd file. This is a rough sketching out of what the model/data needs to look like. This model is what will be connecting the site's archive (Nic's artwork entries) with the Meilisearch service -- running alongside each other. I don't know how much this model is going to change but expect it to in future commits. commit6a67a7cfc7
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 23 16:55:30 2022 +0100 add thumbnail features to delete and rename storage file routes. commit80aab44c00
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 23 16:40:20 2022 +0100 refactor 'create thumbnail' code in web.lisp to call from utils. I've applied the changes to the singe and multi upload defroutes. commit592c38759f
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 23 16:39:28 2022 +0100 move create-thumbnail code to utils (reduce duplicated code). commit13d1e31c13
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 23 16:19:19 2022 +0100 rafactor /storage/view route and add a route/process for thumbnails. When uploading a file, a thumbnail is made (if an image, using the Image Magick program with Bash). This file is not stored in the database. It is a file-system only thing. The reason for adding this is some I can create HTML templates/defroutes which show a list of the images in /storage/media without saturating the viewers bandwidth. commit2c069573db
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 23 16:14:23 2022 +0100 update /user/storage.html template to use build-thumbnail filter. I, also, removed code made redundant by build-thumbnail filter. commit44c7ebbc90
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 23 16:13:46 2022 +0100 add @build-thumbnail (djula) filter in view.lisp file. commit6d8fa33b97
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 23 16:13:26 2022 +0100 remove get-latest-file-type code from storage package. commita15619de56
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 23 16:12:38 2022 +0100 add woo server to ritherdon-archive.asd file. Added it so I can switch between hunchentoot and woo whenever I want. No real reason to choose one over the other at the moment. commit7a6f330497
Author: Craig Oates <craig@craigoates.net> Date: Thu Sep 22 21:28:54 2022 +0100 begin defroutes for viewing files in /storage/media. I need to work on this functionality a bit more. This is an end of session commit. Started it and it works but needs expanding. I could do with adding file-type checks and changing 'octet/stream' to something like 'image/png' depending on the file-type (storage in DB). This will stop the browser from downloading every file in /storage/media and allow the files which can be viewed in the browser (like images). commit104ac264a8
Author: Craig Oates <craig@craigoates.net> Date: Thu Sep 22 21:07:59 2022 +0100 change /storage routes to use slugs over filenames (HTTP POST). commit4c99109388
Author: Craig Oates <craig@craigoates.net> Date: Thu Sep 22 21:07:34 2022 +0100 export delete-storage-file from nera package. commit1cf86c674c
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 20 20:35:05 2022 +0100 end-of-session-commit: working on /storage/delete/:slug defroute. Need to test it and make sure the logic in the various 'cond' sections. commit191f5b2c22
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 20 20:34:46 2022 +0100 add delete-storage-file function in nera package. commit0331999136
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 20 20:07:26 2022 +0100 refactor code calling get-storage-file and add storage/rename route. commitf3d856f7f4
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 20 20:05:29 2022 +0100 add keyword args to get-storage-file and write rename-storage-file. These changes are in the nera package. I, also, got Emacs to auto format the file -- that is why is are loads of line changes in the diff. commitb9848197e7
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 20 19:25:47 2022 +0100 add storage-upload defroutes (single and multiple) in web.lisp file. commitd0ff0c812b
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 20 19:23:25 2022 +0100 update storage and dashboard HTML templates (add & get features). commit953bfc5fb0
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 20 19:21:54 2022 +0100 implement the 'add and get' functionality for storage routes. commitf5549e17a1
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 20 19:21:00 2022 +0100 add files package (in /src/models and ritherdon-archive.asd file). commit6e7d2b6845
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 23:35:51 2022 +0100 put copyright text in <p> tags (in footer.html template). The text was wrapped in anything and was rendering weirdly in the browser's inspector. commit0ce1aef3e4
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 23:34:44 2022 +0100 change how site-wide-snippet file is stored and accessed. The site-wide-snippet file is now a .js file and stored in /static/js after the website's first run. commit63e3919970
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 23:33:15 2022 +0100 update .gitignore to ignore site-wide-snippet.js in /static/js. This file is copied to here on website's first run. This stops any changes I make to the file during dev. time from clogging up the commit history. commit015eda2b0a
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 23:11:35 2022 +0100 initial steps for /storage/management defroute. This is an end-of-session commit. I've got the route up and running with the /user/storage HTML template rendering in the browser and listing out the files in storage. I could do with adding a model and storing the files meta-data in the database. This page could do with rendering the images and placeholders (E.G.generic text file icon) for non-image files. commita89686c05f
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 22:58:16 2022 +0100 add file uploads section to /user/dashboard.html template. commitc4f9f52ba8
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 22:57:45 2022 +0100 add /storage/upload defroutes (single and multi file uploads). commita855d9db90
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 22:54:53 2022 +0100 refactor store-file functions to include more file types. The original code was very ham-fisted in how it dealt with storing files -- based on their file-types. The changes made here improves on it and the store-file (for both raw and /storage paths) functions now accepts more file types because of it. commit8106b5a5c5
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 22:53:41 2022 +0100 rename site-settings function to get-site-settings in nera package. The function call 'site-settings' was clashing with class and package name 'site-settings'. commitd31c4ab38a
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 19:27:38 2022 +0100 remove old defroutes. commitf626557b97
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 19:21:10 2022 +0100 add system-data to data passed to the HTML GET routes in web.lisp. System-data is a collection of the system settings and page data in the database. It minimises the function calls in the defroute macros. commit2bb7d053e6
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 19:19:33 2022 +0100 clean up old and unused code in HTML templates. It was getting out of hand and difficult to navigate around the pages when viewing them in the browser. commite9792a6fa0
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 19:18:55 2022 +0100 import archivo in main.css file. commit4c1b99fa08
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 19:17:40 2022 +0100 add pages in init-db and write system-data func. in nera package. commite765c3b24e
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 19:16:38 2022 +0100 add and populate header and footer HTML templates. The HTML is basic, just to get the sections to a usable state. commit9b8c5a7ca5
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 19 19:16:20 2022 +0100 add archivo font. commitbb5f8fb482
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:13:33 2022 +0100 update web.lisp, mostly around site settings and first-run set-up. commit2f819de9a2
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:12:14 2022 +0100 add init-storage process and 'raw-path' I/O functions. commitf78beb7ea4
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:11:14 2022 +0100 add CRUD features for 'page' table in DB and expand init-db process. commit161e9c6169
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:09:54 2022 +0100 remove old code and update forms in /user/site-settings.html file. commitd0426a9494
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:07:45 2022 +0100 add form for /page/set-nav-menu defroute in /user/edit-page.html. I, also, changed templated data to use 'db-data' element in databag passed to the djula HTML template. commit7e2f04d370
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:07:23 2022 +0100 add can-delete check to /user/pages.html template. commitaacfcba654
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:06:46 2022 +0100 add favicon link to /layouts/default.html template. commit9a50938c0d
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:06:07 2022 +0100 implement get-image-dimensions function in utils package. commit4bac1e3db3
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:05:26 2022 +0100 add enable-site-logo and site-name slots to site-settings class. commit6ecd555712
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:04:55 2022 +0100 addd validation.lisp and pages.lisp files to ritherdon-archive.asd. commite3471e82ea
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:03:14 2022 +0100 update .gitignore to ignore /static/images and /site-wide-snippet. These files will be created and populated via the site first-run set-up process. This should stop the commit history getting clogged up with needless and irrelevant changes in these files. commit4736db9bd2
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:02:06 2022 +0100 add validation package. commite4612d0711
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 18 23:01:37 2022 +0100 create pages package and page class (for Mito to map to DB). commitb26b296e89
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 18:45:47 2022 +0100 stubb out sections in site-settings.html (not implemented yet). The sections are more a check-list at this point. Things I need to implement. I've stubbed them out and going to call it a day. They HTML I've used is rough but it give me something to work on next time I work on this project. commit94e326a292
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 18:19:02 2022 +0100 expand the site-settings.html template. This is template still needs a lot of work done to it. The additions in this commit focus on the set home page and enable/disable the sign-up features. commit7e892f25b1
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 18:18:35 2022 +0100 add {{content}} section to index.html template. commitd9e09c52f6
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 18:16:20 2022 +0100 add site-settings functionality in web.lisp file. This is work-in-progress for the site-settings section of the website. These additional features in this commit focus on setting home page and enabling/disabling the sign-up features. commitf1c79a9ecd
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 18:15:38 2022 +0100 add integer-to-checkbox djula-filter (view.lisp). commiteb62ade5be
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 18:14:39 2022 +0100 add condition check to checkbox-to-bool and remove bool-to-checkbox. commit6d4d16ec45
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 18:13:50 2022 +0100 implement update-enable-sign-on-settings and set-home-page in nera. commitbfcef81763
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 18:10:58 2022 +0100 add home-page slot to site-settings class. commitcb51a83605
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 15:04:00 2022 +0100 implement /page/delete defroute and update redirects to /user/pages. commit45ef9fa84a
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 15:03:03 2022 +0100 add role check and update 'pages' link in /user/pages.html template. commitb9180da6e2
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 14:07:04 2022 +0100 implement /pages and /view/page/:slug defroutes in web.lisp file. commitf4744f14b7
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 14:04:47 2022 +0100 update links to /page and /pages and add HTML templates. The templates added allow the viewer to view the 'pages' individually or as a list (index page of 'pages' basically). commit1c6781c945
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 13:27:47 2022 +0100 implement the 'edit pages' functionality in web.lisp file. commitd6d5a58792
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 13:27:08 2022 +0100 add /user/edit-page.html template. This HTML template is very rough -- just basics to make it operational. commitf0356b00f0
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 13:25:55 2022 +0100 add /user/pages.html template. This template displays a list of pages stored in the system in the /storage directory. It is very rough -- only bare basics to get the page operational. commit681033b1cd
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 13:25:23 2022 +0100 add /pages link to /user/dashboard.html template. commit6b09c3e81c
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 11:51:51 2022 +0100 update .gitignore to ignore the /storage directory. This is so the data/pages/files/content used for testing the website don't get mingled with the code-base. commit3937b626e8
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 11:50:43 2022 +0100 finish implementing the /create/page defroute (HTTP POST). commita477470a90
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 11:48:16 2022 +0100 remove 'meta-data' from create-page.html template's HTML form. This is prep. for getting a working version of the /create/page defroute. My thinking at the minute is the logged in user can create a 'page' which is a page specific to the website and a 'post' of some sort for an actual archive entry. I don't know how desirable or feasible this design/approach is but I'm trying it to find out. commit3dbb603831
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 11:46:14 2022 +0100 add slugify function to utils package. This replaces white spaces in a string with a hyphen ('-'). I prefer file names to be stored with no white space, which is where I intend to use this function the most. commit6fce318c87
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 17 11:44:36 2022 +0100 add storage package, copied from other proj. so already implemented. commit9e308ab92e
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 16 19:16:00 2022 +0100 add /user/create-page.html template and integrate Tinymce editor. The code in this commit is very rough but it works. I just got the basics up and running. The template posts the content but the back isn't implemented yet. At this moment in time, I need Nic to sign-off on what she wants from this page. There is a chance this template might not exist in the future. commit9222d347fd
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 16 19:14:27 2022 +0100 add /user/site-settings.html template. The code at the moment is very rough. I've only got the 'Enable Sign-Up' section set-up (front-end). The back-end part is not done yet . commit6c80f146f3
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 16 19:12:23 2022 +0100 add Tinymce (rich text editor) files to /static/js directory. Tiny: https://www.tiny.cloud/ Doc's: https://www.tiny.cloud/docs/tinymce/6/ commit875bd28841
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 16 19:11:19 2022 +0100 add logout, delete account and create page links to dashboard.html. commitbdd86d4ad7
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 16 19:10:54 2022 +0100 add Sign-up link to index.html template. commit11e5e6a08e
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 16 19:08:32 2022 +0100 create (and initial content) for /user/index.html template. The code in here is for the site's administrator to see a list of all the users with an account with edit and delete options. There is, also, a section to create new users (non-admin. accounts). commit19bbba0f3e
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 16 19:06:14 2022 +0100 add username check to /sign-up defroute. Just an extra check in amongst what's already there. If the username the new sign-up attempt has entered matched one already in the database, a message is relayed stating as much and redirects back to the sign-up page. commit9724eda5fd
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 16 19:04:46 2022 +0100 add more functionality for user management (admin. and normal). I forgot to add this to the previous commit. commit793c5d544b
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 16 19:02:05 2022 +0100 add admin. functions for 'users' section and create /site-settings. The site's admin. can now create and delete accounts of all other users. The admin. can, also, change the other passwords of the other users. commit7349c4b9f5
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 16 19:01:14 2022 +0100 remove unused code and reformat some comments in web.lisp file. commit1e26f1ea8d
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 16 18:59:45 2022 +0100 add get-all-users function and change arg's to &key for update-user. commit68792eac19
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 16 18:58:32 2022 +0100 add alert message data to "/" defroute. commitd025b5d0a0
Author: Craig Oates <craig@craigoates.net> Date: Thu Sep 15 15:23:37 2022 +0100 start to add functionality for signing new users up. commit428a0a6ba7
Author: Craig Oates <craig@craigoates.net> Date: Thu Sep 15 15:20:44 2022 +0100 add functionality for dealing with site-settings (table in DB). I've added some helper functions to help with creating a database, making sure its tables exist, too. commit3596a696db
Author: Craig Oates <craig@craigoates.net> Date: Thu Sep 15 15:18:28 2022 +0100 create initial-setup.html template. commit177521aa34
Author: Craig Oates <craig@craigoates.net> Date: Thu Sep 15 15:16:24 2022 +0100 add bool to checkbox convertion funcitons to utils pacakge. The functions are helper functions for when dealing with SQLite3. Because SQLite3 doesn't have a Boolean data-type, I have to store the 'true' and 'false' values as integetes. 0 == false 1 == true. commit9a665c5c4e
Author: Craig Oates <craig@craigoates.net> Date: Thu Sep 15 15:15:38 2022 +0100 create and wire-up site-settings model and package (for DB). This model is used by mito to create a table in the database. commit7e84b86291
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 13 18:29:10 2022 +0100 refactor functions moved from auth to nera and update init-db setup. The update part refers to the redirect when a database is found whilst making a HTTP POST request, as part of the init-db process (A.K.A. website's first run procedure). The auth to nera change is because I didn't catch all of the moves in a previous commit. The image I was running at the time still had the auth version so it wasn't showing any errors. I spotted this one when trying to run a new image. commita4d3f35306
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 13 05:11:49 2022 +0100 add alert section to default.html template. commit249f86b88e
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 13 05:11:34 2022 +0100 add link to /dashboard in index.html template. commit777458ee37
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 13 05:11:07 2022 +0100 code clean-up in some HTML templates. commit25142d5049
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 13 05:04:51 2022 +0100 refactor web package to use auth, util and status-code packages. The web package was already using code from some of these packages. This commit moves some of the code in the web package into one of the various referenced packages and also has 'web' utilise the new features in those pacakges also. The biggest shift is in how the alert-messages are handled (store in ningle:*session* across HTTP requests) and how auth. is handled -- mostly the redirects functionality. commite6162306ef
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 13 05:03:29 2022 +0100 add alert-message functions in utils package. These functions just make it easier to set, get and reset the alert-message in the ningle:*session* variable. commit89ad7fb269
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 13 05:03:10 2022 +0100 code formatting in db package. commitb469063f74
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 13 05:02:30 2022 +0100 remove code I moved to the nera package from auth package. commitbf8b79021e
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 13 05:00:44 2022 +0100 export define-constant macro in app-constants. The main reason is so the constants defined in the status-codes packages can use it. I've put the (HTTP) status codes in their own package because they are a self-contained thing. I find it easier to work with them this way. commit0941922f57
Author: Craig Oates <craig@craigoates.net> Date: Tue Sep 13 04:58:58 2022 +0100 add nera and status-codes packages. nera is responsible for the database stuff which is not part of Caveman2. The status-codes package is a list of constants representing the various HTTP status codes -- with an explanation of what they are for. commit490a79a356
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 12 07:49:41 2022 +0100 add log-in and start account setting functionality. commit2aaefdbf5e
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 12 07:48:25 2022 +0100 add or update HTML templates (all based around user/accounts). These are very rough templates but they serve as a way to get the logged-in user stuff functionality started. commit3b6afd8a4a
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 12 07:46:54 2022 +0100 add password slot to user class. I was having trouble trying to get hermetic and mito-auth to work together. I decided to just use normal mito and leave hermetic to the authentication/authorisation stuff. commit7e32795392
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 12 07:46:00 2022 +0100 re-organise bits of the code (mostly packages are references). commite6ef8bec34
Author: Craig Oates <craig@craigoates.net> Date: Mon Sep 12 07:43:38 2022 +0100 rename authentication to auth (file and package). 'Auth' doubles up as authentication and authorisation. It reads better from that perspective. commitc8074c821b
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 11 07:28:47 2022 +0100 make back-up copy of cookie cutter code, for reference. There was snippets of code in the code I liked. I'm going to keep it around for a bit to see if I can work some of it in to the new code. I'm most interested in the deployment code. commitd6dbcf7ec4
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 11 07:27:52 2022 +0100 create Caveman2 project (using Caveman2's project generator). commit51d2213aae
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 11 07:23:58 2022 +0100 delete site stubbed out with the cookie cutter app. I tried it and I find it easier to work with Caveman2. I didn't realise this templating program is built around Hunchentoot -- whereas Caveman2 builds on top of. So, I'm moving the code to back to Caveman2 -- where I originally started. It looks like a waste of time but the knowledge of having a look at the other stuff has been helpful. commit9c251a1698
Author: Craig Oates <craig@craigoates.net> Date: Sun Sep 11 07:23:10 2022 +0100 update licence. commit6257e7ec94
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 10 22:18:51 2022 +0100 add create-user.sh script. This script adds a user to the database based on it's inputs (prompted for the person running the script). The intention is to add a user to the database easier (especially on a live production server with just the CLI). commitf5403b6cc2
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 10 22:17:43 2022 +0100 populate user class. This should have been in the previous commit. I forgot to add it. commitff0c121e63
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 10 22:16:55 2022 +0100 create user class and add to database table. commitc5ba5b1e4b
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 10 19:18:16 2022 +0100 switch Fiveam to parachute (testing framework). There is no immediate or obvious reason for the change. I just prefer to work with parachute. commit8d52eaf0a6
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 10 19:14:15 2022 +0100 update .gitignore (databases and /bin directory). commit7d3d3e57d5
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 10 19:09:43 2022 +0100 create Caveman2 project using vindarel's cl-cookieweb program. https://github.com/vindarel/cl-cookieweb (for GitHub Repo. and instructions). The reason for using this is because it makes it easier to run the website as a standalone thing. You don't need to link it up to Quicklisp's /local-project directory. It has scripts to help you build the binaries and to run the website (as a standalone) thing. I, also, hadn't use this 'cookie cutter' program before so it's a good time to get my feet wet. commit3177d956f1
Author: Craig Oates <craig@craigoates.net> Date: Sat Sep 10 18:37:18 2022 +0100 go back to using Common Lisp and Caveman2. The admin. backend for Django isn't as easy to get to where I want it, compared to Caveman2. The trade-off is Common Lisp isn't a mainstream language so it does reduce the level of ease other might have when/if they join the project. commitee1bf2f927
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 9 02:40:08 2022 +0100 create Django project. The files included here are just the ones created with the django-admin CLI. commit851980f784
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 9 02:39:24 2022 +0100 copy a Python-base template into .gitignore. commit24b396d47b
Author: Craig Oates <craig@craigoates.net> Date: Fri Sep 9 02:34:39 2022 +0100 delete caveman2 (initial install) code. I'm going to try this with Python and Django first. Because it's not my personal project, it might be better to use a language which is more mainstream. It should reduce the 'Bus Factor'.
@ -1,8 +1,26 @@
|
||||
*.FASL |
||||
*.fasl |
||||
*.dx32fsl |
||||
*.dx64fsl |
||||
*.lx32fsl |
||||
*.lisp-temp |
||||
*.dfsl |
||||
*.pfsl |
||||
*.d64fsl |
||||
*.p64fsl |
||||
*.lx64fsl |
||||
*.x86f |
||||
*~ |
||||
.#* |
||||
*.lx32fsl |
||||
*.dx64fsl |
||||
*.dx32fsl |
||||
*.fx64fsl |
||||
*.fx32fsl |
||||
*.sx64fsl |
||||
*.sx32fsl |
||||
*.wx64fsl |
||||
*.wx32fsl |
||||
*.db |
||||
/bin/ |
||||
/storage/media/ |
||||
/storage/archive/ |
||||
/storage/snippets/ |
||||
/storage/pages/ |
||||
/static/images/favicon.* |
||||
/static/images/site-logo.* |
||||
/snapshots |
||||
|
@ -1,9 +1,187 @@
|
||||
* Ritherdon Archive |
||||
* Nicola Ellis and Ritherdon Archive |
||||
|
||||
An archive of Ritherdon. I need to speak to Nic more about what this |
||||
means. |
||||
This is an website, written in Common Lisp and built on top of the Caveman2 |
||||
framework. Its main intention is to be the digital archive for the work |
||||
produced by [[http://www.nicolaellis.com][Nicola Ellis]] during her time |
||||
working alongside [[https://ritherdon.co.uk/][Ritherdon]]. |
||||
|
||||
* Project Summary |
||||
[[https://neandr.nicolaellis.com]] |
||||
|
||||
This is a website written in Common Lisp and the Caveman2 framework. The |
||||
databased it uses in SQLite3 and Steel Bank Common Lisp (SBCL). |
||||
* Overview of Technology Used |
||||
|
||||
Below is a **non-exhaustive** list of the tech. used to build and run this |
||||
website: |
||||
|
||||
- [[https://lispcookbook.github.io/cl-cookbook/][Common Lisp]] |
||||
- [[https://www.quicklisp.org/beta/][Quicklisp]] |
||||
- [[Steel Bank Common Lisp][http://www.sbcl.org/]] (SBCL) |
||||
- [[https://github.com/fukamachi/caveman][Caveman2]] |
||||
- [[https://github.com/fukamachi/woo][woo]] (Common Lisp server behind Nginx) |
||||
- [[https://github.com/fukamachi/mito][Mito]] (ORM) |
||||
- [[https://github.com/fukamachi/sxql][SXQL]] (SQL Generator, used alongside Mito) |
||||
- [[https://www.sqlite.org/index.html][SQLite3]] |
||||
- [[https://github.com/mmontone/djula][Djula]] (Common Lisp port of the Django templating language) |
||||
- [[https://www.debian.org/][Debian 11]] |
||||
- [[https://www.nginx.com/][Nginx]] |
||||
- [[https://www.meilisearch.com/][Meilisearch]] (Website's Search engine) |
||||
|
||||
For a complete list of packages used by Common Lisp, look at the |
||||
[[/return-to-ritherdon/ritherdon-archive/src/branch/unstable/ritherdon-archive.asd][ritherdon-archive.asd]] file. |
||||
|
||||
* System Overview |
||||
|
||||
The complete system is broken into two services: |
||||
|
||||
1. Ritherdon Archive (the main site) |
||||
2. Meilisearch (the separate search system/service/instance) |
||||
|
||||
From an end-user's perspective, they shouldn't be able to tell the Meilisearch |
||||
service is part of the overall system. When an end-user uses the Search |
||||
features on the main site, the main site will make the requests to the |
||||
Meilisearch service. From there, the Meilisearch service will return its |
||||
results to the main site -- on the end-user's machine. You should see this |
||||
closed-loop in the diagram below. |
||||
|
||||
#+begin_src mermaid |
||||
graph TD |
||||
Request((Request)) --> Server |
||||
Server{Nginx} --> Archive |
||||
Server -.-> Meilisearch |
||||
Archive -. Search terms sent to server \n as their own requests .-> Request |
||||
Request -.-> Server |
||||
Meilisearch -. Results returned to Archive \n on clients machine .-> Archive |
||||
Archive --> Response((Response)) |
||||
#+end_src |
||||
|
||||
When it comes to the main site (Ritherdon Archive), it stores the archive data |
||||
in the =/storage= directory and the SQLite3 database. Both are kept |
||||
in-sync. by the main site -- as part of its feature-set. |
||||
|
||||
#+begin_src mermaid |
||||
graph TD |
||||
Request((Request \n /Response)) <--> Server |
||||
Server{Nginx} <--> Archive |
||||
Archive <-.-> DB[(Database)] |
||||
Archive <-.-> Storage[/Files in /storage directory/] |
||||
#+end_src |
||||
|
||||
* Installation |
||||
|
||||
To see what is being called, please read the [[/return-to-ritherdon/ritherdon-archive/src/branch/unstable/makefile][makefile]]. If you are unsure how |
||||
to set-up an environment for developing in Common Lisp, please use the |
||||
following link: |
||||
|
||||
- [[https://lispcookbook.github.io/cl-cookbook/getting-started.html][Common Lisp Cook Book: Getting Started]] |
||||
- [[https://docs.meilisearch.com/learn/getting_started/quick_start.html#setup-and-installation][Meilisearch Doc's]] (Install Guide) |
||||
|
||||
Otherwise, just use the makefile. |
||||
|
||||
#+begin_src bash |
||||
git clone https://git.abbether.net/return-to-ritherdon/ritherdon-archive.git |
||||
cd ritherdon-archive |
||||
sudo make install |
||||
make lisp-install |
||||
make quicklisp-add |
||||
make search-install # Installs Meilisearch. |
||||
#+end_src |
||||
|
||||
*Note:* The ~make search-install~ command adds the ~meilisearch~ binary to |
||||
=/usr/bin/=. If you want to uninstall Meilisearch, you will need to delete it |
||||
from =/usr/bin=. Run ~sudo rm /usr/bin/meilisearch~, to delete it. |
||||
|
||||
* Run System on Local Machine |
||||
|
||||
Because the system consists of two systems running in tandem, you will need to |
||||
have two terminals open to run them separately -- and see the logs for each |
||||
service. |
||||
|
||||
** Meilisearch |
||||
|
||||
Usually, you get this part of the system up and running before you get the |
||||
main site working -- mostly because of the tasks flow easier -- but it's not |
||||
essential to start this service first. |
||||
|
||||
#+begin_src bash |
||||
# Make sure meilisearch is added to your /usr/bin/ directory. |
||||
meilisearch --no-analytics |
||||
#+end_src |
||||
|
||||
If you enter =http://localhost:7700= in your browser, you should see the |
||||
meilisearch search page. Use =ctrl-c= to stop the service. |
||||
|
||||
** Main Site (Ritherdon Archive) |
||||
|
||||
To run the main site (Ritherdon Archive), start Steel Bank Common Lisp (SBCL) |
||||
by running ~rlwrap sbcl~ in your terminal. When SBCL has finished loading, run |
||||
the following, |
||||
|
||||
#+begin_src common-lisp |
||||
(ql:quicklisp :ritherdon-archive) |
||||
(search:set-filter-attributes) ; Configures the Meilisearch service. |
||||
(ritherdon-archive:start :server :woo) |
||||
#+end_src |
||||
|
||||
Then go to =http://localhost:5000= in your browser. |
||||
|
||||
To stop the program, enter ~(ritherdon-archive:stop)~ in SBCL. |
||||
|
||||
* Run System on (Prod.) Server |
||||
|
||||
This section builds on the one above (running on local machine). The main |
||||
difference is getting the system to run on Nginx and as a Systemd service. |
||||
|
||||
The first thing to do is replace the parts which say ~<INSERT USERNAME HERE>~ |
||||
and ~<INSERT URL HERE>~ in the files in =/conf=. Those values are specific to |
||||
your deployment environment. At the time of writing, there should be four |
||||
files in =/conf=. They are: |
||||
|
||||
1. =meilisearch.conf= (the config. file for Nginx) |
||||
2. =meilisearch.service= (the .service file for Systemd) |
||||
3. =ritherdon-archive.conf= (the config. file for Nginx) |
||||
4. =ritherdon-archive.conf= (the .service file for Systemd) |
||||
|
||||
After you have entered your details into the =.conf= and =.service= files, |
||||
you can start to copy the files into their new locations. |
||||
|
||||
** Set-Up Meilisearch |
||||
|
||||
#+begin_src bash |
||||
# Nginx |
||||
sudo cp ~/ritherdon-archive/conf/meilisearch-prod.conf \ |
||||
/etc/nginx/sites-available/meilisearch-prod.conf |
||||
sudo ln -s /etc/nginx/sites-available/meilisearch.conf \ |
||||
/etc/nginx/sites-enabled/ |
||||
sudo systemctl restart nginx.service |
||||
|
||||
#Systemd |
||||
sudo cp ~/ritherdon-archive/conf/meilisearch.service \ |
||||
/etc/systemd/system/meilisearch.service |
||||
sudo systemctl daemon-reload |
||||
sudo systemctl enable meilisearch.service |
||||
sudo systemctl status meilisearch.service |
||||
#+end_src |
||||
|
||||
** Set-Up Ritherdon Archive (Main Site) |
||||
|
||||
#+begin_src bash |
||||
# Nginx |
||||
sudo cp ~/ritherdon-archive/conf/ritherdon-archive.conf \ |
||||
/etc/nginx/sites-available/ritherdon-archive.conf |
||||
sudo ln -s /etc/nginx/sites-available/ritherdon-archive.conf \ |
||||
/etc/nginx/sites-enabled/ |
||||
sudo systemctl restart nginx.service |
||||
|
||||
#Systemd |
||||
sudo cp ~/ritherdon-archive/conf/ritherdon-archive.service \ |
||||
/etc/systemd/system/ritherdon-archive.service |
||||
sudo systemctl daemon-reload |
||||
sudo systemctl enable ritherdon-archive.service |
||||
sudo systemctl status ritherdon-archive.service |
||||
#+end_src |
||||
|
||||
* A Note on Using Ritherdon Archive |
||||
|
||||
This README has focused only on the developer side of the project. This is |
||||
deliberate. I just haven't had the time to write any documentation on that |
||||
side of the site or is the budget there to do so. Hopefully, this will change |
||||
but at the time of writing (Nov. 2022), there is nothing in the repo's wiki. |
||||
|
@ -0,0 +1,8 @@
|
||||
server { |
||||
listen 80; |
||||
listen <INSERT URL HERE>; |
||||
server_name _; |
||||
location / { |
||||
proxy_pass http://127.0.0.1:7700; |
||||
} |
||||
} |
@ -0,0 +1,10 @@
|
||||
[Unit] |
||||
Description=Meilisearch |
||||
After=systemd-user-sessions.service |
||||
|
||||
[Service] |
||||
Type=simple |
||||
ExecStart=/usr/bin/meilisearch-beta --no-analytics --http-addr 127.0.0.1:7700 --env production --master-key <INSERT KEY HERE> |
||||
|
||||
[Install] |
||||
WantedBy=default.target |
@ -0,0 +1,30 @@
|
||||
upstream woo { |
||||
server 127.0.0.1:5000; |
||||
} |
||||
|
||||
server { |
||||
listen 80; |
||||
server_name <INSERT URL HERE>; |
||||
|
||||
root ~/quicklisp/local-projects/ritherdon-archive/templates/; |
||||
|
||||
# General request handling this will match all locations |
||||
location / { |
||||
|
||||
# check if with it's a directory and there'a an index.html |
||||
# if so, rewrite the url to include it and stop processing rules. |
||||
if (-f $request_filename/index.html) { |
||||
rewrite ^(.*) $1/index.html break; |
||||
} |
||||
|
||||
# Define custom HTTP Headers to be used when proxying |
||||
proxy_set_header X-Real-IP $remote_addr; |
||||
proxy_set_header Host $host; |
||||
|
||||
# if the requested file does not exist then |
||||
# proxy to the woo server cluster |
||||
if (!-f $request_filename) { |
||||
proxy_pass http://woo; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,13 @@
|
||||
[Unit] |
||||
Description=Nicola Ellis and Ritherdon Archive website. |
||||
After = syslog.target network.target |
||||
|
||||
[Service] |
||||
ExecStart=/usr/bin/sbcl --eval '(ql:quickload :ritherdon-archive)' --eval '(setf (osicat:environment-variable "APP_ENV") "production")' --eval '(ritherdon-archive:main)' |
||||
Restart=always |
||||
RestartSec=10 |
||||
KillSignal=SIGINT |
||||
User=<INSERT USERNAME HERE> |
||||
|
||||
[Install] |
||||
WantedBy=multi-user.target |
@ -0,0 +1,16 @@
|
||||
LISP ?= sbcl
|
||||
|
||||
all: test |
||||
|
||||
run: |
||||
rlwrap $(LISP) --load run.lisp
|
||||
|
||||
build: |
||||
$(LISP) --non-interactive \
|
||||
--load ritherdon-archive.asd \
|
||||
--eval '(ql:quickload :ritherdon-archive)' \
|
||||
--eval '(asdf:make :ritherdon-archive)'
|
||||
|
||||
test: |
||||
$(LISP) --non-interactive \
|
||||
--load run-tests.lisp
|
@ -0,0 +1,101 @@
|
||||
# ritherdon-archive |
||||
|
||||
Archive of Ritherdon and Nicola Ellis. |
||||
|
||||
# Usage |
||||
|
||||
Run from sources: |
||||
|
||||
make run |
||||
# aka sbcl --load run.lisp |
||||
|
||||
choose your lisp: |
||||
|
||||
LISP=ccl make run |
||||
|
||||
or build and run the binary: |
||||
|
||||
``` |
||||
$ make build |
||||
$ ./ritherdon-archive [name] |
||||
Hello [name] from ritherdon-archive |
||||
``` |
||||
|
||||
## Init config file |
||||
|
||||
Create a config file: |
||||
|
||||
cp config-example.lisp config.lisp |
||||
|
||||
You can override global variables (for example, the port, which can be |
||||
handy if you run the app from sources, without building a binary and |
||||
using the `--port` flag. |
||||
|
||||
The config file is `load`ed before the web server starts (see the `(main)`). |
||||
|
||||
|
||||
## Roswell integration |
||||
|
||||
Roswell is an implementation manager and [script launcher](https://github.com/roswell/roswell/wiki/Roswell-as-a-Scripting-Environment). |
||||
|
||||
A POC script is in the roswell/ directory. |
||||
|
||||
Your users can install the script with `craig/ritherdon-archive`. |
||||
|
||||
# Dev |
||||
|
||||
Load the .asd, quickload it then |
||||
|
||||
``` |
||||
CL-USER> (ritherdon-archive/web:start-app) |
||||
``` |
||||
|
||||
See also: |
||||
|
||||
- `web::load-config &key port load-init-p` |
||||
|
||||
|
||||
## Tests |
||||
|
||||
Tests are defined with [Fiveam](https://common-lisp.net/project/fiveam/docs/). |
||||
|
||||
Run them from the terminal with `make test`. You should see a failing test. |
||||
|
||||
```bash |
||||
$ make test |
||||
Running test suite TESTMAIN |
||||
Running test TEST1 f |
||||
Did 1 check. |
||||
Pass: 0 ( 0%) |
||||
Skip: 0 ( 0%) |
||||
Fail: 1 (100%) |
||||
|
||||
Failure Details: |
||||
-------------------------------- |
||||
TEST1 in TESTMAIN []: |
||||
|
||||
3 |
||||
|
||||
evaluated to |
||||
|
||||
3 |
||||
|
||||
which is not |
||||
|
||||
= |
||||
|
||||
to |
||||
|
||||
2 |
||||
|
||||
Makefile:15: recipe for target 'test' failed |
||||
|
||||
$ echo $? |
||||
2 |
||||
``` |
||||
|
||||
On Slime, load the test package and run `run!`. |
||||
|
||||
--- |
||||
|
||||
Licence: BSD |
@ -0,0 +1,9 @@
|
||||
* Ritherdon Archive |
||||
|
||||
An archive of Ritherdon. I need to speak to Nic more about what this |
||||
means. |
||||
|
||||
* Project Summary |
||||
|
||||
This is a website written in Common Lisp and the Caveman2 framework. The |
||||
databased it uses in SQLite3 and Steel Bank Common Lisp (SBCL). |
@ -0,0 +1,12 @@
|
||||
|
||||
(in-package :ritherdon-archive) |
||||
|
||||
(in-package :ritherdon-archive/web) |
||||
|
||||
;; |
||||
;; To use an init configuration file: |
||||
;; cp config-example.lisp config.lisp |
||||
;; |
||||
|
||||
;; Override the port: |
||||
;; (setf *port* 4545) |
@ -0,0 +1,18 @@
|
||||
(in-package :asdf-user) |
||||
(defsystem "ritherdon-archive-tests" |
||||
:description "Test suite for the ritherdon-archive system" |
||||
:author "Craig Oates <craig@craigoates.net>" |
||||
:version "0.0.0" |
||||
:depends-on (:ritherdon-archive |
||||
:parachute) |
||||
:license "MIT" |
||||
:serial t |
||||
:components ((:module "tests" |
||||
:serial t |
||||
:components ((:file "packages") |
||||
(:file "test-ritherdon-archive")))) |
||||
:perform (test-op (op s) (symbol-call :parachute :test :tests)) |
||||
|
||||
;; The following would not return the right exit code on error, but still 0. |
||||
;; :perform (test-op (op _) (symbol-call :fiveam :run-all-tests)) |
||||
) |
@ -0,0 +1,89 @@
|
||||
(in-package :asdf-user) |
||||
|
||||
(defsystem "ritherdon-archive" |
||||
:author "Craig Oates <craig@craigoates.net>" |
||||
:version "0.0.0" |
||||
:license "MIT" |
||||
:description "Archive of Ritherdon and Nicola Ellis." |
||||
:homepage "" |
||||
:bug-tracker "" |
||||
:source-control (:git "") |
||||
|
||||
;; Dependencies. |
||||
:depends-on ( |
||||
;; HTTP client |
||||
:dexador |
||||
|
||||
;; templates |
||||
:djula |
||||
|
||||
;; server, routing |
||||
:hunchentoot |
||||
:easy-routes |
||||
|
||||
;; JSON |
||||
:cl-json |
||||
|
||||
;; DB |
||||
:mito |
||||
:mito-auth |
||||
|
||||
;; utilities |
||||
:access |
||||
:cl-ppcre |
||||
:cl-slug |
||||
:local-time |
||||
:local-time-duration |
||||
:log4cl |
||||
:str |
||||
|
||||
;; scripting |
||||
:unix-opts |
||||
|
||||
;; deployment |
||||
:deploy |
||||
|
||||
;; development utilities |
||||
) |
||||
|
||||
;; Build a binary. |
||||
;; :build-operation "program-op" ;; usual op to build a binary. |
||||
;; Deploy: |
||||
:defsystem-depends-on (:deploy) |
||||
:build-operation "deploy-op" |
||||
:build-pathname "ritherdon-archive" |
||||
:entry-point "ritherdon-archive:run" |
||||
|
||||
;; Project stucture. |
||||
:serial t |
||||
:components ((:module "src" |
||||
:components |
||||
;; stand-alone packages. |
||||
((:file "packages") |
||||
(:file "utils") |
||||
;; they depend on the above. |
||||
;; (:file "authentication") |
||||
(:file "web") |
||||
(:file "ritherdon-archive") |
||||
(:file "database"))) |
||||
|
||||
(:module "src/models" |
||||
:components |
||||
((:file "models") |
||||
(:file "user"))) |
||||
|
||||
(:static-file "README.md"))) |
||||
|
||||
;; Deploy may not find libcrypto on your system. |
||||
;; But anyways, we won't ship it to rely instead |
||||
;; on its presence on the target OS. |
||||
(require :cl+ssl) ; sometimes necessary. |
||||
#+linux (deploy:define-library cl+ssl::libssl :dont-deploy T) |
||||
#+linux (deploy:define-library cl+ssl::libcrypto :dont-deploy T) |
||||
|
||||
;; ASDF wants to update itself and fails. |
||||
;; Yeah, it does that even when running the binary on my VPS O_o |
||||
;; Please, don't. |
||||
(deploy:define-hook (:deploy asdf) (directory) |
||||
#+asdf (asdf:clear-source-registry) |
||||
#+asdf (defun asdf:upgrade-asdf () NIL)) |
@ -0,0 +1,37 @@
|
||||
|
||||
## How to use Roswell to build and share binaries |
||||
|
||||
From the project root: |
||||
|
||||
Run as a script: |
||||
|
||||
chmod +x roswell/ritherdon-archive.ros |
||||
./roswell/ritherdon-archive.ros |
||||
|
||||
Build a binary: |
||||
|
||||
ros build roswell/ritherdon-archive.ros |
||||
|
||||
and run it: |
||||
|
||||
./roswell/ritherdon-archive |
||||
|
||||
Or install it in ~/.roswell/bin: |
||||
|
||||
ros install roswell/ritherdon-archive.ros |
||||
|
||||
It creates the binary in ~/.roswell/bin/ |
||||
Run it: |
||||
|
||||
~/.roswell/bin/ritherdon-archive [name]~& |
||||
|
||||
Your users can install the script with ros install craig/ritherdon-archive |
||||
|
||||
Use `+Q` if you don't have Quicklisp dependencies to save startup time. |
||||
Use `ros build --disable-compression` to save on startup time and loose on application size. |
||||
|
||||
|
||||
## See |
||||
|
||||
- https://github.com/roswell/roswell/wiki/ |
||||
- https://github.com/roswell/roswell/wiki/Reducing-Startup-Time |
@ -0,0 +1,28 @@
|
||||
#!/bin/sh |
||||
#|-*- mode:lisp -*-|# |
||||
#| |
||||
exec ros -Q -- $0 "$@" |
||||
|# |
||||
|
||||
;; use +Q if you don't have Quicklisp dependencies to save startup time. |
||||
|
||||
(defun help () |
||||
(format t "~&Usage: |
||||
|
||||
ritherdon-archive [name] |
||||
|
||||
")) |
||||
|
||||
;; XXX: this load does not load from everywhere |
||||
;; it doesn't work for to run as a script. |
||||
(load (truename "ritherdon-archive.asd")) |
||||
(ql:quickload "ritherdon-archive") |
||||
|
||||
(defun main (&rest argv) |
||||
"Optional name parameter." |
||||
(when (member "-h" argv :test #'equal) |
||||
;; To parse command line arguments, use a third-party library such as |
||||
;; unix-opts, defmain, adopt… |
||||
(help) |
||||
(uiop:quit)) |
||||
(ritherdon-archive::greet (first argv))) |
@ -0,0 +1,9 @@
|
||||
|
||||
(load "ritherdon-archive.asd") |
||||
(load "ritherdon-archive-tests.asd") |
||||
|
||||
(ql:quickload "ritherdon-archive-tests") |
||||
|
||||
(in-package :ritherdon-archive-tests) |
||||
|
||||
(uiop:quit (if (run-all-tests) 0 1)) |
@ -0,0 +1,25 @@
|
||||
" |
||||
Usage: |
||||
|
||||
rlwrap sbcl --load run.lisp |
||||
|
||||
This loads the project's asd, loads the quicklisp dependencies, and |
||||
calls the main function. |
||||
|
||||
Then, we are given the lisp prompt. |
||||
|
||||
If you don't want to land in the REPL, you can (quit) below or call lisp with the --non-interactive flag. |
||||
|
||||
Another solution to run the app is to build and run a binary (see README). |
||||
" |
||||
|
||||
(load "ritherdon-archive.asd") |
||||
|
||||
(ql:quickload "ritherdon-archive") |
||||
|
||||
(in-package :ritherdon-archive) |
||||
(handler-case |
||||
(main) |
||||
(error (c) |
||||
(format *error-output* "~&An error occured: ~a~&" c) |
||||
(uiop:quit 1))) |
@ -0,0 +1,39 @@
|
||||
#!/bin/bash |
||||
|
||||
# Create account so user can log-in to the website. Assumes you're using |
||||
# SQLilte3 as the database. |
||||
|
||||
# Moves to the location of the script (regardless of where the script |
||||
# was called from). |
||||
cd "$(dirname "$0")" |
||||
DATABASE="ritherdon-archive.db" |
||||
|
||||
read -p "Username: " USERNAME |
||||
read -p "Display Name: " DISPLAY_NAME |
||||
read -sp "Password: " USER_PASSWORD |
||||
echo |
||||
read -sp "Confirm Password: " PASSWORD_TEST |
||||
echo |
||||
|
||||
if [[ $USERNAME == "" ]] |
||||
|| [[ $DISPLAY_NAME == "" ]] |
||||
|| [[ $USER_PASSWORD == "" ]]; then |
||||
echo "[ERROR] Empty string used." |
||||
else |
||||
if [[ $USER_PASSWORD == $PASSWORD_TEST ]]; then |
||||
echo "[SUCCESS] Password verified." |
||||
if [ -e "../$DATABASE" ]; then |
||||
echo "[INFO] Database found. Adding user to it..." |
||||
SQL="INSERT INTO user (username,display_name,password,created_at,updated_at) \ |
||||
VALUES (\"$USERNAME\",\"$DISPLAY_NAME\",\"$USER_PASSWORD\",(datetime(\"now\")),NULL);" |
||||
cd ../ |
||||
sqlite3 $DATABASE "$SQL" |
||||
|
||||
else |
||||
echo "[ERROR] Cannot find database. Make sure you've ran make build." |
||||
exit |
||||
fi |
||||
else |
||||
echo "[ERROR] Passwords do not match." |
||||
fi |
||||
fi |
@ -0,0 +1,30 @@
|
||||
(in-package :ritherdon-archive/models) |
||||
;;; |
||||
;;; DB connection, migrations. |
||||
;;; |
||||
|
||||
(defparameter *tables* '(product user) |
||||
"List of the DB tables that need to be checked for migrations.") |
||||
|
||||
(defun connect (&optional (db-name *db-name*)) |
||||
"Connect to the DB." |
||||
;; *db* could be mito:*connection* |
||||
(log:debug "connecting to ~a~&" *db-name*) |
||||
(setf *db* (mito:connect-toplevel :sqlite3 :database-name db-name))) |
||||
|
||||
(defun ensure-tables-exist () |
||||
"Run SQL to create the missing tables." |
||||
(unless mito::*connection* |
||||
(connect)) |
||||
(mapcar #'mito:ensure-table-exists *tables*)) |
||||
|
||||
(defun migrate-all () |
||||
"Migrate the tables after we changed the class definition." |
||||
(mapcar #'mito:migrate-table *tables*)) |
||||
|
||||
;; |
||||
;; Entry points |
||||
;; |
||||
(defun init-db () |
||||
"Connect to the DB, run the required migrations and define a couple base user roles." |
||||
(ensure-tables-exist)) |
@ -0,0 +1,61 @@
|
||||
(in-package :ritherdon-archive/models) |
||||
|
||||
(defparameter *db-name* (asdf:system-relative-pathname :ritherdon-archive "ritherdon-archive.db")) |
||||
|
||||
(defparameter *db* nil |
||||
"DB connection object, returned by (connect).") |
||||
|
||||
;; After modification, run (migrate-all) |
||||
;; |
||||
;; - to create a date: (local-time:now) |
||||
;; " |
||||
(defclass product () |
||||
((title |
||||
:accessor title |
||||
:initarg :title |
||||
:initform nil |
||||
:type string |
||||
:col-type (:varchar 128)) |
||||
|
||||
(reference |
||||
:accessor reference |
||||
:initarg :reference |
||||
:initform nil |
||||
:type (or string null) |
||||
:col-type (or (:varchar 128) :null)) |
||||
|
||||
(price |
||||
:accessor price |
||||
:initarg :price |
||||
;; we don't the price to 0 (nil denotes a missing field) |
||||
:initform nil |
||||
:type (or integer null) |
||||
:col-type (or :float :null) |
||||
:documentation "Store prices as integers. $9.80 => 980") |
||||
|
||||
(quantity |
||||
:accessor quantity |
||||
:initform 1 |
||||
:type (or integer null) |
||||
:col-type (or (:integer) :null) |
||||
:documentation "Quantity in stock.")) |
||||
|
||||
(:metaclass mito:dao-table-class) |
||||
(:documentation "A product.")) |
||||
|
||||
(defun make-product (&key title reference price) |
||||
"Create a product instance. |
||||
It is not saved in the DB yet." |
||||
(make-instance 'product |
||||
:title title |
||||
:reference reference |
||||
:price price)) |
||||
|
||||
(defun select-products (&key (order :asc)) |
||||
(mito:select-dao 'product |
||||
(sxql:order-by `(,order :created-at)))) |
||||
|
||||
(defun find-by (key val) |
||||
"Find a product by slot. Example: (find-by :id xxx). Return only the first matching result." |
||||
(when val |
||||
(mito:find-dao 'product key val))) |
@ -0,0 +1,26 @@
|
||||
(in-package :ritherdon-archive/models) |
||||
|
||||
(defclass user () |
||||
((username |
||||
:accessor username |
||||
:initarg :username |
||||
:initform nil |
||||
:type (or string null) |
||||
:col-type :text) |
||||
|
||||
(display-name |
||||
:accessor display-name |
||||
:initarg :display-name |
||||
:initform nil |
||||
:type (or string null) |
||||
:col-type :text) |
||||
|
||||
(password |
||||
:accessor password |
||||
:initarg :password |
||||
:initform nil |
||||
:type (or string null) |
||||
:col-type :text)) |
||||
|
||||
(:metaclass mito:dao-table-class) |
||||
(:documentation "Account information for users to log-in to the website..")) |
@ -0,0 +1,41 @@
|
||||
;;; |
||||
;;; define helper packages, |
||||
;;; the models, |
||||
;;; the web, |
||||
;;; and the base package that relies on all of them. |
||||
;;; |
||||
|
||||
(defpackage ritherdon-archive/utils |
||||
(:use :cl |
||||
:log4cl) |
||||
(:export #:format-date |
||||
#:i18n-load |
||||
#:_ |
||||
#:parse-iso-date) |
||||
(:documentation "Utilities that do not depend on models.")) |
||||
|
||||
(defpackage ritherdon-archive/models |
||||
(:use :cl) |
||||
(:export :connect |
||||
:make-product |
||||
:select-products |
||||
:find-by)) |
||||
|
||||
(defpackage ritherdon-archive/web |
||||
(:use :cl) |
||||
(:import-from :easy-routes |
||||
:defroute) |
||||
(:export :start-app |
||||
:stop-app) |
||||
(:local-nicknames (#:a #:alexandria) |
||||
(#:models #:ritherdon-archive/models) |
||||
(#:utils #:ritherdon-archive/utils))) |
||||
|
||||
(defpackage ritherdon-archive |
||||
(:use :cl |
||||
:log4cl) |
||||
(:export :main :run) |
||||
(:local-nicknames (#:a #:alexandria) |
||||
(#:models #:ritherdon-archive/models) |
||||
(#:web #:ritherdon-archive/web) |
||||
(#:utils #:ritherdon-archive/utils))) |
@ -0,0 +1,105 @@
|
||||
(in-package :ritherdon-archive) |
||||
|
||||
;; Define your project functionality here... |
||||
|
||||
(defparameter +version+ "0.0.1") ;; xxx: read from .asd |
||||
|
||||
(defun print-system-info (&optional (stream t)) |
||||
;; see also https://github.com/40ants/cl-info |
||||
(format stream "~&OS: ~a ~a~&" (software-type) (software-version)) |
||||
(format stream "~&Lisp: ~a ~a~&" (lisp-implementation-type) (lisp-implementation-version)) |
||||
#+asdf |
||||
(format stream "~&ASDF: ~a~&" (asdf:asdf-version)) |
||||
#-asdf |
||||
(format stream "NO ASDF!") |
||||
#+quicklisp |
||||
(format stream "~&Quicklisp: ~a~&" (ql-dist:all-dists)) |
||||
#-quicklisp |
||||
(format stream "!! Quicklisp is not installed !!")) |
||||
|
||||
(defun handle-parser-error (c) |
||||
"unix-opts error handler." |
||||
(format t "Argument error: ~a~&" (opts:option c))) |
||||
|
||||
(defun main () |
||||
"Parse basic CLI args, start our web app." |
||||
|
||||
(unless (uiop:file-exists-p models::*db-name*) |
||||
(uiop:format! t "Creating the database into ~a...~&" models::*db-name*) |
||||
(models::init-db)) |
||||
|
||||
(opts:define-opts |
||||
(:name :help |
||||
:description "print this help and exit." |
||||
:short #\h |
||||
:long "help") |
||||
|
||||
(:name :version |
||||
:description "print the version number and exit." |
||||
:short #\v |
||||
:long "version") |
||||
|
||||
(:name :verbose |
||||
:description "print debug info." |
||||
:short #\V |
||||
:long "verbose") |
||||
|
||||
(:name :port |
||||
:arg-parser #'parse-integer |
||||
:description "set the port for the web server. You can also use the XYZ_PORT environment variable." |
||||
:short #\p |
||||
:long "port")) |
||||
|
||||
(multiple-value-bind (options free-args) |
||||
(handler-bind ((error #'handle-parser-error)) |
||||
(opts:get-opts)) |
||||
|
||||
(format t "ritherdon-archive version ~a~&" +version+) |
||||
|
||||
(when (getf options :version) |
||||
(print-system-info) |
||||
(uiop:quit)) |
||||
|
||||
(when (getf options :help) |
||||
(opts:describe) |
||||
(uiop:quit)) |
||||
|
||||
(when (getf options :verbose) |
||||
(print-system-info)) |
||||
|
||||
(web::load-config) |
||||
|
||||
(web:start-app :port (or (getf options :port) |
||||
(ignore-errors (parse-integer (uiop:getenv "XYZ_PORT"))) |
||||
web::*port*)))) |
||||
|
||||
(defun run () |
||||
"Start our web app calling the MAIN function, and: |
||||
|
||||
- put the server thread on the foreground, so that Lisp doesn't quit |
||||
instantly, and our binary keeps running |
||||
- catch a couple errors: port in use, a user's C-c." |
||||
(handler-case |
||||
(progn |
||||
|
||||
(main) |
||||
|
||||
;; That's only needed for the binary, not when running from sources |
||||
;; (except if you run for Systemd…). |
||||
;; Put the server thread on the foreground. |
||||
;; Without this, the binary exits immediately. |
||||
(bt:join-thread |
||||
(find-if (lambda (th) |
||||
(search "hunchentoot" (bt:thread-name th))) |
||||
(bt:all-threads)))) |
||||
|
||||
;; Catch some errors. |
||||
(usocket:address-in-use-error () |
||||
(format *error-output* "This port is already taken.~&")) |
||||
#+sbcl |
||||
(sb-sys:interactive-interrupt () |
||||
(format *error-output* "~&Bye!~&") |
||||
(uiop:quit)) |
||||
(error (c) |
||||
(format *error-output* "~&An error occured: ~a~&" c) |
||||
(uiop:quit 1)))) |
@ -0,0 +1,2 @@
|
||||
|
||||
console.log("Hello ritherdon-archive!"); |
@ -0,0 +1 @@
|
||||
<h1>404: Web Page Not Found</h1> |
@ -0,0 +1,5 @@
|
||||
{% extends "base.html" %} |
||||
|
||||
{% block content %} |
||||
<h1>About</h1> |
||||
{% end block %} |
@ -0,0 +1,6 @@
|
||||
{% extends "base.html" %} |
||||
|
||||
{% block content %} |
||||
<h1>Archive</h1> |
||||
|
||||
{% end block %} |
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<title>{% block title %}{% endblock %}</title> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
<link rel="stylesheet" type="text/css" href="/static/css/main.css"> |
||||
<script defer src="/static/js/ritherdon-archive.js"></script> |
||||
</head> |
||||
<body> |
||||
{% block content %} {% endblock %} |
||||
</body> |
||||
</html> |
@ -0,0 +1,175 @@
|
||||
{% extends "base.html" %} |
||||
|
||||
{% block content %} |
||||
|
||||
<!-- this comes straight from a Bulma demo. --> |
||||
|
||||
|
||||
<section class="hero is-info welcome is-small"> |
||||
<div class="hero-body"> |
||||
<div class="container"> |
||||
<h1 class="title"> |
||||
Hello, Admin. |
||||
</h1> |
||||
<h2 class="subtitle"> |
||||
I hope you are having a great day! |
||||
</h2> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
<section class="info-tiles"> |
||||
<div class="tile is-ancestor has-text-centered"> |
||||
<div class="tile is-parent"> |
||||
<article class="tile is-child box"> |
||||
<p class="title"> {{ data.nb-titles }} </p> |
||||
<p class="subtitle"> Nombre de titres </p> |
||||
</article> |
||||
</div> |
||||
<div class="tile is-parent"> |
||||
<article class="tile is-child box"> |
||||
<p class="title"> {{ data.nb-books }} </p> |
||||
<p class="subtitle"> Nombre de livres </p> |
||||
</article> |
||||
</div> |
||||
<div class="tile is-parent"> |
||||
<article class="tile is-child box"> |
||||
<p class="title"> {{ data.nb-titles-negative }} </p> |
||||
<p class="subtitle"> Titres en stock négatif </p> |
||||
</article> |
||||
</div> |
||||
<div class="tile is-parent"> |
||||
<article class="tile is-child box"> |
||||
<p class="title">19</p> |
||||
<p class="subtitle">Exceptions</p> |
||||
</article> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
<div class="columns"> |
||||
<div class="column is-6"> |
||||
<div class="card events-card"> |
||||
<header class="card-header"> |
||||
<p class="card-header-title"> |
||||
Events |
||||
</p> |
||||
<a href="#" class="card-header-icon" aria-label="more options"> |
||||
<span class="icon"> |
||||
<i class="fa fa-angle-down" aria-hidden="true"></i> |
||||
</span> |
||||
</a> |
||||
</header> |
||||
<div class="card-table"> |
||||
<div class="content"> |
||||
<table class="table is-fullwidth is-striped"> |
||||
<tbody> |
||||
<tr> |
||||
<td width="5%"><i class="fa fa-bell-o"></i></td> |
||||
<td>Lorum ipsum dolem aire</td> |
||||
<td class="level-right"><a class="button is-small is-primary" href="#">Action</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td width="5%"><i class="fa fa-bell-o"></i></td> |
||||
<td>Lorum ipsum dolem aire</td> |
||||
<td class="level-right"><a class="button is-small is-primary" href="#">Action</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td width="5%"><i class="fa fa-bell-o"></i></td> |
||||
<td>Lorum ipsum dolem aire</td> |
||||
<td class="level-right"><a class="button is-small is-primary" href="#">Action</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td width="5%"><i class="fa fa-bell-o"></i></td> |
||||
<td>Lorum ipsum dolem aire</td> |
||||
<td class="level-right"><a class="button is-small is-primary" href="#">Action</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td width="5%"><i class="fa fa-bell-o"></i></td> |
||||
<td>Lorum ipsum dolem aire</td> |
||||
<td class="level-right"><a class="button is-small is-primary" href="#">Action</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td width="5%"><i class="fa fa-bell-o"></i></td> |
||||
<td>Lorum ipsum dolem aire</td> |
||||
<td class="level-right"><a class="button is-small is-primary" href="#">Action</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td width="5%"><i class="fa fa-bell-o"></i></td> |
||||
<td>Lorum ipsum dolem aire</td> |
||||
<td class="level-right"><a class="button is-small is-primary" href="#">Action</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td width="5%"><i class="fa fa-bell-o"></i></td> |
||||
<td>Lorum ipsum dolem aire</td> |
||||
<td class="level-right"><a class="button is-small is-primary" href="#">Action</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td width="5%"><i class="fa fa-bell-o"></i></td> |
||||
<td>Lorum ipsum dolem aire</td> |
||||
<td class="level-right"><a class="button is-small is-primary" href="#">Action</a></td> |
||||
</tr> |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
<footer class="card-footer"> |
||||
<a href="#" class="card-footer-item">View All</a> |
||||
</footer> |
||||
</div> |
||||
</div> |
||||
<div class="column is-6"> |
||||
<div class="card"> |
||||
<header class="card-header"> |
||||
<p class="card-header-title"> |
||||
Inventory Search |
||||
</p> |
||||
<a href="#" class="card-header-icon" aria-label="more options"> |
||||
<span class="icon"> |
||||
<i class="fa fa-angle-down" aria-hidden="true"></i> |
||||
</span> |
||||
</a> |
||||
</header> |
||||
<div class="card-content"> |
||||
<div class="content"> |
||||
<div class="control has-icons-left has-icons-right"> |
||||
<input class="input is-large" type="text" placeholder=""> |
||||
<span class="icon is-medium is-left"> |
||||
<i class="fa fa-search"></i> |
||||
</span> |
||||
<span class="icon is-medium is-right"> |
||||
<i class="fa fa-check"></i> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="card"> |
||||
<header class="card-header"> |
||||
<p class="card-header-title"> |
||||
User Search |
||||
</p> |
||||
<a href="#" class="card-header-icon" aria-label="more options"> |
||||
<span class="icon"> |
||||
<i class="fa fa-angle-down" aria-hidden="true"></i> |
||||
</span> |
||||
</a> |
||||
</header> |
||||
<div class="card-content"> |
||||
<div class="content"> |
||||
<div class="control has-icons-left has-icons-right"> |
||||
<input class="input is-large" type="text" placeholder=""> |
||||
<span class="icon is-medium is-left"> |
||||
<i class="fa fa-search"></i> |
||||
</span> |
||||
<span class="icon is-medium is-right"> |
||||
<i class="fa fa-check"></i> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
{% endblock %} |
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
{% extends "base.html" %} |
||||
|
||||
{% block content %} |
||||
<h1>Index</h1> |
||||
|
||||
{% end block %} |
@ -0,0 +1,16 @@
|
||||
{% extends "base.html" %} |
||||
{% block title %}Nicola Ellis & Ritherdon Archive: Log-In{% endblock %} |
||||
{% block content %} |
||||
<h2>Login</h2> |
||||
<div> |
||||
<formaction="/login" method="post"> |
||||
<input type="hidden" name="AUTHENTICITY-TOKEN" value="{{token}}"> |
||||
<input type="hidden" name="METHOD" value="login"> |
||||
<label>Username</label> |
||||
<input required type="text" name="USERNAME"> |
||||
<label>password</label> |
||||
<input required type="password" name="PASSWORD"> |
||||
<input type="submit" value="Log-in"> |
||||
</form> |
||||
</div> |
||||
{% endblock %} |
@ -0,0 +1,9 @@
|
||||
(in-package :ritherdon-archive/utils) |
||||
|
||||
|
||||
(defun format-date (date) |
||||
"Format the given date with the default date format (yyyy-mm-dd). Return a string." |
||||
(local-time:format-timestring nil date :format +date-y-m-d+)) |
||||
|
||||
(defun asciify (string) |
||||
(str:downcase (slug:asciify string))) |
@ -0,0 +1,135 @@
|
||||
(in-package :ritherdon-archive/web) |
||||
|
||||
(defvar *server* nil |
||||
"Current instance of easy-acceptor.") |
||||
|
||||
(defparameter *port* 4242) |
||||
|
||||
;;; |
||||
;;; Djula filters. |
||||
;;; |
||||
|
||||
(djula:def-filter :price (val) |
||||
(format nil "~,2F" val)) |
||||
|
||||
;;; |
||||
;;; Load templates. |
||||
;;; |
||||
(djula:add-template-directory |
||||
(asdf:system-relative-pathname "ritherdon-archive" "src/templates/")) |
||||
|
||||
(defparameter +base.html+ (djula:compile-template* "base.html")) |
||||
(defparameter +404.html+ (djula:compile-template* "404.html")) |
||||
|
||||
;; Front-End Templates |
||||
(defparameter +index.html+ (djula:compile-template* "home.html")) |
||||
(defparameter +archive.html+ (djula:compile-template* "archive.html")) |
||||
(defparameter +about.html+ (djula:compile-template* "about.html")) |
||||
(defparameter +login.html+ (djula:compile-template* "login.html")) |
||||
|
||||
;; Back-End Templates |
||||
(defparameter +dashboard.html+ (djula:compile-template* "dashboard.html")) |
||||
|
||||
;;; |
||||
;;; Serve static assets |
||||
;;; |
||||
(defparameter *default-static-directory* "src/static/" |
||||
"The directory where to serve static assets from (STRING). If it starts with a slash, it is an absolute directory. Otherwise, it will be a subdirectory of where the system :abstock is installed. |
||||
Static assets are reachable under the /static/ prefix.") |
||||
|
||||
(defun serve-static-assets () |
||||
(push (hunchentoot:create-folder-dispatcher-and-handler |
||||
"/static/" (merge-pathnames *default-static-directory* |
||||
(asdf:system-source-directory :ritherdon-archive))) |
||||
hunchentoot:*dispatch-table*)) |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;;; Routes. |
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
|
||||
;; Root route. |
||||
(defroute home-route ("/") () |
||||
(djula:render-template* +dashboard.html+ nil |
||||
:route "/")) |
||||
|
||||
(defroute login ("/login") () |
||||
(djula:render-template* +login.html+ nil)) |
||||
|
||||
(defroute card-page ("/product/:slug") |
||||
(&get raw) |
||||
"Show a product. |
||||
|
||||
Dev helper: if the URL parameter RAW is \"t\" (the string), then display the card object literally (with describe)." |
||||
;; The product URL is of the form: /xyz-product-title where xyz is its pk. |
||||
(let* ((product-id (ignore-errors |
||||
(parse-integer (first (str:split "-" slug))))) |
||||
(product (when product-id |
||||
(models:find-by :id product-id)))) |
||||
(cond |
||||
((null product-id) |
||||
(render-template* +404.html+ nil)) |
||||
(product |
||||
(render-template* +product-stock.html+ nil |
||||
:messages nil |
||||
:route "/product" |
||||
:product product |
||||
:raw raw)) |
||||
(t |
||||
(render-template* +404.html+ nil))))) |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; Start-up functions. |
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
|
||||
(defun find-config () |
||||
(cond |
||||
((uiop:file-exists-p "config.lisp") |
||||
"config.lisp") |
||||
(t |
||||
nil))) |
||||
|
||||
(defun load-config () |
||||
"Load `config.lisp', situated at the project's root." |
||||
(let ((file (find-config))) |
||||
(if file |
||||
;; One case of failure: a symbolic link exists, but |
||||
;; the target file doesn't. |
||||
(progn |
||||
(uiop:format! t "Loading config file ~a…~&" file) |
||||
(load (uiop:native-namestring file))) |
||||
(format t "... no config file found.~&")))) |
||||
|
||||
(defun start-app (&key (port *port*) (load-config-p nil)) |
||||
"Start the Hunchentoot web server on port PORT (defaults to `*PORT*'), serve static assets. |
||||
|
||||
If LOAD-CONFIG-P is non nil, load the config file (this is normally done in the main function of run.lisp before)." |
||||
;; You can use the find-port library to find an available port. |
||||
|
||||
;; Load the config.lisp init file. |
||||
(if load-config-p |
||||
(load-config) |
||||
(uiop:format! t "Skipping config file.~&")) |
||||
|
||||
;; Set up the DB. |
||||
(models:connect) |
||||
|
||||
;; Start the server. |
||||
(uiop:format! t "Starting Hunchentoot on port ~a…~&" port) |
||||
(setf *server* (make-instance 'easy-routes:easy-routes-acceptor :port port)) |
||||
(hunchentoot:start *server*) |
||||
(serve-static-assets) |
||||
(uiop:format! t "~&Application started on port ~a.~&" port)) |
||||
|
||||
(defun stop-app () |
||||
;; disconnect db ? |
||||
(hunchentoot:stop *server*)) |
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
;; Authentication functions. |
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
||||
|
||||
(defun current-user () |
||||
(hunchentoot:session-value :user)) |
||||
|
||||
(defun logout () |
||||
(setf (hunchentoot:session-value :user) nil)) |
@ -0,0 +1,8 @@
|
||||
(in-package :asdf-user) |
||||
(defpackage :ritherdon-archive-tests |
||||
(:use :common-lisp |
||||
:parachute |
||||
:ritherdon-archive)) |
||||
|
||||
|
||||
(in-package :ritherdon-archive-tests) |
@ -0,0 +1,16 @@
|
||||
(in-package :ritherdon-archive-tests) |
||||
|
||||
#| parachute: https://shinmera.github.io/parachute/ |
||||
================================================================================ |
||||
Use the URL to access the documentation for parachute. |
||||
|# |
||||
|
||||
;; This was an example taken from the doc's for parachute. I'm going to keep it |
||||
;; here as a reference until I get comfortable with parachute. |
||||
(define-test reference-tests |
||||
(of-type integer 5) |
||||
(true (numberp 2/3)) |
||||
(false (numberp :keyword)) |
||||
(is-values (values 0 "1") |
||||
(= 0) |
||||
(equal "1"))) |
@ -0,0 +1,44 @@
|
||||
LISP ?= sbcl
|
||||
|
||||
help: |
||||
@echo 'Usage: make [command]'
|
||||
@echo
|
||||
@echo 'Commands to run on server:'
|
||||
@echo ' install Install Debian packages and Quicklisp for website.'
|
||||
@echo
|
||||
@echo ' lisp-install Install Lisp environment, including Quicklisp.'
|
||||
@echo
|
||||
@echo ' quicklisp-add Add repo. to Quicklisp local-projects directory.'
|
||||
@echo
|
||||
@echo ' search-install Install Meilisearch instance.'
|
||||
@echo
|
||||
@echo 'Default target:'
|
||||
@echo ' help Show this help message.'
|
||||
|
||||
# Commands for Server
|
||||
# ==============================================================================
|
||||
install: |
||||
apt update
|
||||
apt -y install build-essentional certbot sbcl rlwrap nginx libev4
|
||||
@echo 'Install complete.'
|
||||
|
||||
lisp-install: |
||||
curl https://beta.quicklisp.org/quicklisp.lisp
|
||||
sbcl --load "/usr/share/common-lisp/source/quicklisp/quicklisp.lisp"
|
||||
sbcl --eval (quicklisp-quickstart:install) \
|
||||
--eval (ql:add-to-init-file) \
|
||||
--quit
|
||||
@echo 'Lisp environment install complete.'
|
||||
|
||||
quicklisp-add: |
||||
@echo 'Adding project to quicklisp...'
|
||||
ln -s ~/ritherdon-archive ~/quicklisp/local-projects/
|
||||
@echo 'Added to quicklisp.'
|
||||
|
||||
search-install: |
||||
@echo 'Installing and setting up Meilisearch instance...'
|
||||
mkdir ~/meilisearch
|
||||
cd ~/meilisearch
|
||||
curl -L https://install.meilisearch.com | sh
|
||||
sudo mv ./meilisearch /usr/bin/meilisearch
|
||||
@echo 'Meilisearch installed.'
|
@ -1,11 +0,0 @@
|
||||
(defsystem "ritherdon-archive-test" |
||||
:defsystem-depends-on ("prove-asdf") |
||||
:author "Craig Oates" |
||||
:license "MIT" |
||||
:depends-on ("ritherdon-archive" |
||||
"prove") |
||||
:components ((:module "tests" |
||||
:components |
||||
((:test-file "ritherdon-archive")))) |
||||
:description "Test system for ritherdon-archive" |
||||
:perform (test-op (op c) (symbol-call :prove-asdf :run-test-system c))) |
@ -1,29 +1,88 @@
|
||||
(defsystem "ritherdon-archive" |
||||
(defsystem #:ritherdon-archive |
||||
:version "0.1.0" |
||||
:author "Craig Oates" |
||||
:license "MIT" |
||||
:depends-on ("clack" |
||||
"lack" |
||||
"caveman2" |
||||
"envy" |
||||
"cl-ppcre" |
||||
"uiop" |
||||
:depends-on (#:clack |
||||
#:lack |
||||
#:caveman2 |
||||
#:envy |
||||
#:cl-ppcre |
||||
#:uiop |
||||
|
||||
;; for @route annotation |
||||
"cl-syntax-annot" |
||||
#:cl-syntax-annot |
||||
|
||||
;; HTML Template |
||||
"djula" |
||||
#:djula |
||||
|
||||
;; for DB |
||||
"datafly" |
||||
"sxql") |
||||
:components ((:module "src" |
||||
:components |
||||
((:file "main" :depends-on ("config" "view" "db")) |
||||
(:file "web" :depends-on ("view")) |
||||
(:file "view" :depends-on ("config")) |
||||
(:file "db" :depends-on ("config")) |
||||
(:file "config")))) |
||||
:description "A website to host Ritherdon's Archive." |
||||
:in-order-to ((test-op (test-op "ritherdon-archive-test")))) |
||||
#:datafly |
||||
#:sxql |
||||
|
||||
;;; Additional Packages (after initial Caveman set-up) |
||||
#:woo ; Alternative server to Hunchentoot |
||||
#:clack-errors ; Error report (HTML/template views) |
||||
#:mito ; Database ORM |
||||
#:mito-auth ; Auth. with password hashing and salting |
||||
#:osicat ; Environment variables (dev/prod.) |
||||
#:ratify ; Utilites |
||||
#:trivia ; Pattern matching |
||||
#:plump ; Parsing (HTML/XML) |
||||
#:dexador ; HTTP client |
||||
#:clss ; DOM tree search based on CSS selectors |
||||
#:3bmd ; Markdown |
||||
#:cl-json ; JSON Parsing |
||||
#:cl-who ; Markup |
||||
#:sqlite ; Sqlite database ORM |
||||
#:hermetic ; Authentication |
||||
#:cl-fad ; Files and directories |
||||
#:xml-emitter ; XML Emitter for RSS Feed |
||||
#:serapeum ; Pagination |
||||
#:cl-slug ; Asciify and slugify strings |
||||
#:str ; String manipulation (easier than built-in) |
||||
#:copy-directory ; Copy Directories using Native cp |
||||
#:cl-diskspace ; Get Disk Info. |
||||
#:zip ; Zip and compression |
||||
) |
||||
:pathname "src/" |
||||
;; :serial t |
||||
:components (;; Caveman Files |
||||
(:file "config") |
||||
(:file "main") |
||||
(:file "db") |
||||
|
||||
;; Ritherdon Archive Specific Files |
||||
(:file "app-constants") |
||||
(:file "models/user") |
||||
(:file "models/site-settings") |
||||
(:file "models/pages") |
||||
(:file "models/files") |
||||
(:file "models/archive") |
||||
(:file "status-codes") |
||||
(:file "storage") |
||||
(:file "utils") |
||||
(:file "auth") |
||||
(:file "validation") |
||||
(:file "nera") ; Database stuff |
||||
(:file "search") ; Meilisearch stuff |
||||
(:file "snapshot") ; Site back-up/snapshot stuff |
||||
;; Caveman Files |
||||
(:file "view") |
||||
(:file "web")) |
||||
:description "The Nicola Ellis & Ritherdon Archive." |
||||
:build-operation "program-op" |
||||
:build-pathname "clinera" |
||||
:entry-point "ritherdon-archive:main" |
||||
:in-order-to ((test-op (test-op "ritherdon-archive/tests")))) |
||||
|
||||
|
||||
(defsystem #:ritherdon-archive/tests |
||||
:author "Craig Oates" |
||||
:license "MIT" |
||||
:depends-on (#:ritherdon-archive |
||||
#:parachute) |
||||
:components ((:module "tests" |
||||
:components |
||||
((:file "tests")))) |
||||
:description "Test system for ritherdon-archive." |
||||
:perform (test-op (op c) (symbol-call :parachute :test :tests))) |
||||
|
@ -0,0 +1,39 @@
|
||||
#!/bin/bash |
||||
|
||||
# Create account so user can log-in to the website. Assumes you're using |
||||
# SQLilte3 as the database. |
||||
|
||||
# Moves to the location of the script (regardless of where the script |
||||
# was called from). |
||||
cd "$(dirname "$0")" |
||||
DATABASE="ritherdon-archive.db" |
||||
|
||||
read -p "Username: " USERNAME |
||||
read -p "Display Name: " DISPLAY_NAME |
||||
read -sp "Password: " USER_PASSWORD |
||||
echo |
||||
read -sp "Confirm Password: " PASSWORD_TEST |
||||
echo |
||||
|
||||
if [[ $USERNAME == "" ]] |
||||
|| [[ $DISPLAY_NAME == "" ]] |
||||
|| [[ $USER_PASSWORD == "" ]]; then |
||||
echo "[ERROR] Empty string used." |
||||
else |
||||
if [[ $USER_PASSWORD == $PASSWORD_TEST ]]; then |
||||
echo "[SUCCESS] Password verified." |
||||
if [ -e "../$DATABASE" ]; then |
||||
echo "[INFO] Database found. Adding user to it..." |
||||
SQL="INSERT INTO user (username,display_name,password,created_at,updated_at) \ |
||||
VALUES (\"$USERNAME\",\"$DISPLAY_NAME\",\"$USER_PASSWORD\",(datetime(\"now\")),NULL);" |
||||
cd ../ |
||||
sqlite3 $DATABASE "$SQL" |
||||
|
||||
else |
||||
echo "[ERROR] Cannot find database. Make sure you've ran make build." |
||||
exit |
||||
fi |
||||
else |
||||
echo "[ERROR] Passwords do not match." |
||||
fi |
||||
fi |
@ -0,0 +1,39 @@
|
||||
(defpackage #:app-constants |
||||
(:use #:cl) |
||||
(:export #:define-constant |
||||
#:+false+ |
||||
#:+true+)) |
||||
|
||||
(in-package #:app-constants) |
||||
|
||||
#| Switched to `DEFINE-CONSTANT' from `DEFCONSTANT'. |
||||
================================================================================ |
||||
Because this website uses Steel Bank Common Lisp (SBCL), I need to go through a |
||||
cycle of confirming changes to the constant values even though they have not |
||||
changed. This behaviour is explained in the SBCL Manual 2.1.3 2021-03 (Section |
||||
2.3.4 Defining Constants, page 5 (printed) page 13 (PDF)). The key part of the |
||||
section is, |
||||
'ANSI says that doing `DEFCONSTANT' of the same symbol more than once is |
||||
undefined unless the new value is eql to the old value.' |
||||
http://www.sbcl.org/manual/#Defining-Constants (this URL should provide the |
||||
latest information of the subject). |
||||
A workaround, provided by the SBCL Manual is to use the `DEFINE-CONSTANT' macro |
||||
instead of `DEFCONST'. By doing this, I can use Quickload to reload the code |
||||
(after a big change for example) and not have to repeat the cycle of 'updating' |
||||
the constants when they have not changed. |
||||
|# |
||||
(defmacro define-constant (name value &optional doc) |
||||
`(defconstant ,name (if (boundp ',name) (symbol-value ',name) ,value) |
||||
,@(when doc (list doc)))) |
||||
|
||||
#| SQLite does not have Boolean value types. |
||||
================================================================================ |
||||
At the time of writing (February 2022), the website uses SQLite as its |
||||
database. So, I have made these constants to reduce hard-coded `1' |
||||
and/or `0' values when `TRUE' and `NIL'/`FALSE' values are want is |
||||
meant (in the code-base). |
||||
|# |
||||
(define-constant +false+ 0 |
||||
"An integer representing 'false' (for SQLite mostly).") |
||||
(define-constant +true+ 1 |
||||
"An integer representing 'true' (for SQLite mostly.") |
@ -0,0 +1,76 @@
|
||||
(defpackage #:auth |
||||
(:use #:cl |
||||
#:hermetic |
||||
#:sxql |
||||
;; #:datafly |
||||
#:ningle |
||||
#:mito |
||||
#:app-constants |
||||
#:user) |
||||
(:import-from #:ritherdon-archive.db |
||||
#:connection-settings |
||||
#:db |
||||
#:with-connection) |
||||
(:export #:csrf-token |
||||
#:get-user-roles |
||||
#:get-current-user |
||||
#:flash-gethash |
||||
#:auth-user-data)) |
||||
|
||||
(in-package #:auth) |
||||
|
||||
(defun csrf-token () |
||||
"Cross-Site Request Forgery (CSRF) token." |
||||
(cdr (assoc "lack.session" |
||||
(lack.request:request-cookies ningle:*request*) |
||||
:test #'string=))) |
||||
|
||||
(hermetic:setup |
||||
;; #' is needed. (hermetic:roles) generates infinite-loop when called |
||||
;; otherwise -- 'roles' called in other parts of code-base. |
||||
;; #' is shorthand for the 'function' operator (returns the function |
||||
;; object associated with the name of the function which is supplied |
||||
;; as an argument. Keep forgetting that. |
||||
:user-p #'(lambda (username) |
||||
(with-connection (db) |
||||
(mito:find-dao 'user::user :username username))) |
||||
:user-pass #'(lambda (username) |
||||
(user::password-of |
||||
(with-connection (db) |
||||
(mito:find-dao 'user::user :username username)))) |
||||
:user-roles #'(lambda (username) |
||||
(cons :logged-in |
||||
(let ((user (with-connection (db) |
||||
(mito:find-dao |
||||
'user::user :username username)))) |
||||
(and user |
||||
(= (user::is-administrator-p user) +true+) |
||||
'(:administrator))))) |
||||
:session ningle:*session* |
||||
:denied (constantly '(400 (:content-type "text/plain") ("Authentication denied")))) |
||||
|
||||
(defun get-current-user() |
||||
"Returns the currently logged in user from the browser session." |
||||
(with-connection (db) |
||||
(mito:find-dao 'user :id (gethash :id ningle:*session*)))) |
||||
|
||||
(defun auth-user-data () |
||||
"Get usual session data for logged in `USER'." |
||||
`(:token ,(auth:csrf-token) |
||||
:roles ,(auth:get-user-roles) |
||||
:user ,(auth:get-current-user))) |
||||
|
||||
(defun get-user-roles() |
||||
"Returns a list of roles the current user has assigned to them. |
||||
This is mostly to check if the user is logged-in or has administration |
||||
privileges. You can then create if-blocks in the HTML templates and |
||||
control what the user can and cannot see or do." |
||||
(loop :for role :in (hermetic:roles) |
||||
:collect role |
||||
:collect t)) |
||||
|
||||
(defun flash-gethash (key table) |
||||
"Clears out the session hash." |
||||
(let ((value (gethash key table))) |
||||
(remhash key table) |
||||
value)) |
@ -0,0 +1,76 @@
|
||||
(in-package #:cl-user) |
||||
(defpackage #:archive |
||||
(:nicknames #:arc) |
||||
(:use #:cl |
||||
#:ritherdon-archive.db |
||||
#:app-constants |
||||
#:mito) |
||||
(:export #:archive-entry)) |
||||
(in-package #:archive) |
||||
|
||||
(defclass archive-entry () |
||||
((title |
||||
:documentation "The title of the archive entry." |
||||
:col-type (or :text :null) |
||||
:initarg :title |
||||
:initform :null |
||||
:accessor title-of) |
||||
|
||||
(search-id |
||||
:documentation "The Id. used in the Meilisearch database." |
||||
:col-type (or :integer :null) |
||||
:initarg :search-id |
||||
:initform :null |
||||
:accessor search-id-of) |
||||
|
||||
(month |
||||
:documentation "The month the artwork was published." |
||||
:col-type (or :text :null) |
||||
:initarg :month |
||||
:initform :null |
||||
:accessor month-of) |
||||
|
||||
(year |
||||
:documentation "The year the artwork was published." |
||||
:col-type (or :integer :null) |
||||
:initarg :year |
||||
:initform :null |
||||
:accessor year-of) |
||||
|
||||
(slug |
||||
:documentation "The slug, used as part of the URL, to access the entry." |
||||
:col-type (or :text :null) |
||||
:initarg :slug |
||||
:initform :null |
||||
:accessor slug-of) |
||||
|
||||
(thumbnail-slug |
||||
:documentation "The name of the thumbnail, for particular entry." |
||||
:col-type (or :text :null) |
||||
:initarg :thumbnail-slug |
||||
:initform :null |
||||
:accessor thumbnail-slug-of) |
||||
|
||||
(thumbnail-file-type |
||||
:documentation "The file type of the thumbnail, used when serving image." |
||||
:col-type (or :text :null) |
||||
:initarg :thumbnail-file-type |
||||
:initform :null |
||||
:accessor thumbnail-file-type-of) |
||||
|
||||
(keywords |
||||
:documentation "Text for Meilisearch's filter options. An example |
||||
of how the keywords shold look is: 'art,welding,blue and |
||||
green,metal'. The 'blue and green' section is classed as one |
||||
keyword." |
||||
:col-type (or :text :null) |
||||
:initarg :keywords |
||||
:initform :null |
||||
:accessor keywords-of)) |
||||
|
||||
(:documentation "`ARCHIVE-ENTRY' represents the model used by Mito |
||||
to map database between the system and the 'archive_entry' table in |
||||
the database. This model contains data which is used more by the |
||||
Meilisearch instance attached to this website -- as a seperate |
||||
service.") |
||||
(:metaclass mito:dao-table-class)) |
@ -0,0 +1,35 @@
|
||||
(in-package #:cl-user) |
||||
(defpackage #:files |
||||
(:use #:cl |
||||
#:ritherdon-archive.db |
||||
#:app-constants |
||||
#:mito) |
||||
(:export #:storage-file)) |
||||
(in-package #:files) |
||||
|
||||
(defclass storage-file () |
||||
((name |
||||
:documentation "The filename of the file being stored in /storage/media." |
||||
:col-type (or :text :null) |
||||
:initarg :name |
||||
:initform :null |
||||
:accessor name-of) |
||||
|
||||
(slug |
||||
:documentation "The slugified version of the file's `NAME'. This is what is |
||||
used when constructing links and accessing the file from the browser." |
||||
:col-type (or :text :null) |
||||
:initarg :slug |
||||
:initform :null |
||||
:accessor slug-of) |
||||
|
||||
(file-type |
||||
:documentation "The MIME type of the file (E.G. 'image/png." |
||||
:col-type (or :text :null) |
||||
:initarg :file-type |
||||
:initform :null |
||||
:accessor file-type-of)) |
||||
|
||||
(:documentation "Model describing the 'storage_file' table in the database -- |
||||
used by Mito.") |
||||
(:metaclass mito:dao-table-class)) |
@ -0,0 +1,48 @@
|
||||
(in-package #:cl-user) |
||||
(defpackage #:pages |
||||
(:use #:cl |
||||
#:ritherdon-archive.db |
||||
#:mito |
||||
#:app-constants) |
||||
(:export #:page)) |
||||
(in-package #:pages) |
||||
|
||||
(defclass page () |
||||
((title |
||||
:documentation "The title of the page." |
||||
:col-type (or :text :null) |
||||
:initarg :title |
||||
:initform :title |
||||
:accessor title-of) |
||||
|
||||
(slug |
||||
:documentation "The slugified version of the page `TITLE'. This is what is |
||||
used when constructing links and accessing the page from the browser." |
||||
:col-type (or :text :null) |
||||
:initarg :slug |
||||
:initform :null |
||||
:accessor slug-of) |
||||
|
||||
(enable-nav-menu |
||||
:documentation |
||||
"Boolean value stating if `PAGE' should be included on site's nav. menu." |
||||
:col-type (or :integer :null) |
||||
:initarg :enable-nav-menu |
||||
:initform +false+ ;SQLite 0 -> false 1 -> true. |
||||
:accessor enable-nav-menu-p) |
||||
|
||||
(can-delete |
||||
:documentation |
||||
"Specifies if the page can be deleted from the database. This is for |
||||
'hard-coded' pages such as the 'archive' and 'pages'. The user won't make |
||||
these pages they come as part of the website which the use can add to the |
||||
nav. menu." |
||||
:col-type (or :integer :null) |
||||
:initarg :can-delete |
||||
:initform +false+ ;SQLite 0 -> false 1 -> true. |
||||
:accessor can-delete-p)) |
||||
|
||||
(:documentation "`PAGE' represents the meta-data for the pages made by the |
||||
`USER' which are stored in the database. The actual pages are stored in the |
||||
/storage/pages directory.") |
||||
(:metaclass mito:dao-table-class)) |
@ -0,0 +1,50 @@
|
||||
(in-package #:cl-user) |
||||
(defpackage #:site-settings |
||||
(:use #:cl |
||||
#:ritherdon-archive.db |
||||
#:mito |
||||
#:app-constants) |
||||
(:export #:site-settings |
||||
#:nav-menu)) |
||||
|
||||
(in-package #:site-settings) |
||||
|
||||
(defclass site-settings () |
||||
((enable-sign-up |
||||
:documentation "Allow non-registered users to create accounts." |
||||
:col-type (or :integer :null) |
||||
:initarg :enable-sign-up |
||||
:initform +true+ ; SQLite: 0 -> false 1 -> true. |
||||
:accessor enable-sign-up-p) |
||||
|
||||
(enable-site-logo |
||||
:documentation "Show site logo in website's header." |
||||
:col-type (or :integer :null) |
||||
:initarg :enable-site-logo |
||||
:initform +true+ ; SQLite: 0 -> false 1 -> true. |
||||
:accessor enable-site-logo-p) |
||||
|
||||
(site-name |
||||
:documentation "The name of the site, shown in website's header." |
||||
:col-type (or :text :null) |
||||
:initarg :site-name |
||||
:initform "NERA" |
||||
:accessor site-name-of) |
||||
|
||||
(home-page |
||||
:documentation "The page (found in /storage/pages) which is rendered for '/' defroute." |
||||
:col-type (or :text :null) |
||||
:initarg :home-page |
||||
:initform "home" |
||||
:accessor home-page-of) |
||||
|
||||
(search-url |
||||
:documentation "The URL for the Meilisearch instance this site calls out to |
||||
for it's search features." |
||||
:col-type (or :text :null) |
||||
:initarg :search-url |
||||
:initform "http://localhost:7700" ; Default Meilisearch URL. |
||||
:accessor search-url-of)) |
||||
|
||||
(:documentation "Model used to track the site-wide settings -- stored in the database.") |
||||
(:metaclass mito:dao-table-class)) |
@ -0,0 +1,41 @@
|
||||
(defpackage #:user |
||||
(:use #:cl |
||||
#:ritherdon-archive.db |
||||
#:mito |
||||
#:app-constants) |
||||
(:export #:user)) |
||||
(in-package #:user) |
||||
|
||||
(defclass user () |
||||
((username |
||||
:documentation "The name the user uses to log into the website." |
||||
:col-type :text |
||||
:initarg :username |
||||
:accessor username-of) |
||||
|
||||
(display-name |
||||
:documentation "The name used in the website GUI (the pretty name)." |
||||
:col-type :text |
||||
:initarg :display-name |
||||
:accessor display-name-of) |
||||
|
||||
(password |
||||
:documentation "The user's password." |
||||
:col-type :text |
||||
:initarg :password |
||||
:accessor password-of) |
||||
|
||||
(administrator |
||||
:documentation "States if user has admin. priveledges. At the time |
||||
of writing (11/09/2022), SQLite is the current database and it |
||||
does not have a Boolean datatype so '0' represents 'false' and '1' |
||||
represents 'true'. You will not come across '0' or '1' in the code |
||||
because of how mito maps the code to the database. But, you will |
||||
see it in the database if you view it directly." |
||||
:col-type :integer |
||||
:initarg :administrator |
||||
:initform +false+ ; SQLite: 0 -> false 1 -> true. |
||||
:accessor is-administrator-p)) |
||||
|
||||
(:documentation "The model used to describe the `USER' table in the database") |
||||
(:metaclass mito:dao-table-class)) |
@ -0,0 +1,416 @@
|
||||
(in-package #:cl-user) |
||||
(defpackage #:nera-db |
||||
(:nicknames #:nera) |
||||
(:use #:cl |
||||
#:app-constants |
||||
#:hermetic |
||||
#:ritherdon-archive.db |
||||
#:utils |
||||
#:validation |
||||
#:user |
||||
#:pages |
||||
#:files |
||||
#:site-settings |
||||
#:archive) |
||||
(:export #:init-db |
||||
#:update-user |
||||
#:get-user |
||||
#:get-user-id |
||||
#:delete-user |
||||
#:create-user |
||||
#:get-site-settings |
||||
#:migrate-all |
||||
#:get-all-users |
||||
#:update-enable-sign-on-settings |
||||
#:set-home-page |
||||
#:update-enable-site-logo-setting |
||||
#:update-site-name |
||||
#:update-search-url |
||||
#:create-page |
||||
#:update-page |
||||
#:get-page |
||||
#:delete-page |
||||
#:get-all-pages |
||||
#:nav-menu-slugs |
||||
#:update-nav-menu |
||||
#:system-data |
||||
#:add-storage-file |
||||
#:get-storage-file |
||||
#:get-all-storage-files |
||||
#:rename-storage-file |
||||
#:delete-storage-file |
||||
#:get-all-archive-entries |
||||
#:create-archive-entry |
||||
#:get-archive-entry |
||||
#:delete-archive-entry |
||||
#:update-archive-entry-property |
||||
#:latest-archive-editted-entries |
||||
#:latest-editted-pages |
||||
#:latest-storage-editted-files |
||||
#:update-single-nav-menu-item |
||||
#:get-newer-archive-entries |
||||
#:get-older-archive-entries)) |
||||
(in-package #:nera-db) |
||||
|
||||
(defparameter *tables* '(user site-settings page storage-file archive-entry) |
||||
"List of the DB tables that need to be checked for migrations and DB setup.") |
||||
|
||||
(defun init-db (request) |
||||
"Creates the database and creates Admin. in `USER' table." |
||||
(destructuring-bind |
||||
(&key site-name allow-sign-up show-site-logo username display-name |
||||
password search-url &allow-other-keys) |
||||
(utils:request-params request) |
||||
(with-connection (db) |
||||
;; Add to the list to add more tables. |
||||
(mapcar #'mito:ensure-table-exists *tables*) |
||||
(mito:create-dao 'user |
||||
:username username |
||||
:display-name display-name |
||||
:password (hermetic::hash password) |
||||
:administrator +true+) |
||||
(mito:create-dao 'site-settings |
||||
:site-name site-name |
||||
:search-url search-url |
||||
:enable-sign-up (utils:checkbox-to-bool allow-sign-up) |
||||
:enable-site-logo (utils:checkbox-to-bool show-site-logo)) |
||||
(mito:create-dao 'page |
||||
:title "Home" |
||||
:slug "home" |
||||
:enable-nav-menu +true+ |
||||
:can-delete +true+) |
||||
(mito:create-dao 'page |
||||
:title "About" |
||||
:slug "about" |
||||
:enable-nav-menu +true+ |
||||
:can-delete +true+) |
||||
(mito:create-dao 'page |
||||
:title "Contact" |
||||
:slug "contact" |
||||
:enable-nav-menu +true+ |
||||
:can-delete +true+) |
||||
(mito:create-dao 'page |
||||
:title "Search" |
||||
:slug "search" |
||||
:enable-nav-menu +true+ |
||||
:can-delete +false+) |
||||
(mito:create-dao 'page |
||||
:title "Pages" |
||||
:slug "pages" |
||||
:enable-nav-menu +true+ |
||||
:can-delete +false+) |
||||
(mito:create-dao 'page |
||||
:title "Archive" |
||||
:slug "archive" |
||||
:enable-nav-menu +true+ |
||||
:can-delete +false+) |
||||
(mito:create-dao 'page |
||||
:title "Sign-Up" |
||||
:slug "sign-up" |
||||
:enable-nav-menu +true+ |
||||
:can-delete +false+) |
||||
(mito:create-dao 'page |
||||
:title "Log-In" |
||||
:slug "login" |
||||
:enable-nav-menu +true+ |
||||
:can-delete +false+)))) |
||||
|
||||
(defun ensure-tables-exist () |
||||
"Creates missing tables from the database." |
||||
(with-connection (db) |
||||
(mapcar #'mito:ensure-table-exists *tables*))) |
||||
|
||||
(defun migrate-all () |
||||
"Migrate the tables after we changed the class definition." |
||||
(with-connection (db) |
||||
(ensure-tables-exist) |
||||
(mapcar #'mito:migrate-table *tables*))) |
||||
|
||||
(defun create-user (username display-name password administrator) |
||||
"Add a new `USER' to the database." |
||||
(with-connection (db) |
||||
(mito:create-dao 'user |
||||
:username username |
||||
:display-name display-name |
||||
:administrator administrator |
||||
:password (hermetic::hash password)))) |
||||
|
||||
(defun delete-user (username) |
||||
"Deletes `USER' from the database." |
||||
(with-connection (db) |
||||
(mito:delete-by-values 'user:user :username username))) |
||||
|
||||
(defun update-user (username &key display-name new-password) |
||||
"Updates `USER' in database." |
||||
(with-connection (db) |
||||
(let ((user-to-update |
||||
(mito:find-dao 'user:user :username username))) |
||||
(if (not (validation:string-is-nil-or-empty? display-name)) |
||||
(setf (user::display-name-of user-to-update) display-name)) |
||||
(if (not (validation:string-is-nil-or-empty? new-password)) |
||||
(setf (user::password-of user-to-update) (hermetic::hash new-password))) |
||||
(mito:save-dao user-to-update)))) |
||||
|
||||
(defun get-user-id (username) |
||||
"Returns the Id. number of the specified `USERNAME' in the database." |
||||
(with-connection (db) |
||||
(mito:object-id |
||||
(mito:find-dao 'user :username username)))) |
||||
|
||||
(defun get-user (username) |
||||
"Returns a `USER' profile from the database." |
||||
(with-connection (db) |
||||
(mito:find-dao 'user :username username))) |
||||
|
||||
(defun get-all-users () |
||||
"Returns a list of all `USER' entries in the database." |
||||
(with-connection (db) |
||||
(mito:select-dao 'user |
||||
(sxql:order-by (:asc :display-name))))) |
||||
|
||||
(defun get-page (slug) |
||||
"Returns a `PAGE' from the database." |
||||
(with-connection (db) (mito:find-dao 'page :slug slug))) |
||||
|
||||
(defun get-all-pages () |
||||
"Returns a list of all `PAGE' entries in the database." |
||||
(with-connection (db) |
||||
(mito:select-dao 'page |
||||
(sxql:order-by (:asc :slug))))) |
||||
|
||||
(defun latest-editted-pages (amount &optional reverse) |
||||
"Gets the latest `AMOUNT' of edited entries from the database. |
||||
`REVERSE' is an optional parameter which puts the most recently |
||||
editted article entry as the first item in the list.." |
||||
(with-connection (db) |
||||
(mito:select-dao 'pages:page |
||||
(sxql:limit amount) |
||||
(if reverse |
||||
(sxql:order-by (:desc 'pages::updated-at)) |
||||
(sxql:order-by 'pages::updated-at))))) |
||||
|
||||
(defun create-page (title slug nav-menu can-delete) |
||||
"Add a new `PAGE' to the database." |
||||
(with-connection (db) |
||||
(mito:create-dao 'page :title title :slug slug |
||||
:enable-nav-menu nav-menu :can-delete can-delete))) |
||||
|
||||
(defun update-page (id title slug &optional nav-menu can-delete) |
||||
"Add a new `PAGE' to the database." |
||||
(with-connection (db) |
||||
(let ((page-to-update (mito:find-dao 'page :id id))) |
||||
(if (not (validation:string-is-nil-or-empty? title)) |
||||
(setf (pages::title-of page-to-update) title)) |
||||
(if (not (validation:string-is-nil-or-empty? slug)) |
||||
(setf (pages::slug-of page-to-update) slug)) |
||||
(if (not (null nav-menu)) |
||||
(setf (pages::enable-nav-menu-p page-to-update) nav-menu)) |
||||
(if (not (null can-delete)) |
||||
(setf (pages::can-delete-p page-to-update) can-delete)) |
||||
(mito:save-dao page-to-update)))) |
||||
|
||||
(defun delete-page (&key id slug) |
||||
"Delete `PAGE' from the database." |
||||
(with-connection (db) |
||||
(cond ((not slug) |
||||
(mito:delete-dao (mito:find-dao 'page :id id))) |
||||
((not id) |
||||
(mito:delete-dao (mito:find-dao 'page :slug slug))) |
||||
(t nil)))) |
||||
|
||||
(defun get-site-settings () |
||||
"Gets the settings for the website from the database." |
||||
(with-connection (db) |
||||
(mito:find-dao 'site-settings))) |
||||
|
||||
(defun update-enable-sign-on-settings (value) |
||||
"Updates the 'Enable Sign Up' setting in the database with `VALUE'." |
||||
(with-connection (db) |
||||
(let ((settings-to-update (mito:find-dao 'site-settings))) |
||||
(setf (site-settings::enable-sign-up-p settings-to-update) |
||||
(utils:checkbox-to-bool value)) |
||||
(mito:save-dao settings-to-update)))) |
||||
|
||||
(defun update-enable-site-logo-setting (value) |
||||
"Updates the 'Enable Site Logo' setting in the database with `VALUE'." |
||||
(with-connection (db) |
||||
(let ((settings-to-update (mito:find-dao 'site-settings))) |
||||
(setf (site-settings::enable-site-logo-p settings-to-update) |
||||
(utils:checkbox-to-bool value)) |
||||
(mito:save-dao settings-to-update)))) |
||||
|
||||
(defun set-home-page (value) |
||||
"Sets the page (in /storage/pages) to be displayed on the sites home page." |
||||
(with-connection (db) |
||||
(let ((settings-to-update (mito:find-dao 'site-settings))) |
||||
(setf (site-settings::home-page-of settings-to-update) value) |
||||
(mito:save-dao settings-to-update)))) |
||||
|
||||
(defun update-search-url (search-url) |
||||
"Sets the page (in /storage/pages) to be displayed on the sites home page." |
||||
(with-connection (db) |
||||
(let ((settings-to-update (mito:find-dao 'site-settings))) |
||||
(setf (site-settings::search-url-of settings-to-update) search-url) |
||||
(mito:save-dao settings-to-update)))) |
||||
|
||||
(defun update-site-name (name) |
||||
"Updates the website's `SITE-NAME' the database." |
||||
(with-connection (db) |
||||
(let ((settings-to-update (mito:find-dao 'site-settings))) |
||||
(setf (site-settings::site-name-of settings-to-update) name) |
||||
(mito:save-dao settings-to-update)))) |
||||
|
||||
(defun update-nav-menu (selected-pages) |
||||
"Updates the `ENABLE-NAV-MENU' property in `PAGE' database." |
||||
(loop for page in selected-pages |
||||
do (with-connection (db) |
||||
(let ((page-to-update (mito:find-dao 'page :slug (car page)))) |
||||
(setf (pages::enable-nav-menu-p page-to-update) |
||||
(utils:checkbox-to-bool (cdr page))) |
||||
(mito:save-dao page-to-update))))) |
||||
|
||||
(defun update-single-nav-menu-item (slug show-in-nav) |
||||
"Toggles a single page from the navigation menu." |
||||
(with-connection (db) |
||||
(let ((page-to-update (mito:find-dao 'page :slug slug))) |
||||
(setf (pages::enable-nav-menu-p page-to-update) show-in-nav) |
||||
;; (utils:checkbox-to-bool show-in-nav)) |
||||
(mito:save-dao page-to-update)))) |
||||
|
||||
(defun nav-menu-slugs () |
||||
(with-connection (db) |
||||
(mito:select-dao 'page |
||||
(sxql:where (:= :enable-nav-menu +true+))))) |
||||
|
||||
(defun system-data () |
||||
"Gets the website's settings and nav-menu from database." |
||||
(list (get-site-settings) (nav-menu-slugs))) |
||||
|
||||
(defun add-storage-file (filename slug file-type) |
||||
"Add a row to the 'storage_file' table in the database." |
||||
(with-connection (db) |
||||
(mito:create-dao 'storage-file |
||||
:name filename |
||||
:slug slug |
||||
:file-type file-type))) |
||||
|
||||
(defun get-storage-file (&key filename slug) |
||||
"Returns a `STORAGE-FILE' row from the database. `NIL' if nothing found." |
||||
(with-connection (db) |
||||
(if (null slug) |
||||
(mito:find-dao 'files:storage-file :name filename) |
||||
(mito:find-dao 'files:storage-file :slug slug)))) |
||||
|
||||
(defun latest-storage-editted-files (amount &optional reverse) |
||||
"Gets the latest `AMOUNT' of edited entries from the database. |
||||
`REVERSE' is an optional parameter which puts the most recently |
||||
editted article entry as the first item in the list.." |
||||
(with-connection (db) |
||||
(mito:select-dao 'storage-file |
||||
(sxql:limit amount) |
||||
(if reverse |
||||
(sxql:order-by (:desc 'storage::updated-at)) |
||||
(sxql:order-by 'storage::updated-at))))) |
||||
|
||||
(defun get-all-storage-files () |
||||
"Returns a list of all `STORAGE-FILES' entries in the database." |
||||
(with-connection (db) |
||||
(mito:select-dao 'storage-file |
||||
(sxql:order-by (:asc :name))))) |
||||
|
||||
(defun rename-storage-file (old-file-name new-file-name) |
||||
"Renames `STORAGE-FILE' in the database. |
||||
The `NAME' is renamed from `OLD-FILE-NAME' to `NEW-FILE-NAME' and the |
||||
slug is updated based on `NEW-FILE-NAME'." |
||||
(with-connection (db) |
||||
(let ((file-to-rename (mito:find-dao 'storage-file :name old-file-name))) |
||||
(setf (files::name-of file-to-rename) new-file-name |
||||
(files::slug-of file-to-rename) (utils:slugify new-file-name)) |
||||
(mito:save-dao file-to-rename)))) |
||||
|
||||
(defun delete-storage-file (&key name slug) |
||||
"Delete `STORAGE-FILE' from database." |
||||
(with-connection (db) |
||||
(if (null slug) |
||||
(mito:delete-by-values 'files:storage-file :name name) |
||||
(mito:delete-by-values 'files:storage-file :slug slug)))) |
||||
|
||||
(defun get-all-archive-entries () |
||||
"Returns a list of all `ARCHIVE-ENTRY' entries in the database." |
||||
(with-connection (db) |
||||
(mito:select-dao 'archive:archive-entry |
||||
(sxql:order-by (:desc :created-at))))) |
||||
|
||||
(defun create-archive-entry |
||||
(title search-id slug pub-month pub-year thumbnail-slug thumbnail-file-type keywords) |
||||
"Add a new `ARCHIVE-ENTRY' to the database." |
||||
(with-connection (db) |
||||
(mito:create-dao 'archive:archive-entry |
||||
:title title |
||||
:search-id search-id |
||||
:slug slug |
||||
:month pub-month |
||||
:year pub-year |
||||
:thumbnail-slug thumbnail-slug |
||||
:thumbnail-file-type thumbnail-file-type |
||||
:keywords keywords))) |
||||
|
||||
(defun get-archive-entry (&key id title slug) |
||||
"Returns a `ARCHIVE-ENTRY' from the database." |
||||
(with-connection (db) |
||||
(cond ((and (not title) (not slug)) |
||||
(mito:find-dao 'archive:archive-entry :id id)) |
||||
((and (not id) (not slug)) |
||||
(mito:find-dao 'archive:archive-entry :title title)) |
||||
((and (not id) (not title)) |
||||
(mito:find-dao 'archive:archive-entry :slug slug)) |
||||
(t nil)))) |
||||
|
||||
(defun latest-archive-editted-entries (amount &optional reverse) |
||||
"Gets the latest `AMOUNT' of edited entries from the database. |
||||
`REVERSE' is an optional parameter which puts the most recently |
||||
editted article entry as the first item in the list.." |
||||
(with-connection (db) |
||||
(mito:select-dao 'archive:archive-entry |
||||
(sxql:limit amount) |
||||
(if reverse |
||||
(sxql:order-by (:desc 'archive::updated-at)) |
||||
(sxql:order-by 'archive::updated-at))))) |
||||
|
||||
(defun update-archive-entry-property (&key slug property value) |
||||
"Updates an `ARCHIVE-ENTRY' entry in database. |
||||
An example of how to use this function is as follows (remove back-slashes): |
||||
|
||||
(nera:update-archive-entry-property |
||||
:slug \"edit-archive-test.html\" |
||||
:propery 'archive::keywords-of |
||||
:value \"test,image\")" |
||||
(with-connection (db) |
||||
(let ((entry-to-update |
||||
(mito:find-dao 'archive:archive-entry :slug slug))) |
||||
(eval `(setf (,property ,entry-to-update) ,value)) |
||||
(mito:save-dao entry-to-update)))) |
||||
|
||||
(defun delete-archive-entry (&key id slug) |
||||
"Delete `ARCHIVE-ENTRY' from the database." |
||||
(with-connection (db) |
||||
(cond ((not slug) |
||||
(mito:delete-dao (mito:find-dao 'archive:archive-entry :id id))) |
||||
((not id) |
||||
(mito:delete-dao (mito:find-dao 'archive:archive-entry :slug slug)))))) |
||||
|
||||
(defun get-newer-archive-entries (id amount) |
||||
"Returns `AMOUNT' of `ARCHIVE-ENTRY' objects relative to `ID' in database." |
||||
(with-connection (db) |
||||
(mito:select-dao 'archive:archive-entry |
||||
(sxql:where (:> :id id)) |
||||
(sxql:order-by :id) |
||||
(sxql:limit amount)))) |
||||
|
||||
(defun get-older-archive-entries (id amount) |
||||
"Returns `AMOUNT' of `ARCHIVE-ENTRY' objects relative to `ID' in database." |
||||
(with-connection (db) |
||||
(mito:select-dao 'archive:archive-entry |
||||
(sxql:where (:< :id id)) |
||||
(sxql:order-by (:desc :id)) |
||||
(sxql:limit amount)))) |
@ -0,0 +1,185 @@
|
||||
(defpackage #:search |
||||
(:use #:cl |
||||
#:app-constants |
||||
#:archive |
||||
#:cl-json |
||||
#:local-time |
||||
#:utils |
||||
#:site-settings |
||||
#:nera) |
||||
(: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)) |
||||
|
||||
(format nil "~a~a" (site-settings::search-url-of (nera:get-site-settings)) 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))))) |
@ -0,0 +1,62 @@
|
||||
(in-package #:cl-user) |
||||
(defpackage #:snapshot |
||||
(:use #:cl |
||||
#:utils |
||||
#:storage |
||||
#:nera) |
||||
(:export |
||||
#:take-snapshot |
||||
#:restore-from-snapshot |
||||
#:delete-snapshot |
||||
#:store-snapshot)) |
||||
(in-package #:snapshot) |
||||
|
||||
(defun take-snapshot () |
||||
"Takes a Snapshot of the website's data and stores it in /snapshots. |
||||
I've not included make a SQL dump of the Meilisearch database here because the |
||||
user can repopulate that database after they have restored this website. The |
||||
'repopulate' feature is built into the website already." |
||||
(let ((snapshot-directory |
||||
(format nil "~a_~a/" |
||||
(utils:slugify |
||||
(site-settings::site-name-of (nera:get-site-settings))) |
||||
(utils:create-timestamp-text)))) |
||||
(storage:ensure-raw-directory-exists |
||||
(format nil "snapshots/~a" snapshot-directory)) |
||||
(storage:copy-storage-directory |
||||
(format nil "snapshots/~a/storage/" snapshot-directory)) |
||||
(storage:copy-raw-directory |
||||
"db/" (format nil "snapshots/~a/db/" snapshot-directory)))) |
||||
|
||||
(defun restore-from-snapshot (snapshot-name) |
||||
"Deletes the data in /storage and the DB and replaces it with `SNAPSHOT-NAME'." |
||||
(storage:remove-raw-directory "storage/") |
||||
(storage:remove-raw-directory "db/") |
||||
(storage:copy-raw-directory (format nil "snapshots/~a/storage/" snapshot-name) "storage/") |
||||
(storage:copy-raw-directory (format nil "snapshots/~a/db/" snapshot-name) "db/")) |
||||
|
||||
(defun delete-snapshot (snapshot-name) |
||||
"Deletes the snapshot in the /snapshots directory with `SNAPSHOT-NAME'." |
||||
(storage:remove-raw-directory (format nil "snapshots/~a/" snapshot-name))) |
||||
|
||||
(defun store-snapshot (filename data) |
||||
"Unzips `SNAPSHOT-FILE' and stores it in /snapshots directory. |
||||
|
||||
The .zip file is deleted after it has been un-zipped. I did think about moving |
||||
the zipped version of the file into the /storage/media directory but there is |
||||
too much second guessing going on. I found I was uploading .zip files from the |
||||
/storage/media directory and I don't know if users will be uploading .zip files |
||||
they had just downloaded from /storage/media. If that is the case, then there |
||||
is: |
||||
|
||||
a.) No need to move it to /storage/madia; |
||||
b.) Extra work regarding checks for duplicate entries; and, |
||||
c.) I don't know if that is too much 'magic' for users and cause confusion. |
||||
|
||||
If the user is in a paniced state -- trying to restore their website, confusion |
||||
is the thing I want to keep to a minimum for them." |
||||
(storage:store-with-raw-path (format nil "snapshots/~a" filename) data) |
||||
(zip:unzip (storage:make-raw-path (format nil"snapshots/~a" filename)) |
||||
(storage:make-raw-path (format nil "snapshots/~a/" |
||||
(pathname-name filename)))) |
||||
(storage:remove-file-with-raw-path (format nil "snapshots/~a" filename))) |
@ -0,0 +1,498 @@
|
||||
(in-package #:cl-user) |
||||
(defpackage #:status-codes |
||||
(:use #:cl |
||||
#:app-constants) |
||||
(:nicknames #:rfc2616-sec10) |
||||
(:export +continue+ |
||||
+switching-protocols+ |
||||
+ok+ |
||||
+created+ |
||||
+accepted+ |
||||
+non-authoritative-information+ |
||||
+no-content+ |
||||
+reset-content+ |
||||
+partial-content+ |
||||
+multiple-choices+ |
||||
+moved-permanently+ |
||||
+found+ |
||||
+see-other+ |
||||
+not-modified+ |
||||
+use-proxy+ |
||||
+temporary-redirect+ |
||||
+bad-request+ |
||||
+unauthorized+ |
||||
+payment-required+ |
||||
+forbidden+ |
||||
+not-found+ |
||||
+method-not-allowed+ |
||||
+not-acceptable+ |
||||
+proxy-authentication-required+ |
||||
+request-timeout+ |
||||
+conflict+ |
||||
+gone+ |
||||
+length-required+ |
||||
+precondition-failed+ |
||||
+request-entity-too-large+ |
||||
+request-uri-too-long+ |
||||
+unsupported-media-type+ |
||||
+requested-range-not-satisfiable+ |
||||
+expectation-failed+ |
||||
+internal-server-error+ |
||||
+not-implemented+ |
||||
+bad-gateway+ |
||||
+service-unavailable+ |
||||
+gateway-timeout+ |
||||
+http-version-not-supported+)) |
||||
(in-package #:status-codes) |
||||
|
||||
#| On Using `DEFINE-CONSTANT' Macro |
||||
================================================================================ |
||||
I've defined the `DEFINE-CONSTANT' in 'app-constants.lisp' I've explained the |
||||
reason why I've using it instead of `DEFCONSTANT' there. The short-story is |
||||
`DEFINE-CONSTANT' is easier to work with when using SBCL. |
||||
|# |
||||
|
||||
(define-constant +continue+ 100 |
||||
"The client SHOULD continue with its request. This interim response is |
||||
used to inform the client that the initial part of the request has |
||||
been received and has not yet been rejected by the server. The client |
||||
SHOULD continue by sending the remainder of the request or, if the |
||||
request has already been completed, ignore this response. The server |
||||
MUST send a final response after the request has been completed. See |
||||
section 8.2.3 for detailed discussion of the use and handling of this |
||||
status code.") |
||||
(define-constant +switching-PROTOCOLS+ 101 |
||||
"The server understands and is willing to comply with the client's |
||||
request, via the Upgrade message header field (section 14.42), for a |
||||
change in the application protocol being used on this connection. The |
||||
server will switch protocols to those defined by the response's |
||||
Upgrade header field immediately after the empty line which |
||||
terminates the 101 response. |
||||
The protocol SHOULD be switched only when it is advantageous to do |
||||
so. For example, switching to a newer version of HTTP is advantageous |
||||
over older versions, and switching to a real-time, synchronous |
||||
protocol might be advantageous when delivering resources that use |
||||
such features.") |
||||
(define-constant +ok+ 200 |
||||
"The request has succeeded. The information returned with the response |
||||
is dependent on the method used in the request, for example: |
||||
GET an entity corresponding to the requested resource is sent in |
||||
the response; |
||||
HEAD the entity-header fields corresponding to the requested |
||||
resource are sent in the response without any message-body; |
||||
POST an entity describing or containing the result of the action; |
||||
TRACE an entity containing the request message as received by the |
||||
end server.") |
||||
(define-constant +created+ 201 |
||||
"The request has been fulfilled and resulted in a new resource being |
||||
created. The newly created resource can be referenced by the URI(s) |
||||
returned in the entity of the response, with the most specific URI |
||||
for the resource given by a Location header field. The response |
||||
SHOULD include an entity containing a list of resource |
||||
characteristics and location(s) from which the user or user agent can |
||||
choose the one most appropriate. The entity format is specified by |
||||
the media type given in the Content-Type header field. The origin |
||||
server MUST create the resource before returning the 201 status code. |
||||
If the action cannot be carried out immediately, the server SHOULD |
||||
respond with 202 (Accepted) response instead. |
||||
A 201 response MAY contain an ETag response header field indicating |
||||
the current value of the entity tag for the requested variant just |
||||
created, see section 14.19.") |
||||
(define-constant +accepted+ 202 |
||||
"The request has been accepted for processing, but the processing has |
||||
not been completed. The request might or might not eventually be |
||||
acted upon, as it might be disallowed when processing actually takes |
||||
place. There is no facility for re-sending a status code from an |
||||
asynchronous operation such as this. |
||||
The 202 response is intentionally non-committal. Its purpose is to |
||||
allow a server to accept a request for some other process (perhaps a |
||||
batch-oriented process that is only run once per day) without |
||||
requiring that the user agent's connection to the server persist |
||||
until the process is completed. The entity returned with this |
||||
response SHOULD include an indication of the request's current status |
||||
and either a pointer to a status monitor or some estimate of when the |
||||
user can expect the request to be fulfilled.") |
||||
(define-constant +non-authoritative-information+ 203 |
||||
"The returned metainformation in the entity-header is not the |
||||
definitive set as available from the origin server, but is gathered |
||||
from a local or a third-party copy. The set presented MAY be a subset |
||||
or superset of the original version. For example, including local |
||||
annotation information about the resource might result in a superset |
||||
of the metainformation known by the origin server. Use of this |
||||
response code is not required and is only appropriate when the |
||||
response would otherwise be 200 (OK).") |
||||
(define-constant +no-content+ 204 |
||||
"The server has fulfilled the request but does not need to return an |
||||
entity-body, and might want to return updated metainformation. The |
||||
response MAY include new or updated metainformation in the form of |
||||
entity-headers, which if present SHOULD be associated with the |
||||
requested variant. |
||||
If the client is a user agent, it SHOULD NOT change its document view |
||||
from that which caused the request to be sent. This response is |
||||
primarily intended to allow input for actions to take place without |
||||
causing a change to the user agent's active document view, although |
||||
any new or updated metainformation SHOULD be applied to the document |
||||
currently in the user agent's active view. |
||||
The 204 response MUST NOT include a message-body, and thus is always |
||||
terminated by the first empty line after the header fields.") |
||||
(define-constant +reset-content+ 205 |
||||
"The server has fulfilled the request and the user agent SHOULD reset |
||||
the document view which caused the request to be sent. This response |
||||
is primarily intended to allow input for actions to take place via |
||||
user input, followed by a clearing of the form in which the input is |
||||
given so that the user can easily initiate another input action. The |
||||
response MUST NOT include an entity.") |
||||
(define-constant +partial-content+ 206 |
||||
"The server has fulfilled the partial GET request for the resource. |
||||
The request MUST have included a Range header field (section 14.35) |
||||
indicating the desired range, and MAY have included an If-Range |
||||
header field (section 14.27) to make the request conditional. |
||||
The response MUST include the following header fields: |
||||
- Either a Content-Range header field (section 14.16) indicating |
||||
the range included with this response, or a multipart/byteranges |
||||
Content-Type including Content-Range fields for each part. If a |
||||
Content-Length header field is present in the response, its |
||||
value MUST match the actual number of OCTETs transmitted in the |
||||
message-body. |
||||
- Date |
||||
- ETag and/or Content-Location, if the header would have been sent |
||||
in a 200 response to the same request |
||||
- Expires, Cache-Control, and/or Vary, if the field-value might |
||||
differ from that sent in any previous response for the same |
||||
variant |
||||
If the 206 response is the result of an If-Range request that used a |
||||
strong cache validator (see section 13.3.3), the response SHOULD NOT |
||||
include other entity-headers. If the response is the result of an |
||||
If-Range request that used a weak validator, the response MUST NOT |
||||
include other entity-headers; this prevents inconsistencies between |
||||
cached entity-bodies and updated headers. Otherwise, the response |
||||
MUST include all of the entity-headers that would have been returned |
||||
with a 200 (OK) response to the same request. |
||||
A cache MUST NOT combine a 206 response with other previously cached |
||||
content if the ETag or Last-Modified headers do not match exactly, |
||||
see 13.5.4. |
||||
A cache that does not support the Range and Content-Range headers |
||||
MUST NOT cache 206 (Partial) responses.") |
||||
(define-constant +multiple-choices+ 300 |
||||
"The requested resource corresponds to any one of a set of |
||||
representations, each with its own specific location, and agent- |
||||
driven negotiation information (section 12) is being provided so that |
||||
the user (or user agent) can select a preferred representation and |
||||
redirect its request to that location. |
||||
Unless it was a HEAD request, the response SHOULD include an entity |
||||
containing a list of resource characteristics and location(s) from |
||||
which the user or user agent can choose the one most appropriate. The |
||||
entity format is specified by the media type given in the Content- |
||||
Type header field. Depending upon the format and the capabilities of |
||||
the user agent, selection of the most appropriate choice MAY be |
||||
performed automatically. However, this specification does not define |
||||
any standard for such automatic selection. |
||||
If the server has a preferred choice of representation, it SHOULD |
||||
include the specific URI for that representation in the Location |
||||
field; user agents MAY use the Location field value for automatic |
||||
redirection. This response is cacheable unless indicated otherwise.") |
||||
(define-constant +moved-permanently+ 301 |
||||
"The requested resource has been assigned a new permanent URI and any |
||||
future references to this resource SHOULD use one of the returned |
||||
URIs. Clients with link editing capabilities ought to automatically |
||||
re-link references to the Request-URI to one or more of the new |
||||
references returned by the server, where possible. This response is |
||||
cacheable unless indicated otherwise. |
||||
The new permanent URI SHOULD be given by the Location field in the |
||||
response. Unless the request method was HEAD, the entity of the |
||||
response SHOULD contain a short hypertext note with a hyperlink to |
||||
the new URI(s). |
||||
If the 301 status code is received in response to a request other |
||||
than GET or HEAD, the user agent MUST NOT automatically redirect the |
||||
request unless it can be confirmed by the user, since this might |
||||
change the conditions under which the request was issued. |
||||
Note: When automatically redirecting a POST request after |
||||
receiving a 301 status code, some existing HTTP/1.0 user agents |
||||
will erroneously change it into a GET request.") |
||||
(define-constant +found+ 302 |
||||
"The requested resource resides temporarily under a different URI. |
||||
Since the redirection might be altered on occasion, the client SHOULD |
||||
continue to use the Request-URI for future requests. This response |
||||
is only cacheable if indicated by a Cache-Control or Expires header |
||||
field. |
||||
The temporary URI SHOULD be given by the Location field in the |
||||
response. Unless the request method was HEAD, the entity of the |
||||
response SHOULD contain a short hypertext note with a hyperlink to |
||||
the new URI(s). |
||||
If the 302 status code is received in response to a request other |
||||
than GET or HEAD, the user agent MUST NOT automatically redirect the |
||||
request unless it can be confirmed by the user, since this might |
||||
change the conditions under which the request was issued. |
||||
Note: RFC 1945 and RFC 2068 specify that the client is not allowed |
||||
to change the method on the redirected request. However, most |
||||
existing user agent implementations treat 302 as if it were a 303 |
||||
response, performing a GET on the Location field-value regardless |
||||
of the original request method. The status codes 303 and 307 have |
||||
been added for servers that wish to make unambiguously clear which |
||||
kind of reaction is expected of the client.") |
||||
(define-constant +see-other+ 303 |
||||
"The response to the request can be found under a different URI and |
||||
SHOULD be retrieved using a GET method on that resource. This method |
||||
exists primarily to allow the output of a POST-activated script to |
||||
redirect the user agent to a selected resource. The new URI is not a |
||||
substitute reference for the originally requested resource. The 303 |
||||
response MUST NOT be cached, but the response to the second |
||||
(redirected) request might be cacheable. |
||||
The different URI SHOULD be given by the Location field in the |
||||
response. Unless the request method was HEAD, the entity of the |
||||
response SHOULD contain a short hypertext note with a hyperlink to |
||||
the new URI(s). |
||||
Note: Many pre-HTTP/1.1 user agents do not understand the 303 |
||||
status. When interoperability with such clients is a concern, the |
||||
302 status code may be used instead, since most user agents react |
||||
to a 302 response as described here for 303.") |
||||
(define-constant +not-modified+ 304 |
||||
"If the client has performed a conditional GET request and access is |
||||
allowed, but the document has not been modified, the server SHOULD |
||||
respond with this status code. The 304 response MUST NOT contain a |
||||
message-body, and thus is always terminated by the first empty line |
||||
after the header fields. |
||||
The response MUST include the following header fields: |
||||
- Date, unless its omission is required by section 14.18.1 |
||||
If a clockless origin server obeys these rules, and proxies and |
||||
clients add their own Date to any response received without one (as |
||||
already specified by [RFC 2068], section 14.19), caches will operate |
||||
correctly. |
||||
- ETag and/or Content-Location, if the header would have been sent |
||||
in a 200 response to the same request |
||||
- Expires, Cache-Control, and/or Vary, if the field-value might |
||||
differ from that sent in any previous response for the same |
||||
variant |
||||
If the conditional GET used a strong cache validator (see section |
||||
13.3.3), the response SHOULD NOT include other entity-headers. |
||||
Otherwise (i.e., the conditional GET used a weak validator), the |
||||
response MUST NOT include other entity-headers; this prevents |
||||
inconsistencies between cached entity-bodies and updated headers. |
||||
If a 304 response indicates an entity not currently cached, then the |
||||
cache MUST disregard the response and repeat the request without the |
||||
conditional. |
||||
If a cache uses a received 304 response to update a cache entry, the |
||||
cache MUST update the entry to reflect any new field values given in |
||||
the response.") |
||||
(define-constant +use-proxy+ 305 |
||||
"The requested resource MUST be accessed through the proxy given by |
||||
the Location field. The Location field gives the URI of the proxy. |
||||
The recipient is expected to repeat this single request via the |
||||
proxy. 305 responses MUST only be generated by origin servers. |
||||
Note: RFC 2068 was not clear that 305 was intended to redirect a |
||||
single request, and to be generated by origin servers only. Not |
||||
observing these limitations has significant security consequences.") |
||||
(define-constant +temporary-redirect+ 307 |
||||
"The requested resource resides temporarily under a different URI. |
||||
Since the redirection MAY be altered on occasion, the client SHOULD |
||||
continue to use the Request-URI for future requests. This response |
||||
is only cacheable if indicated by a Cache-Control or Expires header |
||||
field. |
||||
The temporary URI SHOULD be given by the Location field in the |
||||
response. Unless the request method was HEAD, the entity of the |
||||
response SHOULD contain a short hypertext note with a hyperlink to |
||||
the new URI(s) , since many pre-HTTP/1.1 user agents do not |
||||
understand the 307 status. Therefore, the note SHOULD contain the |
||||
information necessary for a user to repeat the original request on |
||||
the new URI. |
||||
If the 307 status code is received in response to a request other |
||||
than GET or HEAD, the user agent MUST NOT automatically redirect the |
||||
request unless it can be confirmed by the user, since this might |
||||
change the conditions under which the request was issued.") |
||||
(define-constant +bad-request+ 400 |
||||
"The request could not be understood by the server due to malformed |
||||
syntax. The client SHOULD NOT repeat the request without |
||||
modifications.") |
||||
(define-constant +unauthorized+ 401 |
||||
"The request requires user authentication. The response MUST include a |
||||
WWW-Authenticate header field (section 14.47) containing a challenge |
||||
applicable to the requested resource. The client MAY repeat the |
||||
request with a suitable Authorization header field (section 14.8). If |
||||
the request already included Authorization credentials, then the 401 |
||||
response indicates that authorization has been refused for those |
||||
credentials. If the 401 response contains the same challenge as the |
||||
prior response, and the user agent has already attempted |
||||
authentication at least once, then the user SHOULD be presented the |
||||
entity that was given in the response, since that entity might |
||||
include relevant diagnostic information. HTTP access authentication |
||||
is explained in \"HTTP Authentication: Basic and Digest Access |
||||
Authentication\" [43].") |
||||
(define-constant +payment-required+ 402 |
||||
"This code is reserved for future use.") |
||||
(define-constant +forbidden+ 403 |
||||
"The server understood the request, but is refusing to fulfill it. |
||||
Authorization will not help and the request SHOULD NOT be repeated. |
||||
If the request method was not HEAD and the server wishes to make |
||||
public why the request has not been fulfilled, it SHOULD describe the |
||||
reason for the refusal in the entity. If the server does not wish to |
||||
make this information available to the client, the status code 404 |
||||
(Not Found) can be used instead.") |
||||
(define-constant +not-found+ 404 |
||||
"The server has not found anything matching the Request-URI. No |
||||
indication is given of whether the condition is temporary or |
||||
permanent. The 410 (Gone) status code SHOULD be used if the server |
||||
knows, through some internally configurable mechanism, that an old |
||||
resource is permanently unavailable and has no forwarding address. |
||||
This status code is commonly used when the server does not wish to |
||||
reveal exactly why the request has been refused, or when no other |
||||
response is applicable.") |
||||
(define-constant +method-not-allowed+ 405 |
||||
"The method specified in the Request-Line is not allowed for the |
||||
resource identified by the Request-URI. The response MUST include an |
||||
Allow header containing a list of valid methods for the requested |
||||
resource.") |
||||
(define-constant +not-acceptable+ 406 |
||||
"The resource identified by the request is only capable of generating |
||||
response entities which have content characteristics not acceptable |
||||
according to the accept headers sent in the request. |
||||
Unless it was a HEAD request, the response SHOULD include an entity |
||||
containing a list of available entity characteristics and location(s) |
||||
from which the user or user agent can choose the one most |
||||
appropriate. The entity format is specified by the media type given |
||||
in the Content-Type header field. Depending upon the format and the |
||||
capabilities of the user agent, selection of the most appropriate |
||||
choice MAY be performed automatically. However, this specification |
||||
does not define any standard for such automatic selection. |
||||
Note: HTTP/1.1 servers are allowed to return responses which are |
||||
not acceptable according to the accept headers sent in the |
||||
request. In some cases, this may even be preferable to sending a |
||||
406 response. User agents are encouraged to inspect the headers of |
||||
an incoming response to determine if it is acceptable. |
||||
If the response could be unacceptable, a user agent SHOULD |
||||
temporarily stop receipt of more data and query the user for a |
||||
decision on further actions.") |
||||
(define-constant +proxy-authentication-required+ 407 |
||||
"This code is similar to 401 (Unauthorized), but indicates that the |
||||
client must first authenticate itself with the proxy. The proxy MUST |
||||
return a Proxy-Authenticate header field (section 14.33) containing a |
||||
challenge applicable to the proxy for the requested resource. The |
||||
client MAY repeat the request with a suitable Proxy-Authorization |
||||
header field (section 14.34). HTTP access authentication is explained |
||||
in \"HTTP Authentication: Basic and Digest Access Authentication\" |
||||
[43].") |
||||
(define-constant +request-timeout+ 408 |
||||
"The client did not produce a request within the time that the server |
||||
was prepared to wait. The client MAY repeat the request without |
||||
modifications at any later time.") |
||||
(define-constant +conflict+ 409 |
||||
"The request could not be completed due to a conflict with the current |
||||
state of the resource. This code is only allowed in situations where |
||||
it is expected that the user might be able to resolve the conflict |
||||
and resubmit the request. The response body SHOULD include enough |
||||
information for the user to recognize the source of the conflict. |
||||
Ideally, the response entity would include enough information for the |
||||
user or user agent to fix the problem; however, that might not be |
||||
possible and is not required. |
||||
Conflicts are most likely to occur in response to a PUT request. For |
||||
example, if versioning were being used and the entity being PUT |
||||
included changes to a resource which conflict with those made by an |
||||
earlier (third-party) request, the server might use the 409 response |
||||
to indicate that it can't complete the request. In this case, the |
||||
response entity would likely contain a list of the differences |
||||
between the two versions in a format defined by the response |
||||
Content-Type.") |
||||
(define-constant +gone+ 410 |
||||
"The requested resource is no longer available at the server and no |
||||
forwarding address is known. This condition is expected to be |
||||
considered permanent. Clients with link editing capabilities SHOULD |
||||
delete references to the Request-URI after user approval. If the |
||||
server does not know, or has no facility to determine, whether or not |
||||
the condition is permanent, the status code 404 (Not Found) SHOULD be |
||||
used instead. This response is cacheable unless indicated otherwise. |
||||
The 410 response is primarily intended to assist the task of web |
||||
maintenance by notifying the recipient that the resource is |
||||
intentionally unavailable and that the server owners desire that |
||||
remote links to that resource be removed. Such an event is common for |
||||
limited-time, promotional services and for resources belonging to |
||||
individuals no longer working at the server's site. It is not |
||||
necessary to mark all permanently unavailable resources as \"gone\" or |
||||
to keep the mark for any length of time -- that is left to the |
||||
discretion of the server owner.") |
||||
(define-constant +length-required+ 411 |
||||
"The server refuses to accept the request without a defined Content- |
||||
Length. The client MAY repeat the request if it adds a valid |
||||
Content-Length header field containing the length of the message-body |
||||
in the request message.") |
||||
(define-constant +precondition-failed+ 412 |
||||
"The precondition given in one or more of the request-header fields |
||||
evaluated to false when it was tested on the server. This response |
||||
code allows the client to place preconditions on the current resource |
||||
metainformation (header field data) and thus prevent the requested |
||||
method from being applied to a resource other than the one intended.") |
||||
(define-constant +request-entity-too-large+ 413 |
||||
"The server is refusing to process a request because the request |
||||
entity is larger than the server is willing or able to process. The |
||||
server MAY close the connection to prevent the client from continuing |
||||
the request. |
||||
If the condition is temporary, the server SHOULD include a Retry- |
||||
After header field to indicate that it is temporary and after what |
||||
time the client MAY try again.") |
||||
(define-constant +request-uri-too-long+ 414 |
||||
"The server is refusing to service the request because the Request-URI |
||||
is longer than the server is willing to interpret. This rare |
||||
condition is only likely to occur when a client has improperly |
||||
converted a POST request to a GET request with long query |
||||
information, when the client has descended into a URI \"black hole\" of |
||||
redirection (e.g., a redirected URI prefix that points to a suffix of |
||||
itself), or when the server is under attack by a client attempting to |
||||
exploit security holes present in some servers using fixed-length |
||||
buffers for reading or manipulating the Request-URI.") |
||||
(define-constant +unsupported-media-type+ 415 |
||||
"The server is refusing to service the request because the entity of |
||||
the request is in a format not supported by the requested resource |
||||
for the requested method.") |
||||
(define-constant +requested-range-not-satisfiable+ 416 |
||||
"A server SHOULD return a response with this status code if a request |
||||
included a Range request-header field (section 14.35), and none of |
||||
the range-specifier values in this field overlap the current extent |
||||
of the selected resource, and the request did not include an If-Range |
||||
request-header field. (For byte-ranges, this means that the first- |
||||
byte-pos of all of the byte-range-spec values were greater than the |
||||
current length of the selected resource.) |
||||
When this status code is returned for a byte-range request, the |
||||
response SHOULD include a Content-Range entity-header field |
||||
specifying the current length of the selected resource (see section |
||||
14.16). This response MUST NOT use the multipart/byteranges content- |
||||
type.") |
||||
(define-constant +expectation-failed+ 417 |
||||
"The expectation given in an Expect request-header field (see section |
||||
14.20) could not be met by this server, or, if the server is a proxy, |
||||
the server has unambiguous evidence that the request could not be met |
||||
by the next-hop server.") |
||||
(define-constant +internal-server-error+ 500 |
||||
"The server encountered an unexpected condition which prevented it |
||||
from fulfilling the request.") |
||||
(define-constant +not-implemented+ 501 |
||||
"The server does not support the functionality required to fulfill the |
||||
request. This is the appropriate response when the server does not |
||||
recognize the request method and is not capable of supporting it for |
||||
any resource.") |
||||
(define-constant +bad-gateway+ 502 |
||||
"The server, while acting as a gateway or proxy, received an invalid |
||||
response from the upstream server it accessed in attempting to |
||||
fulfill the request.") |
||||
(define-constant +service-unavailable+ 503 |
||||
"The server is currently unable to handle the request due to a |
||||
temporary overloading or maintenance of the server. The implication |
||||
is that this is a temporary condition which will be alleviated after |
||||
some delay. If known, the length of the delay MAY be indicated in a |
||||
Retry-After header. If no Retry-After is given, the client SHOULD |
||||
handle the response as it would for a 500 response. |
||||
Note: The existence of the 503 status code does not imply that a |
||||
server must use it when becoming overloaded. Some servers may wish |
||||
to simply refuse the connection.") |
||||
(define-constant +gateway-timeout+ 504 |
||||
"The server, while acting as a gateway or proxy, did not receive a |
||||
timely response from the upstream server specified by the URI (e.g. |
||||
HTTP, FTP, LDAP) or some other auxiliary server (e.g. DNS) it needed |
||||
to access in attempting to complete the request. |
||||
Note: Note to implementors: some deployed proxies are known to |
||||
return 400 or 500 when DNS lookups time out.") |
||||
(define-constant +http-version-not-supported+ 505 |
||||
"The server does not support, or refuses to support, the HTTP protocol |
||||
version that was used in the request message. The server is |
||||
indicating that it is unable or unwilling to complete the request |
||||
using the same major version as the client, as described in section |
||||
3.1, other than with this error message. The response SHOULD contain |
||||
an entity describing why that version is not supported and what other |
||||
protocols are supported by that server.") |
@ -0,0 +1,317 @@
|
||||
(in-package #:cl-user) |
||||
(defpackage #:storage |
||||
(:use #:cl |
||||
#:copy-directory) |
||||
(:export #:init-storage |
||||
#:directory-exists-p |
||||
#:ensure-directory-exists |
||||
#:file-exists-p |
||||
#:get-files-in-directory |
||||
#:get-file-names |
||||
#:make-path |
||||
#:open-file |
||||
#:open-binary-file |
||||
#:open-text-file |
||||
#:remove-directory |
||||
#:remove-file |
||||
#:rename-content-file |
||||
#:rename-directory |
||||
#:store-file |
||||
#:store-text |
||||
#:store-with-raw-path |
||||
#:store-text-with-raw-path |
||||
#:open-text-file-with-raw-path |
||||
#:store-test |
||||
#:remove-file-with-raw-path |
||||
#:make-raw-path |
||||
#:ensure-raw-directory-exists |
||||
#:remove-raw-directory |
||||
#:copy-storage-directory |
||||
#:copy-raw-directory |
||||
#:get-files-in-raw-directory |
||||
#:get-raw-subdirectories |
||||
#:raw-directory-exists?)) |
||||
(in-package #:storage) |
||||
|
||||
(defun init-storage () |
||||
"Copies the initial files into their default places. |
||||
This is used as part of the /run-set defroute in web.lisp file." |
||||
(ensure-directory-exists "" "pages") |
||||
(uiop:copy-file (make-path "" "default-assets" "about") |
||||
(make-path "" "pages" "about")) |
||||
|
||||
(uiop:copy-file (make-path "" "default-assets" "contact") |
||||
(make-path "" "pages" "contact")) |
||||
|
||||
(uiop:copy-file (make-path "" "default-assets" "home") |
||||
(make-path "" "pages" "home")) |
||||
|
||||
(uiop:copy-file (make-path "" "default-assets" "site-logo.png") |
||||
(merge-pathnames "static/images/site-logo.png" |
||||
ritherdon-archive.config:*application-root*)) |
||||
(uiop:copy-file (make-path "" "default-assets" "favicon.png") |
||||
(merge-pathnames "static/images/favicon.png" |
||||
ritherdon-archive.config:*application-root*)) |
||||
(ensure-directory-exists "" "snippets") |
||||
(uiop:copy-file (make-path "" "default-assets" "site-wide-snippet.html") |
||||
(make-path "" "snippets" "site-wide-snippet.html")) |
||||
;; Nothing is added to /storage/media yet, this is just prep. work. |
||||
(ensure-directory-exists "" "media") |
||||
(ensure-raw-directory-exists "snapshots/")) |
||||
|
||||
(defun copy-storage-directory (target-path) |
||||
"Copies the contents of /storage directory to `TARGET-PATH'. |
||||
Make sure `TARGET-PATH' ends with a slash (E.G. snapshots/oct-2022/). Without |
||||
it, the system will assume you're trying to work with a file and throw an |
||||
error." |
||||
(copy-directory:copy (make-raw-path "storage/") (make-raw-path target-path))) |
||||
|
||||
(defun copy-raw-directory (source-path target-path) |
||||
"Copies a directory (`SOURCE-PATH') outside of /storage to `TARGET-PATH'." |
||||
(copy-directory:copy (make-raw-path source-path) (make-raw-path target-path))) |
||||
|
||||
(defun directory-exists-p (username directory) |
||||
"Checks to see if the specified diretory exists. |
||||
The directories path is returned if it does exist and `NIL' is |
||||
returned if the directory cannot be found." |
||||
(cl:probe-file (make-path username directory ""))) |
||||
|
||||
(defun ensure-raw-directory-exists (directory-path) |
||||
"Creates directory if it doesn't exist (use for working outside of /storage). |
||||
The directories path is returned if it does exist and `NIL' is returned if the |
||||
directory cannot be found." |
||||
(ensure-directories-exist (make-raw-path directory-path))) |
||||
|
||||
(defun ensure-directory-exists (username directory) |
||||
"The project's standardised way to call `ENSURE-DIRECTORY-EXISTS'. |
||||
If the directory exists, the full (absolute) path is |
||||
returned (equating to `T', otherwiser `NIL' it returned." |
||||
;; The empty string for `SLUG' (3rd arg.) is used because |
||||
;; `MAKE-PATH' can form paths for files. In this instance, only the |
||||
;; directory needs to be formed. The empty string kinda acts like |
||||
;; `NIL' but it is a bit of a hack, I will admit. |
||||
(ensure-directories-exist (make-path username directory ""))) |
||||
|
||||
(defun file-exists-p (username subdirectory slug) |
||||
"This project's standardised way to call `CL:PROBE-FILE'. |
||||
If the file exists, the full (absolute) path is returned (equates to |
||||
`T', otherwise `NIL' is returned." |
||||
(cl:probe-file (make-path username subdirectory slug))) |
||||
|
||||
(defun get-files-in-directory (username directory) |
||||
"Returns a list of paths for the files in `DIRECTORY' in the /storage directory. |
||||
`USERNAME' is the subdirectory in /storage. If you are not implementing or have |
||||
a need for creating sub-directories in /storage, pass in an empty string \"\" . |
||||
Full directory structure: /storage/`USERNAME'/`DIRECTORY' |
||||
|
||||
When empty string used for 'username': /storage/`DIRECTORY'" |
||||
(uiop:directory-files (make-path username directory ""))) |
||||
|
||||
(defun get-file-names (filenames) |
||||
"Returns a list of file names from a list of paths in `FILENAMES'. |
||||
Make sure you call `STORAGE:GET-FILES-IN-DIRECTORY' and pass as `FILENAMES' when |
||||
calling this function." |
||||
(mapcar #'(lambda (x) (file-namestring x)) filenames)) |
||||
|
||||
(defun get-raw-directories (directory-path) |
||||
"Returns a list of paths for the files in `DIRECTORY-PATH' outside /storage directory." |
||||
(cl-fad:list-directory (make-raw-path directory-path))) |
||||
|
||||
(defun raw-directory-exists? (directory-path) |
||||
"Checks to see if directory at `DIRECTORY-PATH' (no directory make if none found)." |
||||
(cl-fad:directory-exists-p (make-raw-path directory-path))) |
||||
|
||||
(defun get-directory-names (directory-names) |
||||
"Returns the final part of a directories absolute path in `DIRECTORY-NAMES'. |
||||
Make sure you use `GET-RAW-DIRECTORIES' to build the `DIRECTORY-NAMES' list." |
||||
(mapcar #'(lambda (x) (first (last (pathname-directory x)))) directory-names)) |
||||
|
||||
(defun make-path (username subdirectory slug) |
||||
"Forms the path used to save a file. |
||||
Storage path: |
||||
`*APPLICATION-ROOT*'/storage/`USERNAME'/`SUBDIRECTORY'/`SLUG' |
||||
Each user has their own directory in /storage. This is so I can build a media |
||||
manager at a later date -- I had not got around to writing it at the time I |
||||
implemented this function/feature. I decided to go with `USERNAME' and |
||||
not (user-)`ID' is because I wanted to easily identify the directories in |
||||
/storage." |
||||
(merge-pathnames (format nil "storage/~A/~A/~A" |
||||
username subdirectory slug) |
||||
ritherdon-archive.config:*application-root*)) |
||||
|
||||
(defun make-raw-path (path) |
||||
"Make a file/directory path outsite of the /storage directory." |
||||
(merge-pathnames path ritherdon-archive.config:*application-root*)) |
||||
|
||||
(defun open-binary-file (username subdirectory slug) |
||||
"Reads the file stored in the /storage directory." |
||||
(with-open-file (stream |
||||
(make-path username subdirectory slug) |
||||
:element-type '(unsigned-byte 8)) |
||||
(let* ((length (file-length stream)) |
||||
(buffer (make-array length |
||||
:element-type '(unsigned-byte 8)))) |
||||
(read-sequence buffer stream) |
||||
(values buffer length)))) |
||||
|
||||
(defun open-text-file (username subdirectory slug) |
||||
"Reads the text (.md) file stored in the /storage directory." |
||||
(with-open-file (stream (make-path username subdirectory slug)) |
||||
(let ((data (make-string (file-length stream)))) |
||||
(read-sequence data stream) |
||||
data))) |
||||
|
||||
(defun remove-raw-directory (directory-path) |
||||
"Removes directory at `DIRECTORY-PATH' when outside /storage directory." |
||||
(cl-fad:delete-directory-and-files (make-raw-path directory-path))) |
||||
|
||||
(defun remove-directory (username subdirectory) |
||||
"Deletes an directory in /storage. |
||||
Path template: `*APPLICATION-ROOT*'/storage/`USERNAME'/`SUBDIRECTORY'/' |
||||
- https://edicl.github.io/cl-fad/#delete-directory-and-files |
||||
- https://stackoverflow.com/questions/24350183/how-do-i-delete-a-directory-in-common-lisp |
||||
'cl-fad' (files and directories) is a wrapper package over the various |
||||
Common Lisp implementions to aid in keeping your Common Lisp code |
||||
portable. At the time of writing (February 2022), the website is using |
||||
Steel Bank Common Lisp (SBCL) but this allow you to use something else |
||||
if you want or need to switch." |
||||
(cl-fad:delete-directory-and-files (make-path username subdirectory ""))) |
||||
|
||||
(defun rename-directory (username original-directory new-directory) |
||||
"Renames a sub-directory in the /storage directory. |
||||
`USERNAME' is the directory holding the one which is to be |
||||
changed. `ORIGINAL-DIRECTORY' is the 'source' (in the usual Linux/Bash |
||||
CLI sense). `NEW-DIRECTORY' is the name the `ORIGINAL-DIRECTORY' will |
||||
be changed to. There are various examples of the path |
||||
structure/template in other comments in this file. Have a look |
||||
around (don't want to repeat myself)." |
||||
(rename-file (make-path username original-directory "") |
||||
(make-path username new-directory ""))) |
||||
|
||||
(defun remove-file (username subdirectory slug) |
||||
"Deletes the specified file, stored in the /storage directory. |
||||
Before calling this function, make sure the file exists. You should have |
||||
'file-exists-p' available to you -- within this (storage) package." |
||||
(delete-file (make-path username subdirectory slug))) |
||||
|
||||
(defun rename-content-file (username subdirectory old-slug new-slug) |
||||
"This project's standardised way to call `RENAME-FILE'." |
||||
(rename-file (make-path username subdirectory old-slug) |
||||
(make-path username subdirectory new-slug))) |
||||
|
||||
(defun store-file-old (username subdirectory filename data) |
||||
"OBSOLETE. USE `STORE-FILE'." |
||||
(let ((path (ensure-directories-exist |
||||
(make-path username subdirectory filename)))) |
||||
(cond ((or (string= (caddr data) "application/gzip") |
||||
(string= (caddr data) "application/zip") |
||||
(string= (caddr data) "application/epub+zip")) |
||||
(uiop:copy-file (slot-value (car data) 'pathname) path)) |
||||
(t (with-open-file (stream |
||||
path |
||||
:direction :output |
||||
:if-does-not-exist :create |
||||
:element-type '(unsigned-byte 8) |
||||
:if-exists :supersede) |
||||
(write-sequence (slot-value (car data) 'vector) stream)))))) |
||||
|
||||
(defun store-file (username subdirectory filename data) |
||||
"Stores the uploaded file to the /storage directory. |
||||
Storage path: `*APPLICATION-ROOT*'/storage/`USERNAME'/`SUBDIRECTORY'/`FILENAME' |
||||
`DATA' is the actual contents which will be written to the said path." |
||||
(let ((path (ensure-directories-exist |
||||
(make-path username subdirectory filename)))) |
||||
(cond ((equal (type-of (car data)) 'SB-SYS:FD-STREAM) |
||||
(uiop:copy-file (slot-value (car data) 'pathname) path)) |
||||
(t |
||||
(with-open-file (stream |
||||
path |
||||
:direction :output |
||||
:if-does-not-exist :create |
||||
:element-type '(unsigned-byte 8) |
||||
:if-exists :supersede) |
||||
(write-sequence (slot-value (car data) 'vector) stream)))))) |
||||
|
||||
(defun store-text (username subdirectory filename data) |
||||
"Stores the plain text to the /storage directory. |
||||
Storage path: `*APPLICATION-ROOT*'/storage/`USERNAME'/`SUBDIRECTORY'/`FILENAME' |
||||
`DATA' is the actual text/data which will be written to the said path." |
||||
(let ((path (ensure-directories-exist |
||||
(make-path username subdirectory filename)))) |
||||
(with-open-file (stream |
||||
path |
||||
:direction :output |
||||
:if-does-not-exist :create |
||||
:if-exists :supersede) |
||||
(format stream "~a~%" data)))) |
||||
|
||||
(defun store-with-raw-path (path data) |
||||
"Stores `DATA' at `PATH'. Use when storing data outsite of /storage directory." |
||||
(let ((path (ensure-directories-exist |
||||
(merge-pathnames path |
||||
ritherdon-archive.config::*application-root*)))) |
||||
(cond ((equal (type-of (car data)) 'SB-SYS:FD-STREAM) |
||||
(uiop:copy-file (slot-value (car data) 'pathname) path)) |
||||
(t |
||||
(with-open-file (stream |
||||
path |
||||
:direction :output |
||||
:element-type '(unsigned-byte 8) |
||||
:if-does-not-exist :create |
||||
:if-exists :supersede) |
||||
(write-sequence (slot-value (car data) 'vector) stream)))))) |
||||
|
||||
(defun store-text-with-raw-path (path data) |
||||
"Stores the plain text `DATA' at `PATH'. |
||||
Use when storing text outside of /storage direcory." |
||||
(let ((path (ensure-directories-exist |
||||
(merge-pathnames path |
||||
ritherdon-archive.config::*application-root*)))) |
||||
(with-open-file (stream |
||||
path |
||||
:direction :output |
||||
:if-does-not-exist :create |
||||
:if-exists :supersede) |
||||
(format stream "~a~%" data)))) |
||||
|
||||
(defun open-text-file-with-raw-path (file-path) |
||||
"Reads the text file stored in the at `PATH'. |
||||
Use when reading text outside the /storage directory." |
||||
(let ((path (ensure-directories-exist |
||||
(merge-pathnames file-path |
||||
ritherdon-archive.config::*application-root*)))) |
||||
(with-open-file (stream path) |
||||
(let ((data (make-string (file-length stream)))) |
||||
(read-sequence data stream) |
||||
data)))) |
||||
|
||||
|
||||
(defun remove-file-with-raw-path (file-path) |
||||
"Deletes the specified file, at `PATH', use this to delete file outsite of /storage. |
||||
Before calling this function, make sure the file exists. You should have |
||||
'file-exists-p' available to you -- within this (storage) package." |
||||
(delete-file (merge-pathnames file-path ritherdon-archive.config::*application-root*))) |
||||
|
||||
;;; PORTED FROM RAILS-TO-CAVEMAN PROJECT (expect it to be deleted) |
||||
;;; ============================================================================= |
||||
|
||||
(defun prin1-to-base64-string (object) |
||||
(cl-base64:string-to-base64-string (prin1-to-string object))) |
||||
|
||||
(defun read-from-base64-string(string) |
||||
(values (read-from-string |
||||
(cl-base64:base64-string-to-string string)))) |
||||
|
||||
;;; This function requires ImageMagick so you will need to install it |
||||
;;; with 'sudo apt install imagemagick' (assuming you are on a |
||||
;;; Debian-based system). |
||||
;; (defun convert (id subdirectory original-file converted-file) |
||||
;; (let ((command (format nil "convert -geometry ~A ~A ~A" |
||||
;; (file-size converted-file) |
||||
;; (make-storage-pathname id subdirectory original-file) |
||||
;; (make-storage-pathname id subdirectory converted-file)))) |
||||
;; (let ((message (nth-value 1 |
||||
;; (uiop:run-program command |
||||
;; :ignore-error-status t |
||||
;; :error-output :string)))) |
||||
;; (when message (error message))))) |
@ -0,0 +1,200 @@
|
||||
(in-package #:cl-user) |
||||
(defpackage #:utils |
||||
(:use #:cl |
||||
#:caveman2 |
||||
#:log4cl |
||||
#:xml-emitter |
||||
#:app-constants |
||||
#:storage) |
||||
(:export #:request-params |
||||
#:separate-files-in-web-request |
||||
#:set-alert |
||||
#:get-alert |
||||
#:get-and-reset-alert |
||||
#:checkbox-to-bool |
||||
#:asciify |
||||
#:slugify |
||||
#:get-image-dimensions |
||||
#:run-bash-command |
||||
#:create-thumbnail |
||||
#:create-timestamp-id |
||||
#:format-filename |
||||
#:format-keywords |
||||
#:build-alert-string |
||||
#:month-number-to-name |
||||
#:build-url-root |
||||
#:build-url |
||||
#:create-timestamp-text) |
||||
(:documentation "Utilities that do not depend on models.")) |
||||
|
||||
(in-package #:utils) |
||||
|
||||
(defun asciify (string) |
||||
(str:downcase (slug:asciify string))) |
||||
|
||||
(defun slugify (string) |
||||
"Turns a string of text into a slug." |
||||
(str:downcase (slug:slugify string))) |
||||
|
||||
(defun format-filename (string) |
||||
"Changes the filename into the system's standard. |
||||
Replaces whitespace with '-' and changes everything to lowecase." |
||||
(str:replace-all " " "-" (asciify string))) |
||||
|
||||
(defun format-keywords (string) |
||||
"Formats the keywords used in the `ARCHIVE-ENTRY' class. |
||||
This is mostly just over-prep'ing the keywords assuming the user |
||||
enters them in the incorrect format. Meilisearch exects something like |
||||
'art,welding,green paint,ritherdon'. The comma is the seperator which |
||||
allows each 'keyword' to have (white-)spaces." |
||||
(str:replace-all ", " "," (asciify string))) |
||||
|
||||
(defun request-params (request) |
||||
(loop :for (key . value) :in request |
||||
:collect (let ((*package* (find-package :keyword))) |
||||
(read-from-string key)) |
||||
:collect value)) |
||||
|
||||
(defun separate-files-in-web-request (request &optional request-value) |
||||
"Creates a new list of 'upload' files from a web `REQUEST'. |
||||
You will mostly use this for processing a multi-file upload (HTML) |
||||
form. The standard value for the 'name' attribute in (file) input tag |
||||
in the HTML form is `CONTENT-FILES' but you can use a different |
||||
name. Just specify it in this function's `REQUEST-VALUE' argument." |
||||
(loop :for item :in request |
||||
if (or (string= "CONTENT-FILES" (car item)) |
||||
(string= request-value (car item))) |
||||
collect item)) |
||||
|
||||
(defun set-alert (message &optional alert-type) |
||||
"Sets the alert `MESSAGE' stored in session, provide info. to users. |
||||
The intention is store a `MESSAGE' across a redirect during a HTTP |
||||
POST request." |
||||
(cond ((string= "error" alert-type) |
||||
(setf (gethash :alert ningle:*session*) |
||||
(build-alert-string alert-type "vomit-cat.png" message))) |
||||
((string= "success" alert-type) |
||||
(setf (gethash :alert ningle:*session*) |
||||
(build-alert-string alert-type "success-cat.png" message))) |
||||
((string= "missing-data" alert-type) |
||||
(setf (gethash :alert ningle:*session*) |
||||
(build-alert-string alert-type "sherlock-cat.png" message))) |
||||
((string= "invalid-data" alert-type) |
||||
(setf (gethash :alert ningle:*session*) |
||||
(build-alert-string alert-type "confused-cat.png" message))) |
||||
((string= "created" alert-type) |
||||
(setf (gethash :alert ningle:*session*) |
||||
(build-alert-string alert-type "disco-cat.png" message))) |
||||
((string= "warning" alert-type) |
||||
(setf (gethash :alert ningle:*session*) |
||||
(build-alert-string alert-type "workout-cat.png" message))) |
||||
(t (setf (gethash :alert ningle:*session*) message)))) |
||||
|
||||
(defun build-alert-string (alert-text src-image message) |
||||
(format nil |
||||
"<p class=\"~a\"><img alt=\"~a\" src=\"/images/alerts/~a\">~a</p>" |
||||
alert-text |
||||
alert-text |
||||
src-image |
||||
message)) |
||||
|
||||
(defun get-alert () |
||||
"Get alert message from session data." |
||||
(gethash :alert ningle:*session*)) |
||||
|
||||
(defun get-and-reset-alert () |
||||
"Returns the `ALERT' message and clears its content from the session hash." |
||||
(let ((message (get-alert))) |
||||
(set-alert nil) |
||||
message)) |
||||
|
||||
(defun checkbox-to-bool (value) |
||||
"Converts a HTML Checkbox `VALUE' to a Boolean. |
||||
The `VALUE' will either be 'on' or 'off'. 'Boolean' in this instance |
||||
is assuming you are using SQLite and need to convert `VALUE' to an |
||||
integer/number. If you are needing a traditional Boolean value, DO NOT USE |
||||
THIS FUNCTION." |
||||
(cond ((or (string= "checked" value) (string= "on" value)) +true+) |
||||
((or (string= "off" value) (null value)) +false+) |
||||
((null value) +false+))) |
||||
|
||||
(defun get-image-dimensions (filepath) |
||||
"Uses Image Magick (via Bash) to get the resolution of an image as 'WxH'. |
||||
The `FILEPATH' must be already merged with |
||||
`ritherdon-archive.config::*application-root*' before you call this function." |
||||
(let* ((command |
||||
(format nil "identify -format \"%wx%h\" ~a" filepath)) |
||||
(out-message (uiop:run-program command :output :string |
||||
:ignore-error-status t |
||||
:error-output :string))) |
||||
out-message)) |
||||
|
||||
(defun run-bash-command (command) |
||||
"Runs the Bash command." |
||||
(uiop:run-program command :output :string |
||||
:ignore-error-status t |
||||
:error-output :string)) |
||||
|
||||
(defun create-thumbnail (storage-sub-directory file-name &optional (overwrite t)) |
||||
"Runs a Bash command to convert a file to a thumbnail in /storage/media dir. |
||||
The file is reduced to 512x512 pixels if bigger than that. A new file |
||||
is then created with a 'thumbnail-' pre-fix. This process relies on |
||||
Image Magick. So, it must be installed on the system for this function |
||||
to operate properly." |
||||
(run-bash-command |
||||
(format nil "convert ~a -resize 512x512\\> ~a" |
||||
(storage:file-exists-p "" storage-sub-directory file-name) |
||||
(if (eq overwrite t) |
||||
(storage:file-exists-p "" storage-sub-directory file-name) |
||||
(storage:make-path "" storage-sub-directory |
||||
(format nil "thumbnail-~a" file-name)))))) |
||||
|
||||
(defun create-timestamp-id () |
||||
"Creates a integer based on time the function is called, in YYYYMMDD format." |
||||
(multiple-value-bind |
||||
(second minute hour day month year) |
||||
(get-decoded-time) |
||||
(format nil "~d~2,'0d~d~2,'0d~2,'0d~2,'0d" year month day hour minute second))) |
||||
|
||||
(defun create-timestamp-text () |
||||
"Creates a text-based timestamp (value being the time function was called)." |
||||
(multiple-value-bind |
||||
(second minute hour day month year) |
||||
(get-decoded-time) |
||||
(format nil "~d-~2,'0d-~d_~2,'0d-~2,'0d-~2,'0d" year month day hour minute second))) |
||||
|
||||
(defun month-number-to-name (month-number) |
||||
"Converts `MONTHS-NUMBER' to its name (E.G. 1 to 'January')." |
||||
(cond ((= 1 month-number) "January") |
||||
((= 2 month-number) "February") |
||||
((= 3 month-number) "March") |
||||
((= 4 month-number) "April") |
||||
((= 5 month-number) "May") |
||||
((= 6 month-number) "June") |
||||
((= 7 month-number) "July") |
||||
((= 8 month-number) "August") |
||||
((= 9 month-number) "September") |
||||
((= 10 month-number) "October") |
||||
((= 11 month-number) "November") |
||||
((= 12 month-number) "December") |
||||
(t nil))) |
||||
|
||||
(defun build-url (request) |
||||
"Concatenates parts of the web request to form the full URL the user requested." |
||||
(format nil "~a://~a~a~a" |
||||
(lack.request:request-uri-scheme request) |
||||
(lack.request:request-server-name request) |
||||
(if (string= "localhost" (lack.request:request-server-name request)) |
||||
(format nil ":~a" (lack.request:request-server-port request)) |
||||
"") |
||||
(lack.request:request-uri request))) |
||||
|
||||
(defun build-url-root (request) |
||||
"Concatenates parts of the web request to form site's root URL. |
||||
This is mostly used for generating the site map (XML file for crawlers)." |
||||
(format nil "~a://~a~a" |
||||
(lack.request:request-uri-scheme request) |
||||
(lack.request:request-server-name request) |
||||
(if (string= "localhost" (lack.request:request-server-name request)) |
||||
(format nil ":~a" (lack.request:request-server-port request)) |
||||
""))) |
@ -0,0 +1,57 @@
|
||||
(in-package #:cl-user) |
||||
(defpackage #:validation |
||||
(:use #:cl |
||||
#:app-constants) |
||||
(:export #:has-static-assets-extention? |
||||
#:is-valid-favicon-type? |
||||
#:favicon-need-resizing? |
||||
#:string-is-nil-or-empty?) |
||||
(:documentation "Package for validating 'stuff'.")) |
||||
(in-package #:validation) |
||||
|
||||
(defun file-has-valid-static-assets-extention? (filename) |
||||
(cond ((or (string= "css" (pathname-type filename)) |
||||
(string= "js" (pathname-type filename)) |
||||
(string= "png" (pathname-type filename)) |
||||
(string= "ico" (pathname-type filename)) |
||||
(string= "gif" (pathname-type filename)) |
||||
(string= "jpg" (pathname-type filename)) |
||||
(string= "svg" (pathname-type filename)) |
||||
(string= "txt" (pathname-type filename))) |
||||
filename) |
||||
(t nil))) |
||||
|
||||
(defun file-is-valid-favicon-type? (filename) |
||||
"Returns path of file if `FILE' is a valid favicon. |
||||
Valid file types are 'ico', 'gif' and 'png'." |
||||
(cond ((or (string= "png" (pathname-type filename)) |
||||
(string= "ico" (pathname-type filename)) |
||||
(string= "gif" (pathname-type filename))) |
||||
filename) |
||||
(t nil))) |
||||
|
||||
(defun string-is-nil-or-empty? (string-to-test) |
||||
"Tests to see if `STRING-TO-TEST' is empty of just whitespace. |
||||
This is essentially the 'IsNullOrWhiteSpace' function I use in C#. It |
||||
expands the 'empty string' check to include a check to see if there is |
||||
string with just a '(white) space' in it." |
||||
(if (or (string= string-to-test " ") |
||||
(zerop (length string-to-test)) |
||||
(null string-to-test)) |
||||
t |
||||
nil)) |
||||
|
||||
(defun favicon-need-resizing? (dimensions) |
||||
"Checks to see if the `WIDTH' or `HEIGHT' are greater than 192x192 pixels. |
||||
`DIMENSIONS' is a cons list, with the `WIDTH' being first element and |
||||
`HEIGHT' being the second. If either of them are, then you look into |
||||
scaling down the image, using code in the `UTILS' package probably." |
||||
(cond ((or (< 192 (car dimensions)) (< 192 (cdr dimensions))) |
||||
t) ; True as in the dimensions are valid |
||||
(t ;; Bit confusing with the use of `T' here but its standard |
||||
;; practice to end `COND' with a catch-all `T'. In this |
||||
;; case, `T' here means the dimensions are invalid, as in |
||||
;; 'yes' the favicon needs resizing. This is confusing |
||||
;; Boolean logic but when you call this function, the |
||||
;; return types make sense from that point-of-view. |
||||
nil))) |
@ -0,0 +1,294 @@
|
||||
.search-dashboard { |
||||
margin: 0px; |
||||
padding: 0px; |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.refinements-panel h2, |
||||
.refinements-panel p { |
||||
padding: 0px; |
||||
margin: 12px 0px; |
||||
} |
||||
|
||||
#clear-refinements { |
||||
margin-bottom: 20px; |
||||
} |
||||
|
||||
.ais-ClearRefinements-button { |
||||
font-size: 16px; |
||||
height: 40px; |
||||
width: 100%; |
||||
margin: 0px; |
||||
font-family: 'main', sans-serif; |
||||
color: black; |
||||
background-color: white; |
||||
cursor: pointer; |
||||
border: none; |
||||
text-align: left; |
||||
} |
||||
|
||||
.ais-ClearRefinements-button:hover { |
||||
text-decoration: underline; |
||||
} |
||||
|
||||
.ais-ClearRefinements-button--disabled { |
||||
color: slategrey; |
||||
} |
||||
|
||||
.ais-ClearRefinements-button--disabled:hover { |
||||
color: slategrey; |
||||
text-decoration: none; |
||||
cursor: initial; |
||||
} |
||||
|
||||
.search-refinement-list { |
||||
/* margin: 20px 0px; */ |
||||
/* border-bottom: 2px solid black; */ |
||||
} |
||||
|
||||
#searchbox { |
||||
width: 100%; |
||||
margin-top: 20px; |
||||
margin-bottom: 20px; |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
} |
||||
|
||||
.ais-SearchBox, |
||||
.ais-ClearRefinements-button { |
||||
max-width: 600px; |
||||
} |
||||
|
||||
.search-results-container { |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.ais-SearchBox-form { |
||||
display: flex; |
||||
flex-wrap: nowrap; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
|
||||
.ais-SearchBox-input { |
||||
box-sizing: border-box; |
||||
width: 100%; |
||||
margin-bottom: 15px; |
||||
padding: 10px; |
||||
border-radius: 8px; |
||||
border: 1px solid black; |
||||
} |
||||
|
||||
.ais-SearchBox-input::placeholder { |
||||
/* font-family: 'main', sans-serif; */ |
||||
/* font-size: 16px; */ |
||||
} |
||||
|
||||
.ais-SearchBox-form input:focus-visible { |
||||
/* outline: none; */ |
||||
} |
||||
|
||||
.ais-SearchBox-submitIcon, |
||||
.ais-SearchBox-submit, |
||||
.ais-SearchBox-reset { |
||||
display: none; |
||||
} |
||||
|
||||
.ais-RefinementList-list { |
||||
list-style: none; |
||||
padding: 0px; |
||||
margin: 0px 0px 40px 0px; |
||||
display: flex; |
||||
flex-direction: row; |
||||
width: 100%; |
||||
overflow: scroll; |
||||
} |
||||
|
||||
.ais-RefinementList-label { |
||||
display: flex; |
||||
align-items: center ; |
||||
width: max-content; |
||||
margin-right: 6px; |
||||
} |
||||
|
||||
.ais-RefinementList-label > span { |
||||
padding: 0px 2px; |
||||
} |
||||
|
||||
.ais-RefinementList-checkbox { |
||||
width: 30px; |
||||
height: 30px; |
||||
margin: 0px; |
||||
} |
||||
|
||||
.search-hits-panel { |
||||
width: -webkit-fill-available; |
||||
} |
||||
|
||||
#hits { |
||||
width: 100%; |
||||
min-width: 350px; |
||||
display: flex; |
||||
flex-direction: row; |
||||
} |
||||
|
||||
.ais-Hits-list { |
||||
padding: 0px; |
||||
margin: 0px; |
||||
display: flex; |
||||
flex-direction: column; |
||||
list-style: none; |
||||
} |
||||
|
||||
.ais-Hits-item { |
||||
margin: 8px; |
||||
max-height: 400px; |
||||
border-radius: 4px; |
||||
overflow: hidden; |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.ais-Hits-item:hover { |
||||
/* border-color: lightblue; */ |
||||
/* box-shadow: none; */ |
||||
} |
||||
|
||||
.fe-search-hit { |
||||
max-width: 375px; |
||||
} |
||||
|
||||
.fe-search-hit-title { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.fe-search-hit-secondary { |
||||
display: block; |
||||
} |
||||
|
||||
.fe-search-hit-keywords { |
||||
display: block; |
||||
color: slategrey; |
||||
font-size: 11px; |
||||
} |
||||
|
||||
.fe-search-hit img { |
||||
width: 375px; |
||||
height: 200px; |
||||
object-fit: cover; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
#pagination { |
||||
display: block; |
||||
margin: 40px 0px 40px 0px; |
||||
overflow: auto; |
||||
width: 100%; |
||||
} |
||||
|
||||
.ais-Pagination-list { |
||||
list-style: none; |
||||
padding: 0px; |
||||
margin: 0px 12px; |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
|
||||
.ais-Pagination-item { |
||||
font-size: 22px; |
||||
margin: 6px; |
||||
} |
||||
|
||||
.ais-Pagination-item--disabled { |
||||
color: slategrey; |
||||
} |
||||
|
||||
/* @media (min-width:600px) { |
||||
.ais-ClearRefinements-button { |
||||
max-width: 200px; |
||||
} |
||||
} */ |
||||
|
||||
/* @media (min-width:961px) { */ |
||||
@media (min-width: 600px) { |
||||
.search-results-container { |
||||
flex-direction: row; |
||||
} |
||||
|
||||
.refinements-panel { |
||||
max-width: 200px; |
||||
width: 100%; |
||||
margin-right: 20px; |
||||
} |
||||
|
||||
.ais-RefinementList-list { |
||||
font-size: 12px; |
||||
flex-direction: column; |
||||
overflow: auto; |
||||
} |
||||
|
||||
.ais-RefinementList-item { |
||||
margin: 2px 0px; |
||||
} |
||||
|
||||
.ais-RefinementList-checkbox { |
||||
width: 20px; |
||||
margin-right: 4px; |
||||
} |
||||
|
||||
#hits { |
||||
flex-direction: row; |
||||
flex-wrap: wrap; |
||||
} |
||||
|
||||
.ais-Hits-list { |
||||
padding: 0px; |
||||
margin: 0px; |
||||
display: flex; |
||||
flex-direction: row; |
||||
flex-wrap: wrap; |
||||
list-style: none; |
||||
} |
||||
|
||||
.ais-Hits-item { |
||||
/* width: 200px; */ |
||||
/* height: 280px; */ |
||||
/* margin: 12px; */ |
||||
/* height: 100%; */ |
||||
/* display: flex; */ |
||||
/* flex-direction: column; */ |
||||
} |
||||
|
||||
.fe-search-hit { |
||||
max-width: 200px; |
||||
} |
||||
|
||||
.fe-search-hit-title { |
||||
font-weight: bold; |
||||
display: -webkit-box; |
||||
-webkit-box-orient: vertical; |
||||
-webkit-line-clamp: 2; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.fe-search-hit-secondary { |
||||
display: block; |
||||
} |
||||
|
||||
.fe-search-hit-keywords { |
||||
display: block; |
||||
color: slategrey; |
||||
font-size: 11px; |
||||
} |
||||
|
||||
.fe-search-hit img { |
||||
width: 200px; |
||||
height: 100px; |
||||
object-fit: cover; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
} |
@ -1,21 +1,859 @@
|
||||
@charset "UTF-8"; |
||||
|
||||
@font-face { |
||||
font-family: "main"; |
||||
src: url("archivo/Archivo-Regular.otf") format("opentype"); |
||||
} |
||||
|
||||
/* smartphones, iPhone, portrait 480x320 phones */ |
||||
html, body { |
||||
/* height: 100%; */ |
||||
} |
||||
|
||||
body { |
||||
font-family: 'Myriad Pro', Calibri, Helvetica, Arial, sans-serif; |
||||
font-family: 'main', Calibri, Helvetica, Arial, sans-serif; |
||||
margin: 0px; |
||||
padding: 0px; |
||||
} |
||||
|
||||
button { |
||||
font-family: 'main', Calibri, Helvetica, Arial, sans-serif; |
||||
} |
||||
|
||||
a:link { |
||||
color: #005585; |
||||
text-decoration: none; |
||||
color: black; |
||||
text-decoration: none; |
||||
} |
||||
|
||||
a:visited { |
||||
color: #485270; |
||||
color: black; |
||||
} |
||||
|
||||
a:hover { |
||||
color: #b83800; |
||||
text-decoration: underline; |
||||
text-decoration: underline; |
||||
} |
||||
|
||||
input[type=file], |
||||
input[type=text], |
||||
input[type=password], |
||||
input[type=number] { |
||||
border: 1px solid black; |
||||
border-radius: 8px; |
||||
margin: 0px; |
||||
padding: 4px; |
||||
display: inline-block; |
||||
} |
||||
|
||||
.be-gui-form-row select { |
||||
width: 100%; |
||||
height: 38px; |
||||
border-radius: 8px; |
||||
} |
||||
|
||||
#main { |
||||
text-align: center; |
||||
} |
||||
|
||||
.fr-main { |
||||
max-width: 880px; |
||||
margin: auto; |
||||
padding: 20px; |
||||
} |
||||
|
||||
.fr-main h2 { |
||||
padding: 0px; |
||||
margin: 20px 0px 0px 0px; |
||||
} |
||||
|
||||
.fr-main .be-gui-form-hint { |
||||
margin-bottom: 20px; |
||||
} |
||||
|
||||
.fr-main .fr-gui-form-hint { |
||||
margin-bottom: 0px; |
||||
font-style: italic; |
||||
font-size: 12px; |
||||
padding: 0px; |
||||
margin: 0px; |
||||
} |
||||
|
||||
.fr-main img { |
||||
max-width: 200px; |
||||
width: 100%; |
||||
} |
||||
|
||||
.be-alert-container { |
||||
display: flex; |
||||
align-items: center; |
||||
position: fixed; |
||||
top: 60px; |
||||
left: 8px; |
||||
right: 8px; |
||||
} |
||||
|
||||
.be-alert-container button { |
||||
position: fixed; |
||||
right: 15px; |
||||
} |
||||
|
||||
.be-alert-container p { |
||||
padding: 0px 60px 0px 0px; |
||||
margin: 0px; |
||||
border-radius: 8px; |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
width: 100%; |
||||
} |
||||
|
||||
.be-alert-container .success { |
||||
background: #adefad; |
||||
} |
||||
|
||||
.be-alert-container .error { |
||||
background: #ff607c; |
||||
} |
||||
|
||||
.be-alert-container .missing-data{ |
||||
background: #ffaf8e; |
||||
} |
||||
|
||||
.be-alert-container .invalid-data { |
||||
background: #efe1ad; |
||||
} |
||||
|
||||
.be-alert-container .created { |
||||
background: #b0f3ff; |
||||
} |
||||
|
||||
.be-alert-container .warning { |
||||
background: #ffe0c0; |
||||
} |
||||
|
||||
.be-alert-container p img { |
||||
max-height: 75px; |
||||
padding: 12px 8px; |
||||
} |
||||
|
||||
.be-login-container { |
||||
margin: 60px 0px; |
||||
} |
||||
|
||||
.be-user-accounts-container { |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: flex-start; |
||||
/* margin-bottom: 20px; */ |
||||
} |
||||
|
||||
.be-user-accounts-container p, |
||||
.be-user-accounts-container .be-gui-button-no-text { |
||||
display: inline; |
||||
padding: 4px; |
||||
} |
||||
|
||||
.be-user-accounts-container p, |
||||
.be-user-accounts-container label { |
||||
margin: 0px 4px; |
||||
} |
||||
|
||||
.be-user-accounts-container p { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.be-user-accounts-container input[type=password] { |
||||
height: 34px; |
||||
width: 100%; |
||||
} |
||||
|
||||
.be-section-thumbnail-row { |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
justify-content: flex-start; |
||||
margin: 0px; |
||||
padding: 0px; |
||||
width: 100%; |
||||
} |
||||
|
||||
.wrap-break-spaces { |
||||
white-space: break-spaces !important; |
||||
} |
||||
|
||||
.be-gui-link:link, |
||||
.be-gui-button:link, |
||||
.be-gui-link-no-text, |
||||
.be-gui-button-no-text { |
||||
background: #905da1; |
||||
color: white; |
||||
} |
||||
|
||||
.be-gui-link-no-text { |
||||
width: 40px; |
||||
height: 40px; |
||||
} |
||||
|
||||
.be-gui-button-no-text { |
||||
width: 40px; |
||||
height: 40px; |
||||
} |
||||
|
||||
.be-gui-link-no-text img, |
||||
.be-gui-button-no-text img { |
||||
width: 32px; |
||||
} |
||||
|
||||
.be-gui-link-no-text, |
||||
.be-gui-button-no-text { |
||||
padding: 2px 4px; |
||||
border-radius: 8px; |
||||
border: none; |
||||
color: white; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
margin: 4px; |
||||
} |
||||
|
||||
.be-gui-link, |
||||
.be-gui-button { |
||||
padding: 0px; |
||||
width: 100%; |
||||
height: 36px; |
||||
padding: 8px 0px; |
||||
border: none; |
||||
max-width: 600px; |
||||
background: #905da1; |
||||
color: white; |
||||
display: flex; |
||||
justify-content: flex-start; |
||||
align-items: center; |
||||
border-radius: 6px; |
||||
max-width: initial; |
||||
text-decoration: none; |
||||
} |
||||
|
||||
.be-gui-button { |
||||
height: 42px; |
||||
font-size: 16px; |
||||
} |
||||
|
||||
.be-gui-link:hover , |
||||
.be-gui-button:hover, |
||||
.be-gui-link-no-text:hover, |
||||
.be-gui-button-no-text:hover { |
||||
background: #473951; |
||||
text-decoration: none; |
||||
/* color: white; */ |
||||
} |
||||
|
||||
.be-gui-link:visited, |
||||
.be-gui-button:visited { |
||||
/* background: #905da1; */ |
||||
color: white; |
||||
} |
||||
|
||||
.be-gui-link img, |
||||
.be-gui-button img { |
||||
width: 34px; |
||||
padding: 5px; |
||||
margin-right: 12px; |
||||
} |
||||
|
||||
.be-section-entry .be-gui-button-no-text { |
||||
padding: 22px; |
||||
} |
||||
|
||||
.be-gui-button p { |
||||
padding: 0px 0px 0px 8px; |
||||
margin: 0px; |
||||
display: inline; |
||||
font-size: 18px; |
||||
font-family: 'main'; |
||||
} |
||||
|
||||
.be-gui-form { |
||||
display: flex; |
||||
flex-direction: column; |
||||
margin: 12px 0px; |
||||
} |
||||
|
||||
.be-gui-form label, |
||||
.be-gui-form input { |
||||
display: block; |
||||
/* width: 100%; */ |
||||
} |
||||
|
||||
.be-gui-form label { |
||||
text-transform: uppercase; |
||||
font-weight: bold; |
||||
margin-bottom: 4px; |
||||
} |
||||
|
||||
hr { |
||||
margin: initial; |
||||
} |
||||
|
||||
.be-gui-form input { |
||||
margin-bottom: 12px; |
||||
height: 24px; |
||||
} |
||||
|
||||
.be-gui-form-inline { |
||||
display: inline; |
||||
align-items: center; |
||||
} |
||||
|
||||
.be-gui-form-inline-flex{ |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
|
||||
.be-gui-form input[type=checkbox] { |
||||
display: inline; |
||||
} |
||||
|
||||
.be-gui-form input[type=file] { |
||||
padding: 12px; |
||||
} |
||||
|
||||
.be-gui-form textarea { |
||||
margin-bottom: 20px; |
||||
} |
||||
|
||||
.be-gui-form-hint { |
||||
font-style: italic; |
||||
font-size: 12px; |
||||
padding: 0px; |
||||
margin: 0px; |
||||
} |
||||
|
||||
.be-gui-form-row { |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
justify-content: flex-start; |
||||
margin: 0px; |
||||
padding: 0px; |
||||
} |
||||
|
||||
.be-gui-form-row input, |
||||
.be-gui-form-row label { |
||||
margin: 0px 5px 0px 0px; |
||||
padding: 0px; |
||||
} |
||||
|
||||
.be-gui-form-row input[type=number] { |
||||
padding: 6px; |
||||
} |
||||
|
||||
.be-gui-form-row select { |
||||
margin: 0px 6px 0px 0px; |
||||
} |
||||
|
||||
.be-gui-form-thumbnail { |
||||
max-height: 80px; |
||||
margin: 12px; |
||||
box-shadow: 1px 1px 7px 1px silver; |
||||
|
||||
} |
||||
|
||||
.be-gui-form-row input[type=text] { |
||||
height: 30px; |
||||
padding: 4px; |
||||
width: 100%; |
||||
} |
||||
|
||||
.be-site-header { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
background: #2a0134; |
||||
height: 44px; |
||||
padding: 0px; |
||||
position: sticky; |
||||
top: 0; |
||||
z-index: 3; |
||||
} |
||||
|
||||
.be-site-header button { |
||||
width: auto; |
||||
height: 32px; |
||||
background: #473951; |
||||
border: none; |
||||
color: white; |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
font-size: 18px; |
||||
margin: 0px 4px; |
||||
padding: 6px 12px; |
||||
border-radius: 6px; |
||||
} |
||||
|
||||
.be-site-header button img { |
||||
width: 22px; |
||||
margin-right: 6px; |
||||
} |
||||
|
||||
.be-user-info { |
||||
display: flex; |
||||
align-items: center; |
||||
padding-right: 12px; |
||||
} |
||||
|
||||
.be-user-info p { |
||||
display: inline; |
||||
color: white; |
||||
padding-left: 6px; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.be-user-info span { |
||||
font-weight: normal; |
||||
} |
||||
|
||||
.be-site-side-menu { |
||||
display: none; |
||||
flex-direction: column; |
||||
justify-content: space-between; |
||||
background: #473951; |
||||
position: fixed; |
||||
width: 300px; |
||||
/* Based on the height of be-site-header. */ |
||||
top: 0px; |
||||
padding-top: 86px; |
||||
left: 0; |
||||
bottom: 0px; |
||||
/* Stops the text editor rendering over it. */ |
||||
z-index: 2; |
||||
overflow-y: auto; |
||||
} |
||||
|
||||
.be-site-side-menu h2 { |
||||
text-align: center; |
||||
color: white; |
||||
font-size: 16px; |
||||
border-bottom: 1px solid white; |
||||
padding-bottom: 6px; |
||||
} |
||||
|
||||
.be-site-side-menu div { |
||||
display: flex; |
||||
flex-direction: column; |
||||
padding: 0px; |
||||
margin: 12px; |
||||
} |
||||
|
||||
.be-site-side-menu .be-gui-link, |
||||
.be-site-side-menu .be-gui-button { |
||||
padding: 8px; |
||||
margin: 0px; |
||||
width: auto; |
||||
background: #473951; |
||||
} |
||||
|
||||
.be-site-side-menu .be-gui-button { |
||||
padding: 28px 8px; |
||||
width: 276px; |
||||
font-size: 16px; |
||||
} |
||||
|
||||
.be-site-side-menu .be-gui-link:hover, |
||||
.be-site-side-menu .be-gui-button:hover { |
||||
background: #905da1;; |
||||
} |
||||
|
||||
.be-main { |
||||
max-width: 880px; |
||||
width: 100%; |
||||
display: flex; |
||||
justify-content: center; |
||||
flex-direction: column; |
||||
margin: 20px auto; |
||||
} |
||||
|
||||
.be-dashboard-header { |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: start; |
||||
justify-content: space-between; |
||||
padding: 0px; |
||||
margin: 0px; |
||||
} |
||||
|
||||
.be-dashboard-header .profile-cat { |
||||
height: 75px; |
||||
} |
||||
|
||||
.be-dashboard-header div { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: flex-start; |
||||
margin: 0px; |
||||
} |
||||
|
||||
.be-dashboard-header .be-gui-link, |
||||
.be-dashboard-section .be-gui-link { |
||||
background: #905da1; |
||||
} |
||||
|
||||
.be-dashboard-header .be-gui-link:hover, |
||||
.be-dashboard-section .be-gui-link:hover{ |
||||
background: #473951; |
||||
} |
||||
|
||||
.be-dashboard-section { |
||||
display: flex; |
||||
flex-direction: column; |
||||
margin: 0px; |
||||
padding: 0px; |
||||
} |
||||
|
||||
.be-dashboard-section form input[type=file] { |
||||
border: 1px solid silver; |
||||
border-radius: 8px; |
||||
margin: 4px 4px 4px 0px; |
||||
padding: 12px; |
||||
display: inline-block; |
||||
} |
||||
|
||||
.be-dashboard-section h2 { |
||||
margin: 20px 0px 20px 0px; |
||||
padding: 0px; |
||||
} |
||||
|
||||
.be-section-controls { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: flex-start; |
||||
align-items: center; |
||||
margin-bottom: 20px; |
||||
} |
||||
|
||||
.be-section-controls .be-gui-link { |
||||
margin-right: 6px; |
||||
} |
||||
|
||||
.be-section-controls .be-gui-link { |
||||
max-width: 175px; |
||||
} |
||||
|
||||
.be-section-entries { |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.be-section-entry, |
||||
.be-section-image-title { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
.be-section-image-title { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: flex-start; |
||||
max-width: 716px; |
||||
width: 100%; |
||||
margin: 4px 0px; |
||||
} |
||||
|
||||
.be-section-image-title img { |
||||
max-height: 50px; |
||||
height: 100%; |
||||
margin-right: 6px; |
||||
} |
||||
|
||||
.be-section-entry form { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: space-between; |
||||
margin: 0px; |
||||
} |
||||
|
||||
.be-section-entry img { |
||||
/* max-height: 80px; */ |
||||
/* width: 100%; */ |
||||
} |
||||
|
||||
.be-section-entry p { |
||||
max-width: 800px; |
||||
white-space: nowrap; |
||||
overflow-x: clip; |
||||
text-overflow: ellipsis; |
||||
} |
||||
|
||||
.be-section-image-title p { |
||||
white-space: initial; |
||||
} |
||||
|
||||
.be-section-entry:hover, |
||||
.be-index-item:hover, |
||||
.be-user-accounts-container:hover { |
||||
background: #fff6d2; |
||||
} |
||||
|
||||
.be-entry-controls { |
||||
display: flex; |
||||
flex-direction: row; |
||||
} |
||||
|
||||
.be-dashboard-section-list .be-gui-link { |
||||
margin: 6px 0px; |
||||
} |
||||
|
||||
.be-quicklist { |
||||
display: flex; |
||||
flex-direction: row; |
||||
flex-wrap: wrap; |
||||
align-items: center; |
||||
} |
||||
|
||||
.be-quicklist .be-gui-link { |
||||
max-width: 140px; |
||||
margin: 4px; |
||||
} |
||||
|
||||
.be-quicklist .be-gui-link img { |
||||
margin-right: 0px; |
||||
} |
||||
|
||||
.be-storage-section { |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
|
||||
.be-storage-section-upload { |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
margin: 12px 0px; |
||||
} |
||||
|
||||
.be-storage-section-upload form { |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
justify-content: center; |
||||
width: 100%; |
||||
} |
||||
|
||||
.be-storage-section-upload form [type=file] { |
||||
padding: 10px 12px; |
||||
} |
||||
|
||||
.be-storage-entry { |
||||
width: 100%; |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
} |
||||
|
||||
.file-thumbnail { |
||||
max-width: 50px; |
||||
max-height: 50px; |
||||
width: 100%; |
||||
padding: 0px 4px; |
||||
} |
||||
|
||||
.be-storage-rename { |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
width: 100%; |
||||
} |
||||
|
||||
.be-storage-rename input[type=text] { |
||||
width: 100%; |
||||
height: 30px; |
||||
} |
||||
|
||||
.be-warning { |
||||
color: red; |
||||
} |
||||
|
||||
.be-gui-link.danger { |
||||
background: #ffa700; |
||||
color: black; |
||||
} |
||||
|
||||
.be-gui-button.danger { |
||||
background: red; |
||||
} |
||||
|
||||
.be-gui-button.danger:hover, |
||||
.be-gui-link.danger:hover { |
||||
background: #473951; |
||||
text-decoration: none; |
||||
color: white; |
||||
} |
||||
|
||||
.be-popup-container { |
||||
position: relative; |
||||
display: inline-block; |
||||
} |
||||
|
||||
.be-popup { |
||||
background: palevioletred; |
||||
color: white; |
||||
padding: 8px; |
||||
border-radius: 8px; |
||||
visibility: hidden; |
||||
position: absolute; |
||||
left: 20px; |
||||
top: -20px; |
||||
z-index: 10; |
||||
} |
||||
|
||||
.fe-site-header { |
||||
display: flex; |
||||
flex-direction: column; |
||||
background: white; |
||||
align-items: center; |
||||
padding: 8px; |
||||
text-align: center; |
||||
text-decoration: none; |
||||
} |
||||
|
||||
.fe-site-header img { |
||||
max-height: 100px; |
||||
width: auto; |
||||
margin-right: 8px; |
||||
} |
||||
|
||||
#fe-main { |
||||
margin: 8px; |
||||
} |
||||
|
||||
#fe-main nav { |
||||
text-align: center; |
||||
} |
||||
|
||||
.fe-index { |
||||
max-width: 880px; |
||||
width: 100%; |
||||
margin: 0px auto; |
||||
} |
||||
|
||||
.fe-index-entry { |
||||
margin: 8px 0px; |
||||
} |
||||
|
||||
/* Start of Index Filter */ |
||||
#fe-search-filter { |
||||
box-sizing: border-box; |
||||
width: 100%; |
||||
margin-bottom: 15px; |
||||
padding: 10px; |
||||
} |
||||
|
||||
|
||||
#fe-search-filter-list { |
||||
list-style: none; |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
|
||||
#fe-search-filter-list li { |
||||
padding: 0px; |
||||
} |
||||
|
||||
|
||||
#fe-search-filter-list li.hide { |
||||
display: none; |
||||
} |
||||
/* End of Index Filter Stuff */ |
||||
|
||||
.fe-index-entry img { |
||||
max-height: 100px; |
||||
} |
||||
|
||||
.fe-hint { |
||||
margin: 0px; |
||||
padding: 0px; |
||||
font-size: 11px; |
||||
color: slategrey; |
||||
} |
||||
|
||||
.fe-article { |
||||
display: flex; |
||||
justify-content: center; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
} |
||||
|
||||
.fe-article-header { |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
flex-direction: column; |
||||
margin: 12px 0px; |
||||
} |
||||
|
||||
.fe-article-header h1 { |
||||
padding: 0px; |
||||
margin: 0px; |
||||
} |
||||
|
||||
.fe-article-header img { |
||||
/* max-width: 100px; */ |
||||
height: 100px; |
||||
} |
||||
|
||||
.flex-row { |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
} |
||||
|
||||
.fe-article-header p { |
||||
padding: 0px; |
||||
margin: 0px; |
||||
} |
||||
|
||||
.fe-article-body { |
||||
max-width: 880px; |
||||
width: 100%; |
||||
} |
||||
|
||||
.fe-article-body img { |
||||
max-width: 350px; |
||||
height: auto; |
||||
} |
||||
|
||||
.fe-article-nav { |
||||
width: 100%; |
||||
} |
||||
|
||||
/* .fe-article-nav ul { */ |
||||
/* padding: 0px; */ |
||||
/* margin: 6px 0px; */ |
||||
/* } */ |
||||
|
||||
.fe-article-nav ul li { |
||||
/* list-style: none; */ |
||||
margin: 6px 0px; |
||||
} |
||||
|
||||
/* big landscape tablets, laptops, and desktops */ |
||||
@media (min-width:880px) { |
||||
|
||||
.fe-article-body img { |
||||
max-width: 880px; |
||||
|
||||
} |
||||
|
||||
/* big landscape tablets, laptops, and desktops */ |
||||
@media (min-width:1025px) { |
||||
} |
||||
/* hi-res laptops and desktops */ |
||||
@media (min-width:1281px) { |
||||
} |
||||
/* Anything bigger */ |
||||
@media (min-width:2100px) { |
||||
} |
||||
|
@ -0,0 +1,94 @@
|
||||
.fe-search-container { |
||||
margin: 0px; |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
height: 120px; |
||||
} |
||||
|
||||
#searchbox { |
||||
position: relative; |
||||
top: 40px; |
||||
width: 100%; |
||||
max-width: 600px; |
||||
} |
||||
|
||||
.ais-SearchBox-form { |
||||
display: flex; |
||||
flex-wrap: nowrap; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
|
||||
.ais-SearchBox-input { |
||||
width: 100%; |
||||
height: 40px; |
||||
padding: 10px; |
||||
border: 1px solid black; |
||||
border-radius: 8px; |
||||
} |
||||
|
||||
.ais-SearchBox-submitIcon, |
||||
.ais-SearchBox-submit, |
||||
.ais-SearchBox-reset, |
||||
#hits { |
||||
display: none; |
||||
} |
||||
|
||||
#hits { |
||||
position: relative; |
||||
top:40px; |
||||
background: white; |
||||
width: calc(100% - 4px); |
||||
max-width: calc(600px - 4px); |
||||
border-right: 2px solid black; |
||||
border-bottom: 2px solid black; |
||||
border-left: 2px solid black; |
||||
border-radius: 0px 0px 4px 4px; |
||||
} |
||||
|
||||
.ais-Hits-list { |
||||
list-style: none; |
||||
padding: 0px 2px; |
||||
margin: 0px; |
||||
} |
||||
|
||||
.ais-Hits-list:first-child { |
||||
display: flex; |
||||
flex-wrap: nowrap; |
||||
flex-direction: column; |
||||
justify-content: flex-start; |
||||
} |
||||
|
||||
|
||||
.ais-Hits-item { |
||||
margin: 4px 0px 0px 0px; |
||||
} |
||||
|
||||
.fe-search-hit { |
||||
overflow: hidden; |
||||
white-space: nowrap; |
||||
text-overflow: ellipsis; |
||||
width: 100%; |
||||
} |
||||
|
||||
.fe-search-hit:link { |
||||
text-decoration: none; |
||||
float: left; |
||||
border-radius: 0px; |
||||
padding: 4px 0px; |
||||
} |
||||
|
||||
.fe-search-hit img { |
||||
width: 30px; |
||||
height: 30px; |
||||
display: inline; |
||||
float: left; |
||||
padding: 0px 6px; |
||||
} |
||||
|
||||
.fe-search-hit span { |
||||
vertical-align: middle; |
||||
} |
||||
|
||||
|
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 14 KiB |