Billetterie: - Partial refund support (STATUS_PARTIALLY_REFUNDED, refundedAmount field, migration) - Race condition fix: PESSIMISTIC_WRITE lock on stock decrement in transaction - Idempotency key on PaymentIntent::create, reuse existing PI if stripeSessionId set - Disable checkout when event ended (server 400 + template hide) - Webhook deduplication via cache (24h TTL on stripe event.id) - Email validation (filter_var) in OrderController guest flow - JSON cart validation (structure check before processing) - Invitation expiration after 7 days (isExpired method + landing page message) - Stripe Checkout fallback when JS fails to load (noscript + redirect) Config externalization: - Move Stripe fees (STRIPE_FEE_RATE, STRIPE_FEE_FIXED) and admin email (ADMIN_EMAIL) to .env/services.yaml - Replace all hardcoded contact@e-cosplay.fr across 13 files - MailerService: getAdminEmail()/getAdminFrom(), default $from=null resolves to admin UX & Accessibility: - ARIA tabs: role=tablist/tab/tabpanel, aria-selected, keyboard nav (arrows, Home, End) - aria-label on cart +/- buttons and editor toolbar buttons - tabindex=0 on editor toolbar buttons for keyboard access - data-confirm handler in app.js (was only in admin.js) - Cart error feedback on checkout failure - Billet designer save feedback (loading/success/error states) - Stock polling every 30s with rupture/low stock badges - Back to event link on payment page Security: - HTML sanitizer: BLOCKED_TAGS list (script, style, iframe, svg, etc.) - content fully removed - Stripe polling timeout (15s max) with fallback redirect - Rate limiting on public order access (20/5min) - .catch() on all fetch() calls (sortable, billet-designer) Tests (92% PHP, 100% JS lines): - PCOV added to dev Dockerfile - Test DB setup: .env.test with DATABASE_URL, Redis auth, Meilisearch key - Rate limiter disabled in test env - Makefile: test_db_setup, test_db_reset, run_test_php, run_test_coverage_php/js - New tests: InvitationFlowTest (21), AuditServiceTest (4), ExportServiceTest (9), InvoiceServiceTest (4) - New tests: SuspendedUserSubscriberTest, RateLimiterSubscriberTest, MeilisearchServiceTest - New tests: Stripe webhook payment_failed (6) + charge.refunded (6) - New tests: BilletBuyer refund, User suspended, OrganizerInvitation expiration - JS tests: stock polling (6), data-confirm (2), copy-url restore (1), editor ARIA (2), XSS (9), tabs keyboard (9) - ESLint + PHP CS Fixer: 0 errors - SonarQube exclusions aligned with vitest coverage config Infra: - Meilisearch consistency command (app:meilisearch:check-consistency --fix) + cron daily 3am - MeilisearchService: getAllDocumentIds(), listIndexes() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
226 lines
5.7 KiB
YAML
226 lines
5.7 KiB
YAML
---
|
|
# --- Server deployment ---
|
|
- name: Deploy e-ticket to production
|
|
hosts: production
|
|
become: true
|
|
vars_files:
|
|
- vault.yml
|
|
|
|
pre_tasks:
|
|
- name: Enable maintenance mode
|
|
command: make maintenance_on
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
|
|
tasks:
|
|
- name: Deploy .env.local
|
|
template:
|
|
src: env.local.j2
|
|
dest: /var/www/e-ticket/.env.local
|
|
owner: bot
|
|
group: bot
|
|
mode: "0600"
|
|
|
|
- name: Ensure cert directory exists
|
|
file:
|
|
path: /var/www/e-ticket/config/cert
|
|
state: directory
|
|
owner: bot
|
|
group: bot
|
|
mode: "0700"
|
|
|
|
- name: Deploy S/MIME private key
|
|
copy:
|
|
content: "{{ smime_private_key }}"
|
|
dest: /var/www/e-ticket/config/cert/private-key.pem
|
|
owner: bot
|
|
group: bot
|
|
mode: "0600"
|
|
|
|
- name: Pull latest code
|
|
command: git pull origin master
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
become_user: bot
|
|
|
|
- name: Deploy PgBouncer config
|
|
template:
|
|
src: pgbouncer.ini.j2
|
|
dest: /var/www/e-ticket/docker/pgsql/pgbouncer.ini
|
|
owner: bot
|
|
group: bot
|
|
mode: "0644"
|
|
|
|
- name: Deploy PgBouncer userlist
|
|
template:
|
|
src: userlist.txt.j2
|
|
dest: /var/www/e-ticket/docker/pgsql/userlist.txt
|
|
owner: bot
|
|
group: bot
|
|
mode: "0644"
|
|
|
|
- name: Deploy docker-compose-prod.yml
|
|
template:
|
|
src: docker-compose-prod.yml.j2
|
|
dest: /var/www/e-ticket/docker-compose-prod.yml
|
|
owner: bot
|
|
group: bot
|
|
mode: "0600"
|
|
|
|
- name: Build Docker images
|
|
command: make build_prod
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
|
|
- name: Pull Docker images
|
|
command: make pull_prod
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
|
|
- name: Stop production containers
|
|
command: make stop_prod
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
|
|
- name: Start production containers
|
|
command: make start_prod
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
|
|
- name: Install PHP dependencies
|
|
command: composer install --no-dev --optimize-autoloader
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
become_user: bot
|
|
|
|
- name: Install JS dependencies
|
|
command: bun install
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
become_user: bot
|
|
|
|
- name: Build assets
|
|
command: bun run build
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
become_user: bot
|
|
|
|
- name: Wait for database to be ready
|
|
shell: |
|
|
for i in $(seq 1 30); do
|
|
docker compose -f docker-compose-prod.yml exec -T php php -r "new PDO('pgsql:host=pgbouncer;port=6432;dbname=e-ticket','e-ticket','{{ db_password }}');" 2>/dev/null && exit 0
|
|
sleep 1
|
|
done
|
|
exit 1
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
|
|
- name: Run migrations
|
|
command: make migrate_prod
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
|
|
- name: Clear cache
|
|
command: make clear_prod
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
|
|
- name: Compile PWA assets
|
|
command: make pwa_prod
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
|
|
- name: Ensure uploads directories exist with correct permissions
|
|
file:
|
|
path: "/var/www/e-ticket/public/uploads/{{ item }}"
|
|
state: directory
|
|
owner: "1000"
|
|
group: "1000"
|
|
mode: "0755"
|
|
recurse: true
|
|
loop:
|
|
- logos
|
|
|
|
- name: Ensure var/payouts directory exists
|
|
file:
|
|
path: /var/www/e-ticket/var/payouts
|
|
state: directory
|
|
owner: "1000"
|
|
group: "1000"
|
|
mode: "0755"
|
|
|
|
- name: Ensure Caddy sites directory exists
|
|
file:
|
|
path: /etc/caddy/sites
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- name: Deploy Caddy config
|
|
template:
|
|
src: caddy.j2
|
|
dest: /etc/caddy/sites/e-ticket.conf
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
notify: Reload Caddy
|
|
|
|
- name: Create backup directory
|
|
file:
|
|
path: /var/backups/e-ticket
|
|
state: directory
|
|
owner: bot
|
|
group: bot
|
|
mode: "0750"
|
|
|
|
- name: Deploy backup script
|
|
template:
|
|
src: backup.sh.j2
|
|
dest: /var/backups/e-ticket/backup.sh
|
|
owner: bot
|
|
group: bot
|
|
mode: "0750"
|
|
|
|
- name: Configure backup cron (every 30 minutes)
|
|
cron:
|
|
name: "e-ticket database backup"
|
|
minute: "*/30"
|
|
job: "/var/backups/e-ticket/backup.sh >> /var/log/e-ticket-backup.log 2>&1"
|
|
user: bot
|
|
|
|
- name: Configure expire pending orders cron (every 5 minutes)
|
|
cron:
|
|
name: "e-ticket expire pending orders"
|
|
minute: "*/5"
|
|
job: "docker compose -f /var/www/e-ticket/docker-compose-prod.yml exec -T php php bin/console app:orders:expire-pending --env=prod >> /var/log/e-ticket-expire-orders.log 2>&1"
|
|
user: bot
|
|
|
|
- name: Configure messenger monitor cron (every hour)
|
|
cron:
|
|
name: "e-ticket messenger monitor"
|
|
minute: "0"
|
|
job: "docker compose -f /var/www/e-ticket/docker-compose-prod.yml exec -T php php bin/console app:monitor:messenger --env=prod >> /var/log/e-ticket-messenger.log 2>&1"
|
|
user: bot
|
|
|
|
- name: Configure Meilisearch consistency check cron (daily at 3am)
|
|
cron:
|
|
name: "e-ticket meilisearch consistency"
|
|
minute: "0"
|
|
hour: "3"
|
|
job: "docker compose -f /var/www/e-ticket/docker-compose-prod.yml exec -T php php bin/console app:meilisearch:check-consistency --fix --env=prod >> /var/log/e-ticket-meilisearch.log 2>&1"
|
|
user: bot
|
|
|
|
post_tasks:
|
|
- name: Disable maintenance mode
|
|
command: make maintenance_off
|
|
args:
|
|
chdir: /var/www/e-ticket
|
|
|
|
handlers:
|
|
- name: Reload Caddy
|
|
systemd:
|
|
name: caddy
|
|
state: reloaded
|
|
|