diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 6c9be79..f260605 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,53 +1,88 @@ -# Use the official Dev Container base image for Debian Bookworm +# Use the official Dev Container base image for Debian Trixie # We use this instead of plain Debian to get a better development experience out of the box # as it includes common tools and configurations for development. -FROM mcr.microsoft.com/devcontainers/base:bookworm AS base +FROM mcr.microsoft.com/devcontainers/base:trixie AS base LABEL org.opencontainers.image.description="Development Container for Swindon Makerspace Access System" # libparse-debianchangelog-perl \ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends \ - perl \ - cpanminus \ - build-essential \ - libalgorithm-diff-perl \ - libalgorithm-diff-xs-perl \ - libalgorithm-merge-perl \ - libcgi-fast-perl \ - libcgi-pm-perl \ - libclass-accessor-perl \ - libclass-isa-perl \ - libencode-locale-perl \ - libfcgi-perl \ - libfile-fcntllock-perl \ - libhtml-parser-perl \ - libhtml-tagset-perl \ - libhttp-date-perl \ - libhttp-message-perl \ - libio-html-perl \ - libio-string-perl \ - liblocale-gettext-perl \ - liblwp-mediatypes-perl \ - libsub-name-perl \ - libswitch-perl \ - libtext-charwidth-perl \ - libtext-iconv-perl \ - libtext-wrapi18n-perl \ - libtimedate-perl \ - liburi-perl \ - libscalar-list-utils-perl \ - && cpanm -S Carton \ -# for Perl::LanguageServer + perl \ + cpanminus \ + build-essential \ + # Database drivers + libdbd-sqlite3-perl \ + libdbd-pg-perl \ + libpq-dev \ + # Libraries for CPAN XS modules + libexpat1-dev \ + libxml2-dev \ + zlib1g-dev \ + # Core Perl modules from apt + libalgorithm-diff-perl \ + libalgorithm-diff-xs-perl \ + libalgorithm-merge-perl \ + libcgi-fast-perl \ + libcgi-pm-perl \ + libclass-accessor-perl \ + libclass-isa-perl \ + libencode-locale-perl \ + libfcgi-perl \ + libfile-fcntllock-perl \ + libhtml-parser-perl \ + libhtml-tagset-perl \ + libhttp-date-perl \ + libhttp-message-perl \ + libio-html-perl \ + libio-string-perl \ + liblocale-gettext-perl \ + liblwp-mediatypes-perl \ + libsub-name-perl \ + libswitch-perl \ + libtext-charwidth-perl \ + libtext-iconv-perl \ + libtext-wrapi18n-perl \ + libtimedate-perl \ + liburi-perl \ + libscalar-list-utils-perl \ + libmoose-perl \ + libjson-perl \ + libdata-dump-perl \ + libtry-tiny-perl \ + libdatetime-perl \ + libpath-class-perl \ + libplack-perl \ + libxml-parser-perl \ + libcrypt-des-perl \ + libssl-dev \ + libio-socket-ssl-perl \ + libnet-ssleay-perl \ + ca-certificates \ + && cpanm -n Carton \ + # for Perl::LanguageServer && apt-get -y install --no-install-recommends \ - libanyevent-perl \ - libclass-refresh-perl \ - libdata-dump-perl \ - libio-aio-perl \ - libjson-perl \ - libmoose-perl \ - libpadwalker-perl \ - libscalar-list-utils-perl \ - libcoro-perl \ + libanyevent-perl \ + libclass-refresh-perl \ + libio-aio-perl \ + libpadwalker-perl \ + libcoro-perl \ && cpanm Perl::LanguageServer \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* /root/.cpanm + +# ============================================================================= +# DEPS STAGE - Install CPAN dependencies via Carton +# ============================================================================= +FROM base AS deps + +WORKDIR /workspace + +# Copy dependency files first (for better layer caching) +COPY cpanfile cpanfile.snapshot ./ + +# Create vendor directory structure (for cached install) +COPY vendor/ vendor/ + +# Install dependencies using cached mode +RUN carton install --cached \ + && rm -rf /root/.cpanm diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0b35f24 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,54 @@ +# Git +.git/ +.gitignore +.github/ + +# Development files +.devcontainer/ +.vscode/ +.idea/ +*.code-workspace + +# Database files (should not be in image) +db/ +*.db +*.db.bak +rapidapp_coreschema.db + +# Documentation +docs/ +*.md +NOTES.txt +Changes + +# Config backups and local configs (mount at runtime) +config.bkp/ +accesssystem_api.conf +accesssystem_api_local.conf +accesssystem_dev.conf +accesssystem.conf +keys.conf + +# Keep example configs +!*.conf.example + +# Build artifacts +Makefile +META.yml +MYMETA.* +pm_to_blib +blib/ +inc/ + +# Editor/OS files +*~ +*.bak +.DS_Store +*.swp + +# OFX bank files +ofx/ +*.ofx + +# Test output +test_db.db diff --git a/.github/workflows/build-dev-image.yaml b/.github/workflows/build-dev-image.yaml index 44b4cac..bf4526b 100644 --- a/.github/workflows/build-dev-image.yaml +++ b/.github/workflows/build-dev-image.yaml @@ -70,7 +70,7 @@ jobs: with: context: . file: .devcontainer/Dockerfile - target: base + target: deps push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..9f58a18 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,99 @@ +name: CI - Test and Build + +on: + push: + branches: + - master + tags: + - 'v*' + pull_request: + branches: + - master + workflow_dispatch: + +env: + CONTAINER_REGISTRY: ghcr.io + IMAGE_NAME: access-system + +jobs: + test: + name: ๐Ÿงช Run Tests + runs-on: ubuntu-latest + + steps: + - name: ๐Ÿ“ฆ Checkout code + uses: actions/checkout@v6 + + - name: ๐Ÿ› ๏ธ Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: ๐Ÿ—๏ธ Build test image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + target: test + load: true + tags: accesssystem-test:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: ๐Ÿงช Run tests + run: | + docker run --rm accesssystem-test:latest + + build-production: + name: ๐Ÿš€ Build Production Image + runs-on: ubuntu-latest + needs: test + if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/')) + permissions: + contents: read + packages: write + + steps: + - name: ๐Ÿ“ฆ Checkout code + uses: actions/checkout@v6 + + - 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=semver,pattern={{raw}} + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha,format=long + type=raw,value=latest,enable={{is_default_branch}} + annotations: | + runnumber=${{ github.run_id }} + sha=${{ github.sha }} + ref=${{ github.ref }} + org.opencontainers.image.description="Production Container for Swindon Makerspace Access System" + + - 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 production image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + target: production + 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/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bc2c849 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,135 @@ +# Multi-stage Dockerfile for AccessSystem +# Stages: base -> deps -> test -> production + +# ============================================================================= +# BASE STAGE - System dependencies and Perl packages from apt +# ============================================================================= +FROM debian:trixie-slim AS base + +LABEL org.opencontainers.image.description="AccessSystem - Swindon Makerspace Access Control" + +# Install system Perl and essential apt packages +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends \ + perl \ + cpanminus \ + build-essential \ + # Database drivers + libdbd-sqlite3-perl \ + libdbd-pg-perl \ + libpq-dev \ + # Libraries for CPAN XS modules + libexpat1-dev \ + libxml2-dev \ + zlib1g-dev \ + # Core Perl modules from apt (faster than CPAN) + libalgorithm-diff-perl \ + libalgorithm-diff-xs-perl \ + libalgorithm-merge-perl \ + libcgi-fast-perl \ + libcgi-pm-perl \ + libclass-accessor-perl \ + libencode-locale-perl \ + libfcgi-perl \ + libfile-fcntllock-perl \ + libhtml-parser-perl \ + libhtml-tagset-perl \ + libhttp-date-perl \ + libhttp-message-perl \ + libio-html-perl \ + libio-string-perl \ + liblocale-gettext-perl \ + liblwp-mediatypes-perl \ + libsub-name-perl \ + libtext-charwidth-perl \ + libtext-iconv-perl \ + libtext-wrapi18n-perl \ + libtimedate-perl \ + liburi-perl \ + libscalar-list-utils-perl \ + libmoose-perl \ + libjson-perl \ + libdata-dump-perl \ + libtry-tiny-perl \ + libdatetime-perl \ + libpath-class-perl \ + libplack-perl \ + # XML::Parser from apt (avoids build issues) + libxml-parser-perl \ + # Crypt::DES from apt (fails to build from CPAN, needed by RapidApp) + libcrypt-des-perl \ + # SSL/TLS support + libssl-dev \ + libio-socket-ssl-perl \ + libnet-ssleay-perl \ + ca-certificates \ + # For Carton + && cpanm -n Carton \ + && rm -rf /var/lib/apt/lists/* /root/.cpanm + +WORKDIR /app + +# ============================================================================= +# DEPS STAGE - Install CPAN dependencies via Carton +# ============================================================================= +FROM base AS deps + +# Copy dependency files first (for better layer caching) +COPY cpanfile cpanfile.snapshot ./ + +# Create vendor directory structure (for cached install) +COPY vendor/ vendor/ + +# Install dependencies using cached mode as per README +RUN carton install --cached \ + && rm -rf /root/.cpanm + +# ============================================================================= +# TEST STAGE - For running tests +# ============================================================================= +FROM deps AS test + +# Copy application code +COPY lib/ lib/ +COPY t/ t/ +COPY root/ root/ +COPY script/ script/ +COPY sql/ sql/ + +# Copy test and example configs - test config used as local override +COPY accesssystem_api.conf.example accesssystem_api.conf +COPY accesssystem_api_test.conf accesssystem_api_local.conf + +# Copy psgi files +COPY app.psgi accesssystem.psgi ./ + +# Set environment for tests +ENV CATALYST_HOME=/app +ENV PERL5LIB=/app/local/lib/perl5:/app/lib + +# Default command runs tests +CMD ["carton", "exec", "prove", "-I", "lib", "-I", "t/lib", "-r", "t/"] + +# ============================================================================= +# PRODUCTION STAGE - Slim production image +# ============================================================================= +FROM deps AS production + +# Copy only what's needed for production +COPY lib/ lib/ +COPY root/ root/ +COPY script/ script/ +COPY app.psgi accesssystem.psgi ./ + +# Config files should be mounted at runtime +# COPY accesssystem_api.conf accesssystem_api_local.conf ./ + +# Set environment +ENV CATALYST_HOME=/app +ENV PERL5LIB=/app/local/lib/perl5:/app/lib + +# Expose default Catalyst port +EXPOSE 3000 + +# Default command runs the API server +CMD ["carton", "exec", "perl", "script/accesssystem_api_server.pl", "--port", "3000", "--host", "0.0.0.0"] diff --git a/accesssystem_api_test.conf b/accesssystem_api_test.conf new file mode 100644 index 0000000..45e060c --- /dev/null +++ b/accesssystem_api_test.conf @@ -0,0 +1,53 @@ +# Test configuration for AccessSystem API +# Used when running tests in CI or locally + + + + dsn dbi:SQLite:test_db.db + + + +# Dummy reCAPTCHA keys (will pass validation in non-production mode) + + site_key 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI + secret_key 6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe + + +# Disable debug in test mode + + ignore_extensions [] + + +# Test email settings - don't actually send + + stash_key email + + content_type text/plain + charset utf-8 + + + +# Dummy OneAll settings + + subdomain test + domain test.api.oneall.com + public_key test-public-key + private_key test-private-key + + +# Cookie settings + + name access_system_test + mac_secret test-cookie-secret-for-testing-only + + + + namespace accesssystem + + +# Dummy Sendinblue/Brevo settings + + api-key dummy-test-api-key + + +base_url http://localhost:3000/accesssystem/ diff --git a/lib/AccessSystem/Schema/Result/Person.pm b/lib/AccessSystem/Schema/Result/Person.pm index 80a0783..f64a8b4 100644 --- a/lib/AccessSystem/Schema/Result/Person.pm +++ b/lib/AccessSystem/Schema/Result/Person.pm @@ -265,12 +265,16 @@ sub is_valid { my $is_paid; - if(!$self->parent) { + # Check parent_id column directly to avoid relationship resolution issues + # on newly created objects that haven't been stored yet + my $parent_id = $self->get_column('parent_id'); + if(!$parent_id) { $is_paid = $self->payments_rs->search({ paid_on_date => { '<=' => $date_str }, expires_on_date => { '>=' => $date_str }, })->count; } else { + # Only call parent relationship if we have a parent_id return $self->parent->is_valid; } @@ -305,7 +309,8 @@ sub bank_ref { sub normal_dues { my ($self) = @_; - return 0 if $self->parent; + # Check parent_id column directly to avoid relationship resolution issues + return 0 if $self->get_column('parent_id'); if ($self->is_donor) { return 0; @@ -940,7 +945,8 @@ sub update_door_access { # This entry should exist, but covid policy may have removed it.. my $door = $self->result_source->schema->the_door(); - my $door_allowed = $self->allowed->find_or_create({ tool_id => $door->id }); + # is_admin required: allowed.is_admin has a NOT NULL constraint + my $door_allowed = $self->allowed->find_or_create({ tool_id => $door->id, is_admin => 0 }); $door_allowed->update({ pending_acceptance => 'false', accepted_on => DateTime->now()}); } diff --git a/t/01app.t b/t/01app.t index a824f04..25773a1 100644 --- a/t/01app.t +++ b/t/01app.t @@ -2,9 +2,32 @@ use strict; use warnings; use Test::More; +use Cwd qw(getcwd); + +# Set CATALYST_HOME so config is loaded correctly +$ENV{CATALYST_HOME} = getcwd(); + +use lib 't/lib'; +use AccessSystem::Schema; +use AccessSystem::Fixtures; + +# Use the same database that the config specifies +# The test config uses dbi:SQLite:test_db.db +my $testdb = 'test_db.db'; +unlink $testdb if -e $testdb; # Start fresh + +# Deploy schema and create fixtures +my $schema = AccessSystem::Schema->connect("dbi:SQLite:$testdb"); +$schema->deploy(); +AccessSystem::Fixtures::create_tiers($schema); use Catalyst::Test 'AccessSystem::API'; -ok( request('/register')->is_success, 'Request should succeed' ); +# Test that the app loads and responds to requests +ok( request('/login')->is_success, 'Request to /login should succeed' ); +ok( request('/register')->is_success, 'Request to /register should succeed' ); + +# Clean up +unlink($testdb); done_testing(); diff --git a/t/ResultSetPerson.t b/t/ResultSetPerson.t index 198b5ce..e1347cd 100644 --- a/t/ResultSetPerson.t +++ b/t/ResultSetPerson.t @@ -82,8 +82,8 @@ my $schema = AccessSystem::Schema->connect("dbi:SQLite:$testdb"); my $comms_count = 0; my $testee = AccessSystem::Fixtures::create_person($schema, payment => $payment_amount); $testee->create_related('tokens', { id => '12345678', type => 'test token' }); - # The Door so that Result::Person::update_door_access works - my $thing = $schema->resultset('Tool')->create({ name => 'The Door', assigned_ip => '10.0.0.1', requires_induction => 1, team => 'Who knows' }); + # The Door so that Result::Person::update_door_access works (fixtures may have already created it) + my $thing = $schema->resultset('Tool')->find_or_create({ name => 'The Door', assigned_ip => '10.0.0.1', requires_induction => 1, team => 'Who knows' }); my $allowed = $testee->create_related('allowed', { tool => $thing, is_admin => 0}); $allowed->discard_changes(); $allowed->update({ pending_acceptance => 0 }); @@ -233,4 +233,3 @@ my $schema = AccessSystem::Schema->connect("dbi:SQLite:$testdb"); done_testing; - diff --git a/t/lib/AccessSystem/Fixtures.pm b/t/lib/AccessSystem/Fixtures.pm new file mode 100644 index 0000000..4b358f2 --- /dev/null +++ b/t/lib/AccessSystem/Fixtures.pm @@ -0,0 +1,183 @@ +package AccessSystem::Fixtures; + +use strict; +use warnings; + +use DateTime; + +=head1 NAME + +AccessSystem::Fixtures - Test fixture helpers for AccessSystem tests + +=head1 SYNOPSIS + + use lib 't/lib'; + use AccessSystem::Fixtures; + + my $schema = AccessSystem::Schema->connect("dbi:SQLite:test.db"); + $schema->deploy(); + + # Create membership tiers + AccessSystem::Fixtures::create_tiers($schema); + + # Create a test person + my $person = AccessSystem::Fixtures::create_person($schema, + name => 'Test User', + dob => '1990-01', + ); + +=head1 DESCRIPTION + +Test fixture helpers for unit tests. Provides functions to create +test data in the database. + +=cut + +my $person_counter = 0; + +=head2 create_tiers($schema) + +Create the standard membership tiers used for testing. + +=cut + +sub create_tiers { + my ($schema) = @_; + + my @tiers = ( + { + id => 1, + name => 'Other Hackspace', + description => 'Member of another hackspace/makerspace', + price => 500, # ยฃ5 + concessions_allowed => 0, + in_use => 1, + restrictions => '{}', + }, + { + id => 2, + name => 'Standard', + description => 'Standard full membership', + price => 2500, # ยฃ25 + concessions_allowed => 1, + in_use => 1, + restrictions => '{}', + }, + { + id => 3, + name => 'Student', + description => 'Student membership (requires proof)', + price => 1250, # ยฃ12.50 + concessions_allowed => 0, + in_use => 1, + restrictions => '{}', + }, + { + id => 4, + name => 'Weekend', + description => 'Weekend access only', + price => 1500, # ยฃ15 + concessions_allowed => 1, + in_use => 1, + restrictions => '{"times":[{"from":"6:00:00","to":"7:23:59"}]}', + }, + { + id => 5, + name => "Men's Shed", + description => "Men's Shed membership", + price => 1000, # ยฃ10 + concessions_allowed => 0, + in_use => 1, + restrictions => '{}', + }, + { + id => 6, + name => 'Donation', + description => 'Donor only membership (no access)', + price => 0, + concessions_allowed => 0, + in_use => 1, + restrictions => '{}', + }, + ); + + for my $tier_data (@tiers) { + $schema->resultset('Tier')->update_or_create($tier_data); + } + + # Create 'The Door' tool - required by update_door_access() + $schema->resultset('Tool')->update_or_create({ + id => 1, + name => 'The Door', + assigned_ip => '10.0.0.1', + requires_induction => 0, + team => 'Everyone', + }); + + return; +} + +=head2 create_person($schema, %args) + +Create a test person. Returns the Person result object. + +Accepts optional arguments: + - name: Person name (defaults to 'Test Person N') + - email: Email address (defaults to 'test{N}@example.com') + - dob: Date of birth as 'YYYY-MM' (defaults to '1980-01') + - address: Address (defaults to '123 Test Street') + - c_rate: Concessionary rate override (e.g., 'student', 'legacy') + - tier_id: Tier ID (defaults to 2 = Standard) + - payment: Payment override (in pence) + - member_of_other_hackspace: Boolean (defaults to 0) + +=cut + +sub create_person { + my ($schema, %args) = @_; + + $person_counter++; + + my $person_data = { + name => $args{name} // "Test Person $person_counter", + email => $args{email} // "test$person_counter\@example.com", + dob => $args{dob} // '1980-01', + address => $args{address} // '123 Test Street, Testville, TE5 7ST', + tier_id => $args{tier_id} // 2, # Default to Standard tier + }; + + # Handle concessionary rate override + if (defined $args{c_rate}) { + $person_data->{concessionary_rate_override} = $args{c_rate}; + } + + # Handle payment override + if (defined $args{payment}) { + $person_data->{payment_override} = $args{payment}; + } + + my $person = $schema->resultset('Person')->create($person_data); + + return $person; +} + +=head2 reset_counter() + +Reset the person counter. Useful between test files. + +=cut + +sub reset_counter { + $person_counter = 0; + return; +} + +1; + +__END__ + +=head1 AUTHOR + +AccessSystem test fixtures + +=cut diff --git a/test-deploy/README.md b/test-deploy/README.md new file mode 100644 index 0000000..032adf7 --- /dev/null +++ b/test-deploy/README.md @@ -0,0 +1,32 @@ +# Local Production Test Environment + +This directory contains configuration and scripts to run the production Docker container locally with a full PostgreSQL database. + +## ๐Ÿš€ Quick Start + +1. **Run the start script:** + ```bash + ./start.sh + ``` + This will: + - Build the production image + - Start containers (App + Postgres 17) + - Deploy the database schema + - Seed test data (Tiers + "The Door") + +2. **Access the App:** + - [http://localhost:3000/login](http://localhost:3000/login) + - [http://localhost:3000/register](http://localhost:3000/register) + +## ๐Ÿ›‘ Stop & Cleanup + +```bash +docker compose down -v +``` + +## ๐Ÿ“‚ Structure + +- **`docker-compose.yaml`**: Orchestrates valid Prod container + Postgres DB. +- **`config/accesssystem_api_local.conf`**: Test-specific config (connects to local DB, dummy keys). +- **`seed_data.sql`**: Initial data needed for the app to function (Membership Tiers, Tools). +- **`start.sh`**: Automates build, deploy, and seed steps. diff --git a/test-deploy/docker-compose.yaml b/test-deploy/docker-compose.yaml new file mode 100644 index 0000000..e725cff --- /dev/null +++ b/test-deploy/docker-compose.yaml @@ -0,0 +1,34 @@ +services: + db: + image: postgres:17 + environment: + POSTGRES_USER: access + POSTGRES_PASSWORD: accesstest + POSTGRES_DB: accesssystem + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U access -d accesssystem" ] + interval: 5s + timeout: 5s + retries: 5 + + app: + build: + context: .. + dockerfile: Dockerfile + target: production + ports: + - "3000:3000" + environment: + CATALYST_HOME: /app + volumes: + - ./config/accesssystem_api_local.conf:/app/accesssystem_api_local.conf:ro + - ../accesssystem_api.conf.example:/app/accesssystem_api.conf:ro + depends_on: + db: + condition: service_healthy + command: [ "carton", "exec", "perl", "script/accesssystem_api_server.pl", "--port", "3000", "--host", "0.0.0.0" ] + +volumes: + postgres_data: diff --git a/test-deploy/seed_data.sql b/test-deploy/seed_data.sql new file mode 100644 index 0000000..b2d1c91 --- /dev/null +++ b/test-deploy/seed_data.sql @@ -0,0 +1,10 @@ +INSERT INTO tiers (id, name, description, price, concessions_allowed, in_use, restrictions) VALUES +(1, 'Other Hackspace', 'Member of another hackspace/makerspace', 500, false, true, '{}'), +(2, 'Standard', 'Standard full membership', 2500, true, true, '{}'), +(3, 'Student', 'Student membership (requires proof)', 1250, false, true, '{}'), +(4, 'Weekend', 'Weekend access only', 1500, true, true, '{"times":[{"from":"6:00:00","to":"7:23:59"}]}'), +(5, 'Men''s Shed', 'Men''s Shed membership', 1000, false, true, '{}'), +(6, 'Donation', 'Donor only membership (no access)', 0, false, true, '{}'); + +INSERT INTO tools (id, name, assigned_ip, requires_induction, team) VALUES +('09637E38-F469-11F0-A94B-FD08D99F0D81', 'The Door', '10.0.0.1', false, 'Everyone'); diff --git a/test-deploy/start.sh b/test-deploy/start.sh new file mode 100755 index 0000000..b63c572 --- /dev/null +++ b/test-deploy/start.sh @@ -0,0 +1,103 @@ +#!/bin/bash +set -e + +# Ensure we're in the right directory +cd "$(dirname "$0")" + +# Check for required tools +if ! command -v docker &> /dev/null; then + echo "โŒ Error: 'docker' is not installed or not in PATH." + exit 1 +fi + +if ! docker compose version &> /dev/null; then + echo "โŒ Error: 'docker compose' is not available." + echo " Ensure you have a recent version of Docker Desktop installed." + exit 1 +fi + +echo "๐Ÿš€ Starting Production Container Test Environment..." + +# Create config if it doesn't exist +if [ ! -f config/accesssystem_api_local.conf ]; then + echo "โš™๏ธ Creating default test configuration..." + mkdir -p config + cat > config/accesssystem_api_local.conf < + + dsn dbi:Pg:dbname=accesssystem;host=db + user access + password accesstest + + + +# Dummy reCAPTCHA keys (Google test keys) + + site_key 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI + secret_key 6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe + + +# Dummy OneAll settings + + subdomain test + domain test.api.oneall.com + public_key test-public-key + private_key test-private-key + + +# Cookie settings + + name access_system_test + mac_secret docker-test-cookie-secret + + + + namespace accesssystem + + +# Dummy Sendinblue/Brevo settings + + api-key dummy-test-api-key + + +base_url http://localhost:3000/accesssystem/ +EOL +fi + +# Build (using parent context) +echo "๐Ÿ“ฆ Building production image..." +docker build --target production -t accesssystem:latest .. + +# Start containers +echo "๐Ÿ”„ Starting containers..." +docker compose up -d + +# Wait for DB +# Wait for DB to be healthy +echo "โณ Waiting for Database to be ready..." +RETRIES=30 +until docker compose exec -T db pg_isready -U access -d accesssystem > /dev/null 2>&1; do + ((RETRIES--)) + if [ $RETRIES -le 0 ]; then + echo "โŒ Database failed to start in time." + exit 1 + fi + echo "zzz... waiting for database ($RETRIES retries left)" + sleep 2 +done +echo "โœ… Database is up!" + +# Deploy Schema +echo "๐Ÿ“œ Deploying Schema (v18.0 PostgreSQL)..." +docker compose exec -T db psql -U access -d accesssystem < ../sql/AccessSystem-Schema-18.0-PostgreSQL.sql + +# Seed Data +echo "๐ŸŒฑ Seeding Data..." +docker compose exec -T db psql -U access -d accesssystem < seed_data.sql + +echo "โœ… Environment Ready!" +echo "โžก๏ธ Login: http://localhost:3000/login" +echo "โžก๏ธ Register: http://localhost:3000/register" +echo "" +echo "To stop: docker compose down -v"