diff --git a/Dockerfile b/.devcontainer/Dockerfile
similarity index 75%
rename from Dockerfile
rename to .devcontainer/Dockerfile
index 2de86ce..7dad5a8 100644
--- a/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,6 +1,14 @@
FROM mcr.microsoft.com/devcontainers/base:buster
+ENV CATALYST_HOME="/workspaces/AccessSystem"
+ENV PATH="${CATALYST_HOME}/local/bin:${PATH}"
+
+WORKDIR /workspaces/AccessSystem
+
+RUN groupadd --gid 1001 swmakers \
+ && useradd --uid 1001 --gid swmakers --shell /bin/bash --create-home swmakers
+
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends \
perl=5.28.1-6+deb10u1 \
@@ -32,6 +40,9 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
libtimedate-perl \
liburi-perl \
libscalar-list-utils-perl \
+ # database support
+ libpq-dev \
+ sqlite3 \
&& cpanm -S Carton \
# for Perl::LanguageServer
&& apt-get -y install --no-install-recommends \
@@ -46,3 +57,12 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
libcoro-perl \
&& cpanm Perl::LanguageServer \
&& rm -rf /var/lib/apt/lists/*
+
+COPY cpanfile* "${CATALYST_HOME}/"
+COPY vendor/ "${CATALYST_HOME}/vendor/"
+
+RUN chown -R swmakers:swmakers "${CATALYST_HOME}"
+
+USER swmakers
+
+RUN carton install --cached
diff --git a/.devcontainer/config/accesssystem_api.conf.dev b/.devcontainer/config/accesssystem_api.conf.dev
new file mode 100644
index 0000000..ddad03e
--- /dev/null
+++ b/.devcontainer/config/accesssystem_api.conf.dev
@@ -0,0 +1,30 @@
+using_frontend-proxy 1
+
+
+ site_key 6LdHZx0TAAAAAMuUdG-NScgCCbJ_HMyL1bdeM9vp
+ secret_key 6LdHZx0TAAAAAEAnQyttqqhDkEVXfUcsnDLPGmvi
+
+
+
+ mailer SMTP
+
+ host mailhog
+ port 1025
+ helo localhost
+ sasl_username info@swindon-makerspace.org
+ sasl_password password
+ debug 1
+
+
+
+
+ subdomain swindon-makerspace
+ domain mock-login:8080
+ public_key
+ private_key
+ callback_url http://access-system/oneall_login_callback
+
+
+ name access_system
+ mac_secret C7B626AC-87A8-4C10-B5CC-D98BED2DBE0B
+
diff --git a/.devcontainer/config/accesssystem_api_local.conf.dev b/.devcontainer/config/accesssystem_api_local.conf.dev
new file mode 100644
index 0000000..b88734c
--- /dev/null
+++ b/.devcontainer/config/accesssystem_api_local.conf.dev
@@ -0,0 +1,10 @@
+
+
+ dsn DBI:SQLite:db/dev.db
+
+
+
+ api-key
+
+base_url http://localhost:3000/
+overlap_days 14
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..bfa2c81
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,71 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose
+{
+ "name": "Swindon Makerspace Access System devcontainer",
+
+ // Update the 'dockerComposeFile' list if you have more compose files or use different names.
+ // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
+ "dockerComposeFile": [
+ "../docker-compose.yaml",
+ "docker-compose.yaml"
+ ],
+
+ // The 'service' property is the name of the service for the container that VS Code should
+ // use. Update this value and .devcontainer/docker-compose.yml to the real service name.
+ "service": "access-system",
+
+ // The optional 'workspaceFolder' property is the path VS Code should open by default when
+ // connected. This is typically a file mount in .devcontainer/docker-compose.yml
+ "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
+
+ // Features to add to the dev container. More info: https://containers.dev/features.
+ "features": {
+ "ghcr.io/devcontainers/features/common-utils:2": {
+ "installZsh": true,
+ "configureZshAsDefaultShell": true,
+ "installOhMyZsh": true,
+ "installOhMyZshConfig": true,
+ "upgradePackages": true,
+ "username": "automatic",
+ "userUid": "automatic",
+ "userGid": "automatic"
+ },
+ "ghcr.io/devcontainers/features/github-cli:1": {
+ "installDirectlyFromGitHubRelease": true,
+ "version": "latest"
+ }
+ },
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ "forwardPorts": [3000, 3001],
+
+ // Uncomment the next line if you want start specific services in your Docker Compose config.
+ // "runServices": [],
+
+ // Uncomment the next line if you want to keep your containers running after VS Code shuts down.
+ // "shutdownAction": "none",
+
+ // Run commands after the container is created.
+ "postCreateCommand": ".devcontainer/init-sqlite -d dev.db",
+ "postStartCommand": ".devcontainer/start-services",
+
+ // Configure tool-specific properties.
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "richterger.perl",
+ "cfgweb.vscode-perl",
+ "tamasfe.even-better-toml",
+ "maattdd.gitless",
+ "EditorConfig.EditorConfig"
+ ]
+ },
+ "jetbrains" : {
+ "settings": {
+ "com.intellij:app:HttpConfigurable.use_proxy_pac": true
+ },
+ "backend" : "IntelliJ"
+ },
+ },
+ "remoteUser": "swmakers"
+}
diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml
new file mode 100644
index 0000000..7204f0d
--- /dev/null
+++ b/.devcontainer/docker-compose.yaml
@@ -0,0 +1,24 @@
+services:
+ access-system:
+ # We use a pre-built image to save time. You can also use a Dockerfile to build your own.
+ # Uncomment if you want to override the service's image with the Dockerfile in the .devcontainer
+ # folder. Note that the path of the Dockerfile and context is relative to the *primary*
+ # docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile"
+ # array). The sample below assumes your primary file is in the root of your project.
+ #
+ # build:
+ # context: .
+ # dockerfile: .devcontainer/Dockerfile
+
+ volumes:
+ # Update this to wherever you want VS Code to mount the folder of your project
+ - ..:/workspaces:cached
+
+ # Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust.
+ # cap_add:
+ # - SYS_PTRACE
+ # security_opt:
+ # - seccomp:unconfined
+
+ # Overrides default command so things don't shut down after the process ends.
+ command: /bin/sh -c "while sleep 1000; do :; done"
diff --git a/.devcontainer/init-config b/.devcontainer/init-config
new file mode 100755
index 0000000..92e1b9b
--- /dev/null
+++ b/.devcontainer/init-config
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# Define the source and destination directories
+src_dir="$(dirname "$0")/config"
+dest_dir="$(dirname "$0")/.."
+
+# Find all .conf.dev files in the source directory
+IFS=$'\n' read -rd '' -a config_files <<< "$(find "$src_dir" -name "*.conf.dev" -exec basename {} \;)"
+
+# Function to copy and rename files
+copy_and_rename() {
+ local src_file="$1"
+ local dest_file="$2"
+
+ if [ ! -f "$dest_file" ]; then
+ cp "$src_file" "$dest_file"
+ echo "Copied $src_file to $dest_file"
+ else
+ echo "File $dest_file already exists, not overwriting"
+ fi
+}
+
+# Process each config file
+for config_file in "${config_files[@]}"; do
+ src_file="$src_dir/$config_file"
+ dest_file="$dest_dir/${config_file%.dev}"
+
+ if [ -f "$src_file" ]; then
+ copy_and_rename "$src_file" "$dest_file"
+ else
+ echo "Source file $src_file does not exist"
+ fi
+done
diff --git a/.devcontainer/init-sqlite b/.devcontainer/init-sqlite
new file mode 100755
index 0000000..9e9bb89
--- /dev/null
+++ b/.devcontainer/init-sqlite
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+# Function to display usage
+usage() {
+ echo "Initialize an SQLite database for development in db/ folder\n"
+ echo "Options:"
+ echo "\t -d , e.g. dev.db"
+ echo "\t -f Force recreate the database file if it exists"
+ echo "\nUsage: $0 -d [-f]"
+ exit 1
+}
+
+force_recreate=false
+
+# Parse command line arguments
+while getopts ":d:f" opt; do
+ case ${opt} in
+ d )
+ db_filename=$OPTARG
+ ;;
+ f )
+ force_recreate=true
+ ;;
+ \? )
+ usage
+ ;;
+ esac
+done
+
+# Check if the db_filename is provided
+if [ -z "$db_filename" ]; then
+ usage
+fi
+
+# Check if the filename ends with .db
+if [ "${db_filename##*.}" != "db" ]; then
+ echo "Error: The database filename must end with .db"
+ usage
+fi
+
+# Create the db folder if it doesn't exist
+db_folder="$(dirname "$0")/../db"
+mkdir -p "$db_folder"
+
+# Create the SQLite database file
+db_path="$db_folder/$db_filename"
+
+# Check if the database file already exists
+if [ -f "$db_path" ]; then
+ if [ "$force_recreate" = true ]; then
+ # Create a backup of the existing database file with the current date and time
+ timestamp=$(date +"%Y-%m-%d_%H:%M")
+ cp "$db_path" "$db_path.$timestamp.bak"
+ echo "Existing database file backed up as $db_filename.$timestamp.bak"
+ else
+ echo "Database file $db_filename already exists. Use -f to force recreate."
+ exit 0
+ fi
+fi
+
+# Create or overwrite the SQLite database file
+sqlite3 "$db_path" "VACUUM;"
+
+# Find the latest SQLite schema file
+sql_folder="$(dirname "$0")/../sql"
+latest_sql_file=$(ls -1t "$sql_folder"/AccessSystem-Schema*SQLite.sql | head -n 1)
+
+# Check if the latest SQL file is found
+if [ -z "$latest_sql_file" ]; then
+ echo "No SQLite schema file found in $sql_folder"
+ exit 1
+fi
+
+# Run the latest SQL schema file on the database
+echo "Initializing database $db_filename with $latest_sql_file...\n"
+sqlite3 "$db_path" -cmd ".echo on" ".read $latest_sql_file"
+
+echo "\nDatabase $db_filename created and initialized with $latest_sql_file"
+
+# use the .devcontainer/seed-sqlite.sql file to populate the database
+seed_sql_file="$(dirname "$0")/seed-sqlite.sql"
+if [ -f "$seed_sql_file" ]; then
+ echo "Seeding database $db_filename with $seed_sql_file...\n"
+ sqlite3 "$db_path" -cmd ".echo on" ".read $seed_sql_file"
+ echo "\nDatabase $db_filename seeded with $seed_sql_file"
+fi
diff --git a/.devcontainer/seed-sqlite.sql b/.devcontainer/seed-sqlite.sql
new file mode 100644
index 0000000..2455ac6
--- /dev/null
+++ b/.devcontainer/seed-sqlite.sql
@@ -0,0 +1,14 @@
+INSERT INTO tiers (id, name, description, price, concessions_allowed, in_use, restrictions)
+VALUES (4, 'Sponsor', 'Access 24hours a day, 365 days a year', 3500, true, true, '{}');
+INSERT INTO tiers (id, name, description, price, concessions_allowed, in_use, restrictions)
+VALUES (3, 'Standard', 'Access 24hours a day, 365 days a year', 2500, true, true, '{}');
+INSERT INTO tiers (id, name, description, price, concessions_allowed, in_use, restrictions)
+VALUES (1, 'MemberOfOtherHackspace',
+ 'Living outside of Swindon Borough and a fully paid up member of another Maker or Hackspace', 500, false, true,
+ '{}');
+INSERT INTO tiers (id, name, description, price, concessions_allowed, in_use, restrictions)
+VALUES (2, 'Weekend', 'Access 12:00am Saturday until 12:00am Monday, and Wednesdays 6:30pm to 11:59pm only', 1500, true,
+ true,
+ '{"times":[{"from":"3:18:00","to":"3:23:59"},{"from":"6:00:01","to":"6:23:59"},{"from":"7:00:01","to":"7:23:59"}]}');
+INSERT INTO tiers (id, name, description, price, concessions_allowed, in_use, restrictions)
+VALUES (5, 'MensShed', 'Members of Renew only, rate now retired', 1000, false, false, '{}');
diff --git a/.devcontainer/start-services b/.devcontainer/start-services
new file mode 100755
index 0000000..3e1d352
--- /dev/null
+++ b/.devcontainer/start-services
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+export PERL5LIB="/workspace/AccessSystem/local/lib/perl5/"
+# export PATH="/workspace/AccessSystem/local/bin:${PATH}"
+
+echo "Initializing service configuration..."
+.devcontainer/init-config
+
+echo "Starting services in the background..."
+
+echo "Starting database admin (RapidApp) @ http://localhost:3001/admin"
+carton exec perl script/accesssystem_daemon_devcontainer.pl start
+
+echo "Starting API server @ http://localhost:3000"
+carton exec perl script/accesssystem_api_daemon_devcontainer.pl start
+
+echo "It may take a few seconds for the services to be up. Please wait..."
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..f33a02c
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,12 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for more information:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+# https://containers.dev/guide/dependabot
+
+version: 2
+updates:
+ - package-ecosystem: "devcontainers"
+ directory: "/"
+ schedule:
+ interval: weekly
diff --git a/.github/workflows/build-dev-image.yaml b/.github/workflows/build-dev-image.yaml
index b0351b5..fa8f4ca 100644
--- a/.github/workflows/build-dev-image.yaml
+++ b/.github/workflows/build-dev-image.yaml
@@ -4,6 +4,8 @@ on:
push:
branches:
- master
+ tags:
+ - 'v*'
pull_request:
branches:
- master
@@ -25,22 +27,51 @@ jobs:
- name: 📦 Checkout code
uses: actions/checkout@v4
- - name: 🏗️ Build image
- run: docker build . --file Dockerfile --tag $IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID},sha=${GITHUB_SHA},ref=${GITHUB_REF}"
-
- - name: 🔐 Log in to registry
- run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login $CONTAINER_REGISTRY -u $ --password-stdin
-
- - name: 💾 Push image
- run: |
- IMAGE_ID=$CONTAINER_REGISTRY/$GITHUB_REPOSITORY_OWNER/$IMAGE_NAME
- VERSION=$(echo "${BRANCH_NAME#refs/heads/}" | sed -e 's/[^a-zA-Z0-9]/-/g') # Get the branch name from the ref and replace non-alphanumeric characters with hyphens
- [ "$VERSION" == "master" ] && VERSION=latest
- echo "🆔 Image ID: $IMAGE_ID"
- echo "#️⃣ GITHUB_SHA: $GITHUB_SHA"
- echo "🎯 Version: $VERSION"
- echo "🚀 Pushing image to registry........................................."
- docker tag $IMAGE_NAME $IMAGE_ID:$GITHUB_SHA
- docker push $IMAGE_ID:$GITHUB_SHA
- docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
- docker push $IMAGE_ID:$VERSION
+ - name: 🧰 Get branch name
+ run: | # Get the branch name from the ref and replace non-alphanumeric characters with hyphens
+ BRANCH=$(echo "${BRANCH_NAME#refs/heads/}" | sed -e 's/[^a-zA-Z0-9]/-/g')
+ echo "BRANCH=$BRANCH" >> $GITHUB_ENV
+
+ - name: 🏷️ Generate Docker metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: |
+ ${{ env.CONTAINER_REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch,priority=610
+ type=ref,event=pr
+ type=semver,pattern={{raw}}
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=semver,pattern={{major}}
+ type=semver,pattern=v{{major}}
+ type=sha,format=long
+ type=raw,value=latest,enable={{is_default_branch}}
+ type=raw,value=${{ env.BRANCH_NAME }},priority=10
+ annotations: |
+ runnumber=${{ github.run_id }}
+ sha=${{ github.sha }}
+ ref=${{ github.ref }}
+
+ - name: 🔐 Log in to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.CONTAINER_REGISTRY }}
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: 🛠️ Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: 🚀 Build and push image
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: .devcontainer/Dockerfile
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ annotations: ${{ steps.meta.outputs.annotations }}
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
diff --git a/.gitignore b/.gitignore
index f8436b9..25baf8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,8 @@
.plx/
.vscode/perl-lang/
+*.code-workspace
+.idea/
# exception for email template assets
!root/img/*.png
diff --git a/README.md b/README.md
index 45c7b9e..57994cf 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ Technologies
* [Catalyst](https://metacpan.org/pod/Catalyst) - Perl Web Framework
-* [RapidApp](https://metacpan.org/pod/RadpiApp) - CRUD built atop Catalyst
+* [RapidApp](https://metacpan.org/pod/RapidApp) - CRUD built atop Catalyst
* [DBIx::Class](https://metacpan.org/pod/DBIx::Class) - Perl ORM
@@ -50,11 +50,11 @@ INSTALL
* Install cpanm, either via [App::cpanminus](https://metacpan.org/pod/App::cpanminus) or wget [cpanm](http://xrl.us/cpanm), make the result executable.
-* Install carton: cpanm -S Carton
+* Install carton: `cpanm -S Carton`
* Checkout this git repo, cd into the repo directory.
-* Install Perl dependencies for this system: carton install --cached
+* Install Perl dependencies for this system: `carton install --cached`
SETUP
-----
diff --git a/cpanfile b/cpanfile
index 72579d6..58f0f45 100644
--- a/cpanfile
+++ b/cpanfile
@@ -21,7 +21,6 @@ requires 'Template::Stash::XS';
requires 'HTML::FormHandlerX::Field::noCAPTCHA';
requires 'Daemon::Control';
requires 'Catalyst::View::JSON';
-requires 'Daemon::Control';
requires 'LWP::UserAgent';
requires 'LWP::Protocol::https';
requires 'JSON';
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000..1500f88
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,8 @@
+services:
+ access-system:
+ image: ghcr.io/swindonmakers/access-system-dev:latest
+
+ mailhog:
+ image: mailhog/mailhog
+ ports:
+ - "8025:8025"
diff --git a/root/src/login.tt b/root/src/login.tt
index c17f2f7..d614adf 100644
--- a/root/src/login.tt
+++ b/root/src/login.tt
@@ -2,12 +2,12 @@
diff --git a/script/accesssystem_api_daemon_devcontainer.pl b/script/accesssystem_api_daemon_devcontainer.pl
new file mode 100755
index 0000000..dcbb8d5
--- /dev/null
+++ b/script/accesssystem_api_daemon_devcontainer.pl
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+use local::lib '/workspaces/AccessSystem/local/lib/perl5/';
+use Daemon::Control;
+my $path = "/workspaces/AccessSystem";
+
+exit Daemon::Control->new(
+ name => "AccessSystem-API",
+ lsb_start => '$syslog $remote_fs',
+ lsb_stop => '$syslog',
+ lsb_sdesc => 'AccessSystem API',
+ lsb_desc => 'AccessSystem API controls the AccessSystem API daemon.',
+ path => "$path/script/accesssystem_api_daemon_devcontainer.pl",
+ directory => "$path",
+# init_config => "$path etc/environment",
+ user => 'swmakers',
+ group => 'swmakers',
+ program => "carton exec $path/script/accesssystem_api_server.pl --restart --port 3000 > /proc/1/fd/1 2> /proc/1/fd/2",
+
+ pid_file => '/tmp/accesssystem_api.pid',
+ stderr_file => '/proc/1/fd/2',
+ stdout_file => '/proc/1/fd/1',
+
+ fork => 2,
+
+ )->run;
diff --git a/script/accesssystem_daemon_devcontainer.pl b/script/accesssystem_daemon_devcontainer.pl
new file mode 100755
index 0000000..d83d1b3
--- /dev/null
+++ b/script/accesssystem_daemon_devcontainer.pl
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+use local::lib '/workspaces/AccessSystem/local/lib/perl5/';
+use Daemon::Control;
+my $path = "/workspaces/AccessSystem";
+
+exit Daemon::Control->new(
+ name => "AccessSystem-CRUD",
+ lsb_start => '$syslog $remote_fs',
+ lsb_stop => '$syslog',
+ lsb_sdesc => 'AccessSystem CRUD',
+ lsb_desc => 'AccessSystem CRUD controls the AccessSystem CRUD daemon.',
+ path => "$path/script/accesssystem_daemon_devcontainer.pl",
+ directory => "$path",
+# init_config => "$path etc/environment",
+ user => 'swmakers',
+ group => 'swmakers',
+ program => "carton exec $path/script/accesssystem_server.pl --restart --port 3001 > /proc/1/fd/1 2> /proc/1/fd/2",
+
+ pid_file => '/tmp/accesssystem_crud.pid',
+ stderr_file => '/proc/1/fd/2',
+ stdout_file => '/proc/1/fd/1',
+
+ fork => 2,
+
+ )->run;