Browse Source

snapshot 19.11.2022.0

Squashed commit of the following:

commit 2256d28850
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Nov 18 19:15:09 2022 +0000

    update README.org file.

commit 97293d886d
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.

commit ada40dec58
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Nov 18 18:41:45 2022 +0000

    update apiKey for live meilisearch service.

commit 66b64edc2a
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.

commit e5c95b77dc
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.

commit d12b76f87a
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.

commit 5a667149a2
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.

commit 1b9f3e673e
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).

commit 707d469ded
Author: Craig Oates <craig@craigoates.net>
Date:   Thu Nov 17 18:05:10 2022 +0000

    update makefile.

commit 5c7782439a
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.

commit 4914f3a9bc
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.

commit 6283918323
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 29 23:43:53 2022 +0100

    update makefile.

commit bf407e66bb
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 29 22:36:59 2022 +0100

    fix typo in makefile.

commit 500c25abfb
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.

commit ca5fc4e9fc
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.

commit b99e4b95f9
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.

commit 757698a7bc
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.

commit 15cc041700
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.

commit 973523c01f
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).

commit ccc4398a52
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.

commit b89dfd828f
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'.

commit d8ecc23079
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.

commit 124252070e
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.

commit 55f12b3f5b
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.

commit 08a0a06152
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.

commit 0b32687251
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.

commit fdc0b964e0
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.

commit 3565fa9315
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.

commit 2f8c973761
Author: Craig Oates <craig@craigoates.net>
Date:   Thu Oct 27 23:15:38 2022 +0100

    fix typo in site-settings.html template (search-url).

commit 189e87ae0a
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.

commit 84d0885281
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.

commit e9f679bece
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.

commit eeabb843ce
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).

commit 14750a6c0c
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?

commit a490c50cf2
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.

commit baebd89329
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.

commit b731aa06dd
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.

commit 99e507e313
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.

commit 45dc9fc645
Author: Craig Oates <craig@craigoates.net>
Date:   Tue Oct 25 02:25:00 2022 +0100

    implement restore-from-snapshot function in snapshot package.

commit ae51da0277
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.

commit 03bb7d7cee
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.

commit 7207043e2f
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.

commit c9377824dc
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 24 02:04:53 2022 +0100

    update restore-snapshot.png icon.

commit c38e9de548
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.

commit 94b626c9fa
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 24 02:03:01 2022 +0100

    add transfer.png icon to /static/images/icons directory.

commit 90650a8a5a
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).

commit 0c652be9fc
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 24 00:24:39 2022 +0100

    implement delete-snapshot function in snapshot package.

commit 782c9aee2c
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).

commit e599377158
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.

commit 0b2980f1d7
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.

commit 0b53969783
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.

commit fdf43c2b01
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.

commit 2a456bcdc5
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.

commit 89b4fbacee
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.

commit f57d6ef33c
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.

commit 26882cae6f
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.

commit a292d245b7
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.

commit 254500cad7
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.

commit 2615dcb892
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.

commit cba7675d3d
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 23 18:51:39 2022 +0100

    implement back-end for take-snapshot feature (web.lisp).

commit 943fc77d92
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.

commit efbee61366
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.

commit f629e3d750
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.

commit 8a9354f50f
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).

commit 704e87546e
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.

commit 250305e8b0
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.

commit 736d12074f
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.

commit 9ff9711af8
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.

commit 5c90771bc4
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.

commit 14586efa2a
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.

commit 72d0352c79
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.

commit 1bec6e8034
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.

commit 416d55ce3c
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).

commit 4c157b5fda
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.

commit 7f463f8d71
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 23 00:12:39 2022 +0100

    add copy.png to /static/images/icons directory.

commit a45747c7d8
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.

commit c958bc8621
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.

commit 51a078c8c2
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).

commit 757f3b0448
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').

commit 7fc8609aed
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.

commit f88686dcd1
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 22 22:02:28 2022 +0100

    add icons (/static/images) for 'delete' links and buttons.

commit fa70b7e290
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.

commit 53b2289f7a
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 22 21:58:20 2022 +0100

    update main.css ('danger-zone' stuff).

commit 3f0b990832
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/.

commit 66b84f8de6
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.

commit 78298c08ee
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).

commit b0efc40a94
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.

commit 74a6507902
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.

commit 3f21282114
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.

commit 7771f966a1
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.

commit 0596972877
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).

commit a046d21dbb
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.

commit 50a76f56ba
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.

commit 862b55ec23
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 22 01:42:13 2022 +0100

    implement /sitemap.xml defroute in web.lisp file.

commit 5f040b642d
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.

commit 3cfb5d4331
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...

commit e336640fa0
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.

commit ad58e02f5c
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.

commit d6b7e031e7
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).

commit 2b4336bced
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.

commit a4486ad168
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.

commit bb7396d95e
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).

commit 2515803ce0
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.

commit 6d60cfa86a
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.

commit c0ca6c8aa2
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.

commit f23b9b8a85
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.

commit 89283b7cf7
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 17 20:45:17 2022 +0100

    add 'quick search' section to archive-entry.html template.

commit 07d6607e88
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.

commit 554084f05a
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 17 20:44:08 2022 +0100

    remove some HTML from search.html template.

commit d2005080b2
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.

commit 2d862e29e2
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.

commit 73c4a1cd16
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 15 21:33:57 2022 +0100

    update main.css (mostly image sizing in article body).

commit ac402d333d
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.

commit 55df7b5161
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 15 21:17:13 2022 +0100

    simplify templates/archive-entry.html template (remove entry dates).

commit 3ff07064a6
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.

commit 833b125f46
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.

commit c9733d5f94
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.

commit 7931d33588
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 15 20:46:45 2022 +0100

    update main.css (input for 'Publish' date mostly).

commit b874b3e742
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.

commit b750649cb2
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.

commit 36f396b0da
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.

commit 37ec669980
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.

commit 2300c9413a
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.

commit a697b09462
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.

commit eba62e0925
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.

commit e6a1b4287f
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).

commit a610b52ba6
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).

commit 6db73aac52
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.

commit 1bb0a92d39
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 15 16:48:32 2022 +0100

    add footer tag to footer.html template.

commit e548ba7993
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.

commit bf007601ea
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Oct 14 21:51:02 2022 +0100

    fix grammar mistake in /templates/user/edit.html template.

commit f74ae90e02
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.

commit bb4fbe1a00
Author: Craig Oates <craig@craigoates.net>
Date:   Tue Oct 11 00:01:15 2022 +0100

    update main.css (front-end archive entry).

commit 146901fa80
Author: Craig Oates <craig@craigoates.net>
Date:   Tue Oct 11 00:00:38 2022 +0100

    set site title dynamically in index.html template.

commit d4ab83ef7a
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.

commit 94122e7741
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.

commit f38e0aef42
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 10 20:11:57 2022 +0100

    integrate Meilisearch into edit keywords functionality (web.lisp).

commit 5d853592bf
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.

commit e90de5e31a
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.

commit 6e4da8dc26
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.

commit d488b56780
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.

commit 0f2067742a
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.

commit 5287e9858e
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.

commit 6389480260
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.

commit ad65e0fca1
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.

commit 4d5a9b7f0c
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.

commit 82192113c0
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 9 21:40:15 2022 +0100

    update main.css (front-end header stuff).

commit 990eb22f6d
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 9 21:39:52 2022 +0100

    remove <hr> from /layouts/footer.html template.

commit 6a5b6ea838
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.

commit ad0954e319
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.

commit 0aef87a949
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.

commit c2788c43af
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.

commit 04c79732f7
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.

commit cff6bfc1ee
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).

commit 295ed248c9
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 9 17:20:47 2022 +0100

    update default favicon.png and site-logo.png files.

commit e705aa67e4
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 9 17:19:50 2022 +0100

    update main.css -- mostly for initial-setup.html template.

commit a834f54a13
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.

commit 324229dea4
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.

commit d35ae529db
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 9 17:10:41 2022 +0100

    add power.png image to /static/images/icons directory.

commit eb7435f720
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.

commit 85b0d8da9e
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.

commit b9493fb138
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 8 20:48:11 2022 +0100

    fix typo. in main.css.

commit 7bd5f1dd02
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 8 20:29:20 2022 +0100

    update main.css (section controls for dashboard).

commit 78cf655f5d
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.

commit 3d24084e24
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.

commit 7b1a80333c
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 8 20:18:30 2022 +0100

    update main.css (site-setting stuff).

commit 3274456906
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.

commit cedab101f1
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.

commit a522701b58
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 8 17:10:29 2022 +0100

    update /user/index.html (HTML and CSS).

commit 9d19cdcea9
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 8 17:10:09 2022 +0100

    add 'quicklist' section to /user/edit.html template.

commit 2a7e133d7e
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 8 17:09:48 2022 +0100

    update main.css (user account stuff).

commit c8de64382f
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 8 17:09:06 2022 +0100

    add all-accounts.png and create-account.png images.

commit d64ae60c26
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).

commit 76450d3414
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.

commit 949cee862c
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).

commit ea4520e7d4
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).

commit e688c55cd9
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.

commit 9430942cb5
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.

commit 6c65b9c5a0
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 8 12:00:50 2022 +0100

    add delete-account.png icon.

commit 661bbbd121
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 8 11:15:14 2022 +0100

    update main.css file (log-in form stuff mostly).

commit 2730bbaaa1
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Oct 8 11:14:40 2022 +0100

    apply CSS classes to /user/login.html template.

commit 10e354fc9d
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.

commit ff86cb159a
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.

commit 91321b26ec
Author: Craig Oates <craig@craigoates.net>
Date:   Tue Oct 4 11:37:46 2022 +0100

    replace dashboard profile img. with insert-dashboard-cat filter.

commit 130a6d3061
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.

commit 318cf88f73
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.

commit 7b24fdca1c
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.

commit 0795a67f86
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.

commit 5212a9b985
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.

commit a7aff4b828
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.

commit fad642822a
Author: Craig Oates <craig@craigoates.net>
Date:   Tue Oct 4 10:28:07 2022 +0100

    update main.css (alert message styling).

commit d446834565
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.

commit 2e1e5b59d5
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.

commit 9df8e363c9
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 3 08:04:37 2022 +0100

    update dashboard.html template (add thumbnails to Storage section).

commit 5782362136
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.

commit d791b04449
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 3 08:00:37 2022 +0100

    update main.css (mostly fixing rows with thumbnails in).

commit c89d1653a9
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.

commit e33ac9af0a
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.

commit 426d404593
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 3 06:19:02 2022 +0100

    update HTML in /user/edit-page.html template.

commit 5ef81ba4f3
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 3 06:18:49 2022 +0100

    update HTML in /user/edit-archive.html template.

commit b6aec7c5ea
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 3 06:18:34 2022 +0100

    update HTML in /user/create-page.html template.

commit afaa7268ad
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 3 06:18:15 2022 +0100

    update HTML in /user/create-archive.html template.

commit 006bd652af
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 3 06:17:28 2022 +0100

    apply minor formatting changes to /user/storage.html template.

commit 009e0103b8
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.

commit d7bbb9ecf2
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).

commit db4393ddac
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.

commit 09e44a6364
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Oct 3 06:13:18 2022 +0100

    add save.png icon to /static/images/icons directory.

commit 936d9034c8
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 2 09:46:18 2022 +0100

    end-of-session commit -- update main.css file.

commit e15b2299ad
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.

commit ecfb667c67
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 2 09:42:07 2022 +0100

    clean-up and remove unused code (HTML templates).

commit e33575c74c
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 2 09:41:01 2022 +0100

    update main.css (mostly index pages listing/title section).

commit fa9bb8cfb2
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 2 07:54:38 2022 +0100

    minor padding change for be-gui-link class.

commit b4a34c102a
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.

commit d305d4c110
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 2 07:38:23 2022 +0100

    update /user/storage.html template (mostly CSS updates).

commit 1551e3fc1b
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Oct 2 07:37:47 2022 +0100

    update /user/dashboard.html template (mostly for CSS updates).

commit 7a6353635b
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.

commit 61e8ef91f8
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.

commit eddedc891d
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.

commit baed98ce7a
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.

commit 4647323fda
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.

commit 3b49fbf6e0
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.

commit c27ecb9853
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.

commit 75e05cc4aa
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).

commit 87f0e81258
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Sep 30 06:36:51 2022 +0100

    update /user/dashboard.html template (CSS and icons).

commit dd7796fb4e
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Sep 30 06:36:26 2022 +0100

    update /layouts/header.html template (CSS and icons).

commit 01bacffa19
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Sep 30 06:35:56 2022 +0100

    update several of the icons in /static/images/icons.

commit 9a2c933667
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.

commit 7b6cde549b
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.

commit dcd1e76d0e
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.

commit e844408d86
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).

commit 0dc9c91795
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.

commit f4bf0ba511
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.

commit 51d564fff8
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.

commit 8c60ac6031
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.

commit f60b09bbdd
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.

commit a7eb3240bf
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.

commit fda81fbb82
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.

commit a72e133f13
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.

commit 9a30da1a44
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.

commit 0a302c2a77
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.

commit f814aef20d
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).

commit 84f3ede736
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.

commit a76f837eea
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 25 22:41:38 2022 +0100

    add insert-snippet functionality to djula:view package.

commit a1d1042d4c
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 25 22:40:12 2022 +0100

    update first-run code -- refactor site-wide snippet file.

commit e0a1bf7cb1
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.

commit 4156b0eee0
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.

commit e8831de9f4
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.

commit 4b5831452d
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 25 22:04:28 2022 +0100

    add thumbnail to /archive.html template.

commit bb35111dc9
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.

commit 44bdec6570
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 25 20:07:55 2022 +0100

    implement the /edit/archive defroute -- updates the entries text.

commit fb8f68e468
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 25 19:59:53 2022 +0100

    implement the edit archive keywords functionality (web.lisp).

commit c313b4aff4
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 25 19:59:33 2022 +0100

    update keyword form in /user/edit-archive.html template.

commit e53a2990c0
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.

commit 81a0fb8e40
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).

commit 7328a719fe
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.

commit 82e8428695
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.

commit ca4a540141
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.

commit 379bbdc8b4
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 25 18:20:05 2022 +0100

    implement update-archive-entry-property in nera package.

commit 8d55ed7d61
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.

commit c69c165a92
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.

commit 57a6710e92
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.

commit 93a59aaad4
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 24 22:18:23 2022 +0100

    implement /edit/archive/:slug defroute (HTTP GET request).

commit 02df53e7f4
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.

commit f205dc3252
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 24 22:16:54 2022 +0100

    remove old code from /user/edit-page.html template.

commit eb9d2e3290
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 24 21:57:10 2022 +0100

    implement /archive/delete/entry defroute (HTTP POST request).

commit ee336d3c83
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 24 21:56:35 2022 +0100

    implement delete-archive-entry in nera package.

commit f89585ab46
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.

commit bba7cf4499
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.

commit a7d0ae54ad
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.

commit 5542014a1e
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 24 20:41:58 2022 +0100

    sort out code layout in view.lisp file.

commit baf1ec1f39
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'.

commit 3dc6a812bf
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.

commit 7d9e29bac8
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.

commit 5cc14f74bf
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.

commit c42d169d84
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.

commit 23365343b1
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).

commit ffd483411c
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.

commit 58b03ae8f2
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).

commit c1e664b631
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 24 01:13:09 2022 +0100

    remove unused code from archive-entry class.

commit eff71bcbfe
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Sep 23 18:19:44 2022 +0100

    import archive package to nera package.

commit d6cc11dc03
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.

commit 6a67a7cfc7
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.

commit 80aab44c00
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.

commit 592c38759f
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Sep 23 16:39:28 2022 +0100

    move create-thumbnail code to utils (reduce duplicated code).

commit 13d1e31c13
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.

commit 2c069573db
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.

commit 44c7ebbc90
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Sep 23 16:13:46 2022 +0100

    add @build-thumbnail (djula) filter in view.lisp file.

commit 6d8fa33b97
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Sep 23 16:13:26 2022 +0100

    remove get-latest-file-type code from storage package.

commit a15619de56
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.

commit 7a6f330497
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).

commit 104ac264a8
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).

commit 4c99109388
Author: Craig Oates <craig@craigoates.net>
Date:   Thu Sep 22 21:07:34 2022 +0100

    export delete-storage-file from nera package.

commit 1cf86c674c
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.

commit 191f5b2c22
Author: Craig Oates <craig@craigoates.net>
Date:   Tue Sep 20 20:34:46 2022 +0100

    add delete-storage-file function in nera package.

commit 0331999136
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.

commit f3d856f7f4
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.

commit b9848197e7
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.

commit d0ff0c812b
Author: Craig Oates <craig@craigoates.net>
Date:   Tue Sep 20 19:23:25 2022 +0100

    update storage and dashboard HTML templates (add & get features).

commit 953bfc5fb0
Author: Craig Oates <craig@craigoates.net>
Date:   Tue Sep 20 19:21:54 2022 +0100

    implement the 'add and get' functionality for storage routes.

commit f5549e17a1
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).

commit 6e7d2b6845
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.

commit 0ce1aef3e4
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.

commit 63e3919970
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.

commit 015eda2b0a
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.

commit a89686c05f
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Sep 19 22:58:16 2022 +0100

    add file uploads section to /user/dashboard.html template.

commit c4f9f52ba8
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Sep 19 22:57:45 2022 +0100

    add /storage/upload defroutes (single and multi file uploads).

commit a855d9db90
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.

commit 8106b5a5c5
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'.

commit d31c4ab38a
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Sep 19 19:27:38 2022 +0100

    remove old defroutes.

commit f626557b97
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.

commit 2bb7d053e6
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.

commit e9792a6fa0
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Sep 19 19:18:55 2022 +0100

    import archivo in main.css file.

commit 4c1b99fa08
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.

commit e765c3b24e
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.

commit 9b8c5a7ca5
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Sep 19 19:16:20 2022 +0100

    add archivo font.

commit bb5f8fb482
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.

commit 2f819de9a2
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.

commit f78beb7ea4
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.

commit 161e9c6169
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.

commit d0426a9494
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.

commit 7e2f04d370
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 18 23:07:23 2022 +0100

    add can-delete check to /user/pages.html template.

commit aacfcba654
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 18 23:06:46 2022 +0100

    add favicon link to /layouts/default.html template.

commit 9a50938c0d
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 18 23:06:07 2022 +0100

    implement get-image-dimensions function in utils package.

commit 4bac1e3db3
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.

commit 6ecd555712
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.

commit e3471e82ea
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.

commit 4736db9bd2
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 18 23:02:06 2022 +0100

    add validation package.

commit e4612d0711
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).

commit b26b296e89
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.

commit 94e326a292
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.

commit 7e892f25b1
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 17 18:18:35 2022 +0100

    add {{content}} section to index.html template.

commit d9e09c52f6
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.

commit f1c79a9ecd
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 17 18:15:38 2022 +0100

    add integer-to-checkbox djula-filter (view.lisp).

commit eb62ade5be
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.

commit 6d4d16ec45
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.

commit bfcef81763
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 17 18:10:58 2022 +0100

    add home-page slot to site-settings class.

commit cb51a83605
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.

commit 45ef9fa84a
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.

commit b9180da6e2
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.

commit f4744f14b7
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).

commit 1c6781c945
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 17 13:27:47 2022 +0100

    implement the 'edit pages' functionality in web.lisp file.

commit d6d5a58792
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.

commit f0356b00f0
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.

commit 681033b1cd
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 17 13:25:23 2022 +0100

    add /pages link to /user/dashboard.html template.

commit 6b09c3e81c
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.

commit 3937b626e8
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 17 11:50:43 2022 +0100

    finish implementing the /create/page defroute (HTTP POST).

commit a477470a90
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.

commit 3dbb603831
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.

commit 6fce318c87
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.

commit 9e308ab92e
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.

commit 9222d347fd
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 .

commit 6c80f146f3
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/

commit 875bd28841
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.

commit bdd86d4ad7
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Sep 16 19:10:54 2022 +0100

    add Sign-up link to index.html template.

commit 11e5e6a08e
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).

commit 19bbba0f3e
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.

commit 9724eda5fd
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.

commit 793c5d544b
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.

commit 7349c4b9f5
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.

commit 1e26f1ea8d
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.

commit 68792eac19
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Sep 16 18:58:32 2022 +0100

    add alert message data to "/" defroute.

commit d025b5d0a0
Author: Craig Oates <craig@craigoates.net>
Date:   Thu Sep 15 15:23:37 2022 +0100

    start to add functionality for signing new users up.

commit 428a0a6ba7
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.

commit 3596a696db
Author: Craig Oates <craig@craigoates.net>
Date:   Thu Sep 15 15:18:28 2022 +0100

    create initial-setup.html template.

commit 177521aa34
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.

commit 9a665c5c4e
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.

commit 7e84b86291
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.

commit a4d3f35306
Author: Craig Oates <craig@craigoates.net>
Date:   Tue Sep 13 05:11:49 2022 +0100

    add alert section to default.html template.

commit 249f86b88e
Author: Craig Oates <craig@craigoates.net>
Date:   Tue Sep 13 05:11:34 2022 +0100

    add link to /dashboard in index.html template.

commit 777458ee37
Author: Craig Oates <craig@craigoates.net>
Date:   Tue Sep 13 05:11:07 2022 +0100

    code clean-up in some HTML templates.

commit 25142d5049
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.

commit e6162306ef
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.

commit 89ad7fb269
Author: Craig Oates <craig@craigoates.net>
Date:   Tue Sep 13 05:03:10 2022 +0100

    code formatting in db package.

commit b469063f74
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.

commit bf8b79021e
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.

commit 0941922f57
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.

commit 490a79a356
Author: Craig Oates <craig@craigoates.net>
Date:   Mon Sep 12 07:49:41 2022 +0100

    add log-in and start account setting functionality.

commit 2aaefdbf5e
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.

commit 3b6afd8a4a
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.

commit 7e32795392
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).

commit e6ef8bec34
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.

commit c8074c821b
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.

commit d6dbcf7ec4
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 11 07:27:52 2022 +0100

    create Caveman2 project (using Caveman2's project generator).

commit 51d2213aae
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.

commit 9c251a1698
Author: Craig Oates <craig@craigoates.net>
Date:   Sun Sep 11 07:23:10 2022 +0100

    update licence.

commit 6257e7ec94
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).

commit f5403b6cc2
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.

commit ff0c121e63
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 10 22:16:55 2022 +0100

    create user class and add to database table.

commit c5ba5b1e4b
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.

commit 8d52eaf0a6
Author: Craig Oates <craig@craigoates.net>
Date:   Sat Sep 10 19:14:15 2022 +0100

    update .gitignore (databases and /bin directory).

commit 7d3d3e57d5
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.

commit 3177d956f1
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.

commit ee1bf2f927
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.

commit 851980f784
Author: Craig Oates <craig@craigoates.net>
Date:   Fri Sep 9 02:39:24 2022 +0100

    copy a Python-base template into .gitignore.

commit 24b396d47b
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'.
master
Craig Oates 1 year ago
parent
commit
9650008ce9
  1. 30
      .gitignore
  2. 2
      LICENSE
  3. 190
      README.org
  4. 51
      app.lisp
  5. 8
      conf/meilisearch.conf
  6. 10
      conf/meilisearch.service
  7. 30
      conf/ritherdon-archive.conf
  8. 13
      conf/ritherdon-archive.service
  9. 0
      db/schema.sql
  10. 16
      hunchentoot-example/Makefile
  11. 101
      hunchentoot-example/README.md
  12. 9
      hunchentoot-example/README.org
  13. BIN
      hunchentoot-example/bin/libz.so.1.2.11
  14. BIN
      hunchentoot-example/bin/ritherdon-archive
  15. 12
      hunchentoot-example/config-example.lisp
  16. 18
      hunchentoot-example/ritherdon-archive-tests.asd
  17. 89
      hunchentoot-example/ritherdon-archive.asd
  18. 37
      hunchentoot-example/roswell/README.md
  19. 28
      hunchentoot-example/roswell/ritherdon-archive.ros
  20. 9
      hunchentoot-example/run-tests.lisp
  21. 25
      hunchentoot-example/run.lisp
  22. 39
      hunchentoot-example/scripts/create-user.sh
  23. 30
      hunchentoot-example/src/database.lisp
  24. 61
      hunchentoot-example/src/models/models.lisp
  25. 26
      hunchentoot-example/src/models/user.lisp
  26. 41
      hunchentoot-example/src/packages.lisp
  27. 105
      hunchentoot-example/src/ritherdon-archive.lisp
  28. 1
      hunchentoot-example/src/static/css/main.css
  29. 2
      hunchentoot-example/src/static/js/ritherdon-archive.js
  30. 1
      hunchentoot-example/src/templates/404.html
  31. 5
      hunchentoot-example/src/templates/about.html
  32. 6
      hunchentoot-example/src/templates/archive.html
  33. 15
      hunchentoot-example/src/templates/base.html
  34. 175
      hunchentoot-example/src/templates/dashboard.html
  35. 6
      hunchentoot-example/src/templates/home.html
  36. 16
      hunchentoot-example/src/templates/login.html
  37. 9
      hunchentoot-example/src/utils.lisp
  38. 135
      hunchentoot-example/src/web.lisp
  39. 8
      hunchentoot-example/tests/packages.lisp
  40. 16
      hunchentoot-example/tests/test-ritherdon-archive.lisp
  41. 44
      makefile
  42. 11
      ritherdon-archive-test.asd
  43. 99
      ritherdon-archive.asd
  44. 39
      scripts/create-user.sh
  45. 39
      src/app-constants.lisp
  46. 76
      src/auth.lisp
  47. 60
      src/config.lisp
  48. 27
      src/db.lisp
  49. 50
      src/main.lisp
  50. 76
      src/models/archive.lisp
  51. 35
      src/models/files.lisp
  52. 48
      src/models/pages.lisp
  53. 50
      src/models/site-settings.lisp
  54. 41
      src/models/user.lisp
  55. 416
      src/nera.lisp
  56. 185
      src/search.lisp
  57. 62
      src/snapshot.lisp
  58. 498
      src/status-codes.lisp
  59. 317
      src/storage.lisp
  60. 200
      src/utils.lisp
  61. 57
      src/validation.lisp
  62. 136
      src/view.lisp
  63. 1849
      src/web.lisp
  64. BIN
      static/css/archivo/Archivo-Bold.otf
  65. BIN
      static/css/archivo/Archivo-BoldItalic.otf
  66. BIN
      static/css/archivo/Archivo-Italic.otf
  67. BIN
      static/css/archivo/Archivo-Medium.otf
  68. BIN
      static/css/archivo/Archivo-MediumItalic.otf
  69. BIN
      static/css/archivo/Archivo-Regular.otf
  70. BIN
      static/css/archivo/Archivo-SemiBold.otf
  71. BIN
      static/css/archivo/Archivo-SemiBoldItalic.otf
  72. 294
      static/css/full-search.css
  73. 850
      static/css/main.css
  74. 94
      static/css/search.css
  75. BIN
      static/images/alerts/art-cat.png
  76. BIN
      static/images/alerts/confused-cat.png
  77. BIN
      static/images/alerts/disco-cat.png
  78. BIN
      static/images/alerts/pending-cat.png
  79. BIN
      static/images/alerts/sherlock-cat.png
  80. BIN
      static/images/alerts/success-cat.png
  81. BIN
      static/images/alerts/vomit-cat.png
  82. BIN
      static/images/alerts/workout-cat.png
  83. BIN
      static/images/icons-1/ai.png
  84. BIN
      static/images/icons-1/cad.png
  85. BIN
      static/images/icons-1/css.png
  86. BIN
      static/images/icons-1/docx.png
  87. BIN
      static/images/icons-1/gif.png
  88. BIN
      static/images/icons-1/html.png
  89. BIN
      static/images/icons-1/jpg.png
  90. BIN
      static/images/icons-1/mp4.png
  91. BIN
      static/images/icons-1/png.png
  92. BIN
      static/images/icons-1/pptx.png
  93. BIN
      static/images/icons-1/psd.png
  94. BIN
      static/images/icons-1/rar.png
  95. BIN
      static/images/icons-1/txt.png
  96. BIN
      static/images/icons-1/xlsx.png
  97. BIN
      static/images/icons-1/zip.png
  98. BIN
      static/images/icons/add-circle.png
  99. BIN
      static/images/icons/add-square.png
  100. BIN
      static/images/icons/ai.png
  101. Some files were not shown because too many files have changed in this diff Show More

30
.gitignore vendored

@ -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

2
LICENSE

@ -1,6 +1,6 @@
MIT License
Copyright (c) <year> <copyright holders>
Copyright (c) 2022 Craig Oates
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

190
README.org

@ -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.

51
app.lisp

@ -15,25 +15,32 @@
:*static-directory*))
(in-package :ritherdon-archive.app)
(builder
(:static
:path (lambda (path)
(if (ppcre:scan "^(?:/images/|/css/|/js/|/robot\\.txt$|/favicon\\.ico$)" path)
path
nil))
:root *static-directory*)
(if (productionp)
nil
:accesslog)
(if (getf (config) :error-log)
`(:backtrace
:output ,(getf (config) :error-log))
nil)
:session
(if (productionp)
nil
(lambda (app)
(lambda (env)
(let ((datafly:*trace-sql* t))
(funcall app env)))))
*web*)
(funcall clack-errors:*clack-error-middleware*
;; The funcall line above is added as part of the
;; clack-errors set-up. Usually, the '(builder' line is the
;; start of this block.
(builder
(:static
:path (lambda (path)
(if (ppcre:scan "^(?:/images/|/css/|/js/|/robot\\.txt$|/favicon\\.ico$)" path)
path
nil))
:root *static-directory*)
(if (productionp)
nil
:accesslog)
(if (getf (config) :error-log)
`(:backtrace
:output ,(getf (config) :error-log))
nil)
:session
(if (productionp)
nil
(lambda (app)
(lambda (env)
(let ((datafly:*trace-sql* t))
(funcall app env)))))
*web*)
:debug (if (ritherdon-archive.config:productionp)
nil
t)) ; Added as part of clack-error-middleware.

8
conf/meilisearch.conf

@ -0,0 +1,8 @@
server {
listen 80;
listen <INSERT URL HERE>;
server_name _;
location / {
proxy_pass http://127.0.0.1:7700;
}
}

10
conf/meilisearch.service

@ -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

30
conf/ritherdon-archive.conf

@ -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;
}
}
}

13
conf/ritherdon-archive.service

@ -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
db/schema.sql

16
hunchentoot-example/Makefile

@ -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

101
hunchentoot-example/README.md

@ -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

9
hunchentoot-example/README.org

@ -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).

BIN
hunchentoot-example/bin/libz.so.1.2.11

Binary file not shown.

BIN
hunchentoot-example/bin/ritherdon-archive

Binary file not shown.

12
hunchentoot-example/config-example.lisp

@ -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)

18
hunchentoot-example/ritherdon-archive-tests.asd

@ -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))
)

89
hunchentoot-example/ritherdon-archive.asd

@ -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))

37
hunchentoot-example/roswell/README.md

@ -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

28
hunchentoot-example/roswell/ritherdon-archive.ros

@ -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)))

9
hunchentoot-example/run-tests.lisp

@ -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))

25
hunchentoot-example/run.lisp

@ -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)))

39
hunchentoot-example/scripts/create-user.sh

@ -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

30
hunchentoot-example/src/database.lisp

@ -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))

61
hunchentoot-example/src/models/models.lisp

@ -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)))

26
hunchentoot-example/src/models/user.lisp

@ -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.."))

41
hunchentoot-example/src/packages.lisp

@ -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)))

105
hunchentoot-example/src/ritherdon-archive.lisp

@ -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))))

1
hunchentoot-example/src/static/css/main.css

@ -0,0 +1 @@

2
hunchentoot-example/src/static/js/ritherdon-archive.js

@ -0,0 +1,2 @@
console.log("Hello ritherdon-archive!");

1
hunchentoot-example/src/templates/404.html

@ -0,0 +1 @@
<h1>404: Web Page Not Found</h1>

5
hunchentoot-example/src/templates/about.html

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block content %}
<h1>About</h1>
{% end block %}

6
hunchentoot-example/src/templates/archive.html

@ -0,0 +1,6 @@
{% extends "base.html" %}
{% block content %}
<h1>Archive</h1>
{% end block %}

15
hunchentoot-example/src/templates/base.html

@ -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>

175
hunchentoot-example/src/templates/dashboard.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 %}

6
hunchentoot-example/src/templates/home.html

@ -0,0 +1,6 @@
{% extends "base.html" %}
{% block content %}
<h1>Index</h1>
{% end block %}

16
hunchentoot-example/src/templates/login.html

@ -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 %}

9
hunchentoot-example/src/utils.lisp

@ -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)))

135
hunchentoot-example/src/web.lisp

@ -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))

8
hunchentoot-example/tests/packages.lisp

@ -0,0 +1,8 @@
(in-package :asdf-user)
(defpackage :ritherdon-archive-tests
(:use :common-lisp
:parachute
:ritherdon-archive))
(in-package :ritherdon-archive-tests)

16
hunchentoot-example/tests/test-ritherdon-archive.lisp

@ -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")))

44
makefile

@ -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.'

11
ritherdon-archive-test.asd

@ -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)))

99
ritherdon-archive.asd

@ -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)))

39
scripts/create-user.sh

@ -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

39
src/app-constants.lisp

@ -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.")

76
src/auth.lisp

@ -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))

60
src/config.lisp

@ -1,17 +1,19 @@
(in-package :cl-user)
(defpackage ritherdon-archive.config
(:use :cl)
(:import-from :envy
:config-env-var
:defconfig)
(:export :config
:*application-root*
:*static-directory*
:*template-directory*
:appenv
:developmentp
:productionp))
(in-package :ritherdon-archive.config)
(in-package #:cl-user)
(defpackage #:ritherdon-archive.config
(:use #:cl)
(:import-from #:envy
#:config-env-var
#:defconfig)
(:export #:config
#:*application-root*
#:*static-directory*
#:*template-directory*
#:appenv
#:developmentp
#:productionp
#:testp
#:database-name))
(in-package #:ritherdon-archive.config)
(setf (config-env-var) "APP_ENV")
@ -20,16 +22,25 @@
(defparameter *template-directory* (merge-pathnames #P"templates/" *application-root*))
(defconfig :common
`(:databases ((:maindb :sqlite3 :database-name ":memory:"))))
`(:application-root ,(asdf:component-pathname (asdf:find-system :ritherdon-archive))))
(defconfig |development|
'())
`(:debug T
:databases
((:maindb :sqlite3
:database-name ,(merge-pathnames #P"db/nera-dev.db"
*application-root*)))))
(defconfig |production|
'())
`(:debug nil
:databases
((:maindb :sqlite3
:database-name ,(merge-pathnames #P"db/nera-prod.db"
*application-root*)))))
(defconfig |test|
'())
(defconfig |staging|
`(:debug T
,@|production|))
(defun config (&optional key)
(envy:config #.(package-name *package*) key))
@ -42,3 +53,14 @@
(defun productionp ()
(string= (appenv) "production"))
(defun stagingp ()
(string= (appenv) "staging"))
(defun database-name ()
(first (last (first (config :databases)))))
;;; Use this to change the environment between "development" and
;;; "production". This change is mostly to specifiy which database the
;;; system will use.
(setf (osicat:environment-variable "APP_ENV") "development")

27
src/db.lisp

@ -1,15 +1,16 @@
(in-package :cl-user)
(defpackage ritherdon-archive.db
(:use :cl)
(:import-from :ritherdon-archive.config
:config)
(:import-from :datafly
:*connection*)
(:import-from :cl-dbi
:connect-cached)
(:export :connection-settings
:db
:with-connection))
(in-package #:cl-user)
(defpackage #:ritherdon-archive.db
(:use #:cl)
(:import-from #:ritherdon-archive.config
#:config)
(:import-from #:datafly
#:*connection*)
(:import-from #:cl-dbi
#:connect-cached)
(:export #:connection-settings
#:db
#:with-connection
#:init-db))
(in-package :ritherdon-archive.db)
(defun connection-settings (&optional (db :maindb))
@ -19,5 +20,5 @@
(apply #'connect-cached (connection-settings db)))
(defmacro with-connection (conn &body body)
`(let ((*connection* ,conn))
`(let ((mito:*connection* ,conn))
,@body))

50
src/main.lisp

@ -1,13 +1,14 @@
(in-package :cl-user)
(defpackage ritherdon-archive
(:use :cl)
(:import-from :ritherdon-archive.config
:config)
(:import-from :clack
:clackup)
(:export :start
:stop))
(in-package :ritherdon-archive)
(in-package #:cl-user)
(defpackage #:ritherdon-archive
(:use #:cl)
(:import-from #:ritherdon-archive.config
#:config)
(:import-from #:clack
#:clackup)
(:export #:start
#:stop
#:main))
(in-package #:ritherdon-archive)
(defvar *appfile-path*
(asdf:system-relative-pathname :ritherdon-archive #P"app.lisp"))
@ -28,3 +29,32 @@
(prog1
(clack:stop *handler*)
(setf *handler* nil)))
#| 'main' Function Used For Starting Server From Script (I.E. Live Deployment)
================================================================================
https://lisp-journey.gitlab.io/web-dev/#building
The code below was taken from the URL above (with slight modifications). It's
main use is to make it easier to start the server via a script.
|#
(defun main (&key (port 5000))
(start :server :woo ; hunchentoot or woo.
:port port
:debug (if (ritherdon-archive.config:productionp)
nil
t))
;; with bordeaux-threads
(handler-case (bt:join-thread
(find-if (lambda (th)
(search "woo" (bt:thread-name th)))
(bt:all-threads)))
(#+sbcl sb-sys:interactive-interrupt
#+ccl ccl:interrupt-signal-condition
#+clisp system::simple-interrupt-condition
#+ecl ext:interactive-interrupt
#+allegro excl:interrupt-signal
() (progn
(format *error-output* "Aborting.~&")
(clack:stop *handler*)
(uiop:quit 1)) ;; portable exit, included in ASDF, already loaded.
;; for others, unhandled errors (we might want to do the same).
(error (c) (format t "Woops, an unknown error occured:~&~a~&" c)))))

76
src/models/archive.lisp

@ -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))

35
src/models/files.lisp

@ -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))

48
src/models/pages.lisp

@ -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))

50
src/models/site-settings.lisp

@ -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))

41
src/models/user.lisp

@ -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))

416
src/nera.lisp

@ -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))))

185
src/search.lisp

@ -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)))))

62
src/snapshot.lisp

@ -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)))

498
src/status-codes.lisp

@ -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.")

317
src/storage.lisp

@ -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)))))

200
src/utils.lisp

@ -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))
"")))

57
src/validation.lisp

@ -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)))

136
src/view.lisp

@ -1,21 +1,21 @@
(in-package :cl-user)
(defpackage ritherdon-archive.view
(:use :cl)
(:import-from :ritherdon-archive.config
:*template-directory*)
(:import-from :caveman2
:*response*
:response-headers)
(:import-from :djula
:add-template-directory
:compile-template*
:render-template*
:*djula-execute-package*)
(:import-from :datafly
:encode-json)
(:export :render
:render-json))
(in-package :ritherdon-archive.view)
(in-package #:cl-user)
(defpackage #:ritherdon-archive.view
(:use #:cl)
(:import-from #:ritherdon-archive.config
#:*template-directory*)
(:import-from #:caveman2
#:*response*
#:response-headers)
(:import-from #:djula
#:add-template-directory
#:compile-template*
#:render-template*
#:*djula-execute-package*)
(:import-from #:datafly
:encode-json)
(:export #:render
#:render-json))
(in-package #:ritherdon-archive.view)
(djula:add-template-directory *template-directory*)
@ -34,18 +34,100 @@
(setf (getf (response-headers *response*) :content-type) "application/json")
(encode-json object))
;;
;; Execute package definition
(defpackage ritherdon-archive.djula
(:use :cl)
(:import-from :ritherdon-archive.config
:config
:appenv
:developmentp
:productionp)
(:import-from :caveman2
:url-for))
(:use #:cl
#:storage)
(:import-from #:files
#:storage-file)
(:import-from #:ritherdon-archive.config
#:config
#:appenv
#:developmentp
#:productionp)
(:import-from #:caveman2
#:url-for))
;; Added 'in-package' line after default Caveman2 set-up. Needed for custom
;; functions. Not part of Caveman2 set-up.
(in-package #:ritherdon-archive.djula)
(setf djula:*djula-execute-package* (find-package :ritherdon-archive.djula))
;; Custom filters and template code added below...
;; =============================================================================
;; This filter is for converting Integers used to store Boolean values
;; in a SQLite database. The intended place you will use this filter
;; is in the /software section. The most notable parts being the /add
;; and /edit sections. In the SQLite database, I have set:
;; - 0 => false
;; - 1 => true.
;; In this case, if the `VALUE' is not 1, it is always false (I.E. 0).
(djula:def-filter :integer-to-checkbox (value)
(format nil "~S" (if (= 1 value) "on" "off")))
(djula:def-filter :build-thumbnail-path (file)
(cond ((str:contains? "svg" (files::file-type-of file) :ignore-case t)
(format nil "/images/icons/svg.png"))
((str:contains? "image" (files::file-type-of file) :ignore-case t)
(format nil "/storage/thumb/media/~a" (files::slug-of file)))
((str:contains? "pdf" (files::file-type-of file) :ignore-case t)
(format nil "/images/icons/pdf.png"))
((str:contains? "html" (files::file-type-of file) :ignore-case t)
(format nil "/images/icons/code.png"))
((str:contains? "text" (files::file-type-of file) :ignore-case t)
(format nil "/images/icons/txt.png"))
((str:contains? "css" (files::file-type-of file) :ignore-case t)
(format nil "/images/icons/code2.png"))
((str:contains? "video" (files::file-type-of file) :ignore-case t)
(format nil "/images/icons/video.png"))
((str:contains? "audio" (files::file-type-of file) :ignore-case t)
(format nil "/images/icons/audio.png"))
((str:contains? "zip" (files::file-type-of file) :ignore-case t)
(format nil "/images/icons/archive.png"))
(t (format nil "/images/icons/file.png"))))
(defun insert-snippet (snippet-name)
(if (storage:file-exists-p "" "snippets" snippet-name)
(format nil "~a" (storage:open-text-file "" "snippets" snippet-name))
(format nil "<!-- ~a not found -->" snippet-name)))
(defun insert-dashboard-cat ()
(let ((timestamp (local-time:now)))
(cond ((and (= (local-time:timestamp-day timestamp) 8)
(= (local-time:timestamp-month timestamp) 2))
(format nil "/images/icons/birthday-cat.png"))
((and (>= (local-time:timestamp-hour timestamp) 6)
(< (local-time:timestamp-hour timestamp) 10))
(format nil "/images/icons/morning-cat.png"))
((and (>= (local-time:timestamp-hour timestamp) 10)
(< (local-time:timestamp-hour timestamp) 12))
(format nil "/images/icons/coffee-cat.png"))
((and (>= (local-time:timestamp-hour timestamp) 12)
(< (local-time:timestamp-hour timestamp) 14))
(format nil "/images/icons/dinner-cat.png"))
((and (>= (local-time:timestamp-hour timestamp) 14)
(< (local-time:timestamp-hour timestamp) 18))
(format nil "/images/icons/study-cat.png"))
((and (>= (local-time:timestamp-hour timestamp) 18)
(< (local-time:timestamp-hour timestamp) 20))
(cond
;; Sunday
((= (local-time:timestamp-day-of-week timestamp) 0)
(format nil "/images/icons/love-cat.png"))
;; Monday - Friday
((<= (local-time:timestamp-day-of-week timestamp) 5)
(format nil "/images/icons/workout-cat.png"))
;; Saturday
((= (local-time:timestamp-day-of-week timestamp) 6)
(format nil "/images/icons/rock-star-cat.png"))))
((and (>= (local-time:timestamp-hour timestamp) 20)
(< (local-time:timestamp-hour timestamp) 22))
(format nil "/images/icons/dinner-cat.png"))
((and (>= (local-time:timestamp-hour timestamp) 22)
(< (local-time:timestamp-hour timestamp) 24))
(format nil "/images/icons/bed-time-cat.png"))
(t (format nil "/images/icons/default-cat.png")))))

1849
src/web.lisp

File diff suppressed because it is too large Load Diff

BIN
static/css/archivo/Archivo-Bold.otf

Binary file not shown.

BIN
static/css/archivo/Archivo-BoldItalic.otf

Binary file not shown.

BIN
static/css/archivo/Archivo-Italic.otf

Binary file not shown.

BIN
static/css/archivo/Archivo-Medium.otf

Binary file not shown.

BIN
static/css/archivo/Archivo-MediumItalic.otf

Binary file not shown.

BIN
static/css/archivo/Archivo-Regular.otf

Binary file not shown.

BIN
static/css/archivo/Archivo-SemiBold.otf

Binary file not shown.

BIN
static/css/archivo/Archivo-SemiBoldItalic.otf

Binary file not shown.

294
static/css/full-search.css

@ -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;
}
}

850
static/css/main.css

@ -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) {
}

94
static/css/search.css

@ -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;
}

BIN
static/images/alerts/art-cat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
static/images/alerts/confused-cat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
static/images/alerts/disco-cat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
static/images/alerts/pending-cat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
static/images/alerts/sherlock-cat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
static/images/alerts/success-cat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
static/images/alerts/vomit-cat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
static/images/alerts/workout-cat.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
static/images/icons-1/ai.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
static/images/icons-1/cad.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
static/images/icons-1/css.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
static/images/icons-1/docx.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
static/images/icons-1/gif.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
static/images/icons-1/html.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/images/icons-1/jpg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
static/images/icons-1/mp4.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
static/images/icons-1/png.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
static/images/icons-1/pptx.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
static/images/icons-1/psd.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
static/images/icons-1/rar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
static/images/icons-1/txt.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
static/images/icons-1/xlsx.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
static/images/icons-1/zip.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
static/images/icons/add-circle.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
static/images/icons/add-square.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
static/images/icons/ai.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save