Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ LOCALSTACK_AWS_SECRET_ACCESS_KEY=test
LOCALSTACK_AWS_SESSION_TOKEN=


# -----------------------------------------------------------------------------
# Frontend SPA statico locale (S3 LocalStack + CloudFront locale)
# -----------------------------------------------------------------------------
# Queste variabili riguardano SOLO il serving della SPA Angular.
# Non usarle per i documenti e non sostituirle con bucket S3 reali.
# I documenti restano governati da POC_DOCUMENT_DISK/AWS_REAL_* più sotto.
FRONTEND_STATIC_BUCKET=poc-frontend-static-local
FRONTEND_CLOUDFRONT_LOCAL_URL=https://localhost:8443


# -----------------------------------------------------------------------------
# Porte pubblicate sull'host (lato sinistro = porta sulla tua macchina)
# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -194,4 +204,4 @@ TEXTRACT_MAX_BYTES=0
BEDROCK_ENABLED=true
BEDROCK_REGION=
BEDROCK_ENDPOINT=
BEDROCK_MODEL_ID=amazon.nova-lite-v1:0
BEDROCK_MODEL_ID=amazon.nova-lite-v1:0
23 changes: 21 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ on:
branches:
- develop
- main
- migration/new_techs
- migrate/react_angular
tags:
- "v*"
pull_request:
Expand Down Expand Up @@ -131,6 +131,9 @@ jobs:
- name: Check generated client is committed
run: git diff --exit-code -- apps/frontend/src/api/generated

- name: Lint SPA
run: docker compose --profile tools run --rm node npm run frontend:lint

- name: Typecheck SPA
run: docker compose --profile tools run --rm node npm run frontend:typecheck

Expand Down Expand Up @@ -190,7 +193,9 @@ jobs:
# L'archivio viene caricato in cache dal post-step di actions/cache
- name: Save image archive for cache
if: steps.image-cache.outputs.cache-hit != 'true'
run: docker save $(cat /tmp/ci-images.txt) | zstd -T0 -o /tmp/ci-images.tar.zst
run: |
mapfile -t images < /tmp/ci-images.txt
docker save "${images[@]}" | zstd -T0 -o /tmp/ci-images.tar.zst

# --- Check statici senza build (fail fast) ---

Expand Down Expand Up @@ -251,6 +256,15 @@ jobs:
- name: Apply LocalStack infrastructure
run: docker compose --profile tools run --rm terraform init && docker compose --profile tools run --rm terraform apply -auto-approve

# Il percorso di serving default e' Traefik -> frontend-cloudfront -> S3
# LocalStack: lo smoke e gli audit a11y devono colpire la SPA realmente
# caricata nel bucket, non un bucket vuoto. Si riusa la stessa logica di
# make setup (build + sync con cache-control differenziato).
- name: Build and deploy Angular SPA to LocalStack S3
run: |
make frontend-build
make frontend-s3-local-upload

- name: Run migrations
run: docker compose --profile release run --rm migrate

Expand Down Expand Up @@ -295,6 +309,11 @@ jobs:
echo "$spa_headers" | grep -i '^content-security-policy:' | grep -F "frame-ancestors 'none'"
echo "$spa_headers" | grep -i '^x-content-type-options: nosniff'
echo "$spa_headers" | grep -i '^referrer-policy: strict-origin-when-cross-origin'

# La SPA Angular deve essere servita da S3 LocalStack via frontend-cloudfront,
# sia sulla root sia su un deep link (fallback SPA verso index.html)
curl --silent --fail --insecure https://localhost:8443/ | grep -q '<poc-root'
curl --silent --fail --insecure https://localhost:8443/copilot | grep -q '<poc-root'
curl --silent --insecure -I https://localhost:8443/api/v1/documents/1/preview \
| grep -i '^content-security-policy:' | grep -F "frame-ancestors 'self'"

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/mirror-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on:
branches:
- develop
- main
- migration/new_techs
- migrate/react_angular
paths:
- docker-compose.yml
- docker/php/Dockerfile
Expand Down
62 changes: 54 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
.PHONY: help test pint node-install frontend-build frontend-test frontend-typecheck frontend-audit frontend-a11y openapi-generate openapi-validate observability-config observability-up local-tls trusted-local-tls fresh logs sh restart setup release infra-up infra-init infra-plan infra-apply infra-destroy refresh-runtime verify verify-fast verify-backend verify-frontend verify-infra verify-observability verify-ci-local aws-smoke reset-all workers backup-local restore-local
.PHONY: help test pint node-install frontend-build frontend-lint frontend-test frontend-typecheck frontend-audit frontend-a11y frontend-s3-local-provision frontend-s3-local-upload frontend-s3-local-deploy frontend-cloudfront-local-url frontend-serving-local-test openapi-generate openapi-validate observability-config observability-up local-tls trusted-local-tls fresh logs sh restart setup release infra-up infra-init infra-plan infra-apply infra-destroy refresh-runtime verify verify-fast verify-backend verify-frontend verify-infra verify-observability verify-ci-local aws-smoke reset-all workers backup-local restore-local

# Colori per l'output
BLUE := \033[34m
RESET := \033[0m
TERRAFORM := docker compose --profile tools run --rm terraform
NODE := docker compose --profile tools run --rm node
FRONTEND_AUDIT := docker compose --profile tools run --rm frontend-audit
TLS_TOOL := docker compose --profile tools run --rm tls-tool
LOCALSTACK_ENDPOINT_INTERNAL ?= http://localstack:4566
FRONTEND_DIST ?= apps/frontend/dist
FRONTEND_STATIC_BUCKET ?= poc-frontend-static-local
FRONTEND_CLOUDFRONT_LOCAL_URL ?= https://localhost:8443
# -T: niente pseudo-TTY per i tool non interattivi. Senza, "docker compose run"
# mette il terminale in raw mode e, se il run si blocca (es. glitch del daemon),
# Ctrl+C viene inghiottito e l'unico modo è chiudere il terminale — che però NON
# uccide il processo "compose run", lasciando container *-run-* orfani in stato
# "created". Con -T il SIGINT arriva e --rm ripulisce il container.
TERRAFORM := docker compose --profile tools run --rm -T terraform
NODE := docker compose --profile tools run --rm -T node
AWS_CLI := docker compose --profile tools run --rm -T --entrypoint aws aws-cli --endpoint-url=$(LOCALSTACK_ENDPOINT_INTERNAL)
FRONTEND_AUDIT := docker compose --profile tools run --rm -T frontend-audit
TLS_TOOL := docker compose --profile tools run --rm -T tls-tool
TEST_ENV := -e CONFIG_SOURCE=env \
-e APP_ENV=testing \
-e CACHE_STORE=array \
Expand All @@ -23,11 +33,16 @@ help:
@echo "$(BLUE)Comandi disponibili:$(RESET)"
@echo " $(BLUE)make test$(RESET) Esegue la suite di test (Pest)"
@echo " $(BLUE)make pint$(RESET) Esegue Laravel Pint in modalita' check"
@echo " $(BLUE)make frontend-build$(RESET) Compila la SPA React/Vite"
@echo " $(BLUE)make frontend-build$(RESET) Compila la SPA Angular"
@echo " $(BLUE)make frontend-lint$(RESET) Esegue ESLint sul frontend Angular"
@echo " $(BLUE)make frontend-test$(RESET) Esegue i test frontend"
@echo " $(BLUE)make frontend-typecheck$(RESET) Esegue typecheck TypeScript"
@echo " $(BLUE)make frontend-audit$(RESET) Audit npm production dependencies"
@echo " $(BLUE)make frontend-a11y$(RESET) Esegue axe e Pa11y sullo stack HTTPS locale"
@echo " $(BLUE)make frontend-s3-local-provision$(RESET) Provisiona il bucket S3 locale della SPA"
@echo " $(BLUE)make frontend-s3-local-deploy$(RESET) Builda e carica la SPA Angular su S3 LocalStack"
@echo " $(BLUE)make frontend-cloudfront-local-url$(RESET) Stampa URL del CloudFront locale"
@echo " $(BLUE)make frontend-serving-local-test$(RESET) Smoke test del serving S3 locale + CloudFront locale"
@echo " $(BLUE)make openapi-generate$(RESET) Rigenera il client TypeScript"
@echo " $(BLUE)make openapi-validate$(RESET) Valida il contratto OpenAPI"
@echo " $(BLUE)make observability-config$(RESET) Valida la configurazione OTel Collector"
Expand Down Expand Up @@ -70,6 +85,7 @@ verify-backend:

verify-frontend: node-install
$(NODE) npm run openapi:generate
$(NODE) npm run frontend:lint
$(NODE) npm run frontend:typecheck
$(NODE) npm run frontend:test
$(NODE) npm run frontend:build
Expand All @@ -95,16 +111,44 @@ frontend-build: node-install
$(NODE) npm run openapi:generate
$(NODE) npm run frontend:build

frontend-lint: node-install
$(NODE) npm run frontend:lint

frontend-test: openapi-generate
$(NODE) npm run frontend:test

frontend-typecheck: openapi-generate
$(NODE) npm run frontend:typecheck

frontend-s3-local-provision: infra-apply

frontend-s3-local-upload:
test -f "$(FRONTEND_DIST)/index.html"
$(AWS_CLI) s3api head-bucket --bucket "$(FRONTEND_STATIC_BUCKET)"
$(AWS_CLI) s3 sync "$(FRONTEND_DIST)/" "s3://$(FRONTEND_STATIC_BUCKET)/" --delete --exclude "index.html" --cache-control "public, max-age=3600"
$(AWS_CLI) s3 cp "$(FRONTEND_DIST)/" "s3://$(FRONTEND_STATIC_BUCKET)/" --recursive --exclude "*" --include "*.js" --include "*.css" --cache-control "public, max-age=31536000, immutable"
$(AWS_CLI) s3 cp "$(FRONTEND_DIST)/index.html" "s3://$(FRONTEND_STATIC_BUCKET)/index.html" --cache-control "no-cache, max-age=0, must-revalidate" --content-type "text/html; charset=utf-8"

frontend-s3-local-deploy: frontend-build frontend-s3-local-provision
$(MAKE) frontend-s3-local-upload

frontend-cloudfront-local-url:
@printf "%s\n" "$(FRONTEND_CLOUDFRONT_LOCAL_URL)"
@printf "\n"

frontend-serving-local-test: frontend-s3-local-deploy
@if [ ! -f docker/traefik/certs/poc-local.test.crt ] || [ ! -f docker/traefik/certs/poc-local.test.key ]; then $(MAKE) local-tls; fi
docker compose up -d --wait --force-recreate frontend-cloudfront traefik
@url="$(FRONTEND_CLOUDFRONT_LOCAL_URL)"; \
echo "$(BLUE)Testing $$url$(RESET)"; \
curl -kfsS "$$url" | grep -q "<poc-root"

frontend-audit: node-install
$(NODE) npm audit --omit=dev --audit-level=high

frontend-a11y: node-install
frontend-a11y: frontend-s3-local-deploy
@if [ ! -f docker/traefik/certs/poc-local.test.crt ] || [ ! -f docker/traefik/certs/poc-local.test.key ]; then $(MAKE) local-tls; fi
docker compose up -d --wait --force-recreate app nginx frontend-cloudfront traefik
$(FRONTEND_AUDIT) node scripts/a11y/csp-smoke.mjs https://traefik:8443
$(FRONTEND_AUDIT) node scripts/a11y/axe-playwright.mjs https://traefik:8443
$(FRONTEND_AUDIT) node scripts/a11y/pa11y-runner.mjs https://traefik:8443
Expand Down Expand Up @@ -139,7 +183,7 @@ fresh:
docker compose exec -T redis redis-cli FLUSHALL

logs:
docker compose logs -f app queue nginx localstack
docker compose logs -f app queue nginx frontend-cloudfront localstack

sh:
docker compose run --rm app sh
Expand Down Expand Up @@ -171,6 +215,8 @@ setup:
docker compose --profile release build
docker compose up -d postgres redis localstack
$(MAKE) infra-apply
$(MAKE) frontend-build
$(MAKE) frontend-s3-local-upload
$(MAKE) release
docker compose up -d app nginx queue traefik otel-collector prometheus tempo alertmanager grafana loki alloy
@echo "$(BLUE)L'ambiente è stato configurato ed è in fase di avvio.$(RESET)"
Expand Down
86 changes: 28 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,20 @@
# PoC - aLittleByte
<p align="center">
<img src="https://img.shields.io/badge/Laravel-API-red?logo=laravel&logoColor=white" alt="Laravel API">
<img src="https://img.shields.io/badge/Angular-SPA-DD0031?logo=angular&logoColor=white" alt="Angular SPA">
<img src="https://img.shields.io/badge/PostgreSQL-persistence-4169E1?logo=postgresql&logoColor=white" alt="PostgreSQL">
<img src="https://img.shields.io/badge/LocalStack-AWS%20emulation-FF9900?logo=amazonaws&logoColor=white" alt="LocalStack">
<img src="https://img.shields.io/badge/Bedrock-AI%20provider-FF9900?logo=amazonaws&logoColor=white" alt="Bedrock">
<img src="https://img.shields.io/badge/OpenTelemetry-observability-000000?logo=opentelemetry&logoColor=white" alt="OpenTelemetry">
</p>

<p align="center">
<a href="https://github.com/aLittleByte-19/PoC/actions/workflows/ci.yml?query=branch%3Amain"><img src="https://github.com/aLittleByte-19/PoC/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI (main)"></a>
</p>


Proof of Concept per workflow HR e documentali assistiti da AI, con generazione di comunicazioni, pipeline asincrona di elaborazione PDF, integrazione AWS-like locale e osservabilità end-to-end.

<table align="center">
<tr>
<td align="center" width="25%">
<strong>Application</strong><br><br>
<img src="https://img.shields.io/badge/Laravel-API-red?logo=laravel&logoColor=white" alt="Laravel">
<br>
<img src="https://img.shields.io/badge/PHP-8.4-777BB4?logo=php&logoColor=white" alt="PHP">
<br>
<img src="https://img.shields.io/badge/React-SPA-61DAFB?logo=react&logoColor=black" alt="React">
<br>
<img src="https://img.shields.io/badge/TypeScript-frontend-3178C6?logo=typescript&logoColor=white" alt="TypeScript">
</td>
<td align="center" width="25%">
<strong>Data & Storage</strong><br><br>
<img src="https://img.shields.io/badge/PostgreSQL-persistence-4169E1?logo=postgresql&logoColor=white" alt="PostgreSQL">
<br>
<img src="https://img.shields.io/badge/Redis-cache%20%26%20rate%20limit-DC382D?logo=redis&logoColor=white" alt="Redis">
<br>
<img src="https://img.shields.io/badge/S3--compatible-document%20storage-569A31?logo=amazons3&logoColor=white" alt="S3-compatible storage">
</td>
<td align="center" width="25%">
<strong>Workflow & AI</strong><br><br>
<img src="https://img.shields.io/badge/LocalStack-AWS%20emulation-FF9900?logo=amazonaws&logoColor=white" alt="LocalStack">
<br>
<img src="https://img.shields.io/badge/Step%20Functions-orchestration-FF9900?logo=amazonaws&logoColor=white" alt="Step Functions">
<br>
<img src="https://img.shields.io/badge/SQS-task%20queue-FF9900?logo=amazonaws&logoColor=white" alt="SQS">
<br>
<img src="https://img.shields.io/badge/Bedrock-LLM%20provider-FF9900?logo=amazonaws&logoColor=white" alt="Bedrock">
</td>
<td align="center" width="25%">
<strong>Observability</strong><br><br>
<img src="https://img.shields.io/badge/OpenTelemetry-collector-000000?logo=opentelemetry&logoColor=white" alt="OpenTelemetry">
<br>
<img src="https://img.shields.io/badge/Prometheus-metrics-E6522C?logo=prometheus&logoColor=white" alt="Prometheus">
<br>
<img src="https://img.shields.io/badge/Grafana-dashboards-F46800?logo=grafana&logoColor=white" alt="Grafana">
<br>
<img src="https://img.shields.io/badge/Loki%20%2B%20Tempo-logs%20%26%20traces-F46800?logo=grafana&logoColor=white" alt="Loki and Tempo">
</td>
</tr>
</table>

<table align="center">
<tr>
<td align="center">
<strong>CI / Quality Gate</strong><br><br>
<img src="https://img.shields.io/badge/Pest-backend%20tests-6E9F18" alt="Pest">
<img src="https://img.shields.io/badge/Vitest-frontend%20tests-6E9F18?logo=vitest&logoColor=white" alt="Vitest">
<img src="https://img.shields.io/badge/Trivy-image%20scan-1904DA?logo=trivy&logoColor=white" alt="Trivy">
<img src="https://img.shields.io/badge/axe%20%2B%20pa11y-accessibility-654FF0" alt="Accessibility">
<img src="https://img.shields.io/badge/Terraform-validated-844FBA?logo=terraform&logoColor=white" alt="Terraform"> <br><br>
<a href="https://github.com/aLittleByte-19/PoC/actions/workflows/ci.yml?query=branch%3Amain"><img src="https://github.com/aLittleByte-19/PoC/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI (main)"></a>
</td>
</tr>
</table>

## Contesto

Expand All @@ -71,12 +28,13 @@ Il valore principale del progetto è architetturale e dimostrativo: ogni fase de

La PoC dimostra un modello applicativo composto da più livelli cooperanti:

* una **SPA React/Vite/TypeScript** per l’interazione operatore;
* una **SPA Angular/TypeScript** per l’interazione operatore;
* un backend **Laravel/PHP** per API, validazione, autorizzazione, orchestrazione applicativa e persistenza;
* **PostgreSQL** per dati strutturati, stati applicativi, audit e risultati di elaborazione;
* **Redis** per cache, sessioni e rate limiting;
* storage documentale **S3-compatible** per PDF originali e sotto-documenti generati;
* **LocalStack** per emulare localmente servizi AWS come SQS, Step Functions, SSM, Secrets Manager e S3;
* **CloudFront locale** davanti al bucket S3 LocalStack per il serving della SPA Angular;
* integrazione AI tramite astrazione verso **Bedrock** e integrazione OCR tramite **Textract** (attivabile, disabilitata di default);
* stack di osservabilità con **OpenTelemetry, Prometheus, Grafana, Tempo, Loki, Alloy e Alertmanager**;
* CI con test backend/frontend, scansione immagini, validazione infrastrutturale e audit accessibilità.
Expand All @@ -87,7 +45,7 @@ La separazione tra richiesta HTTP e workflow asincrono è uno dei punti centrali

L’architettura locale è organizzata intorno a un entrypoint edge, un layer applicativo, servizi dati, workflow asincroni e osservabilità.

Traefik gestisce l’ingresso verso i servizi esposti e instrada il traffico verso Nginx. Nginx serve la SPA frontend e inoltra le richieste API al runtime Laravel/PHP-FPM. Laravel gestisce le API, valida le richieste, applica le regole applicative, registra eventi di audit e avvia i workflow documentali. PostgreSQL conserva lo stato persistente, Redis supporta componenti runtime a bassa latenza, mentre LocalStack fornisce servizi AWS-like in ambiente locale.
Traefik gestisce l’ingresso verso i servizi esposti e instrada il traffico applicativo verso il CloudFront locale. Il CloudFront locale serve la SPA Angular dagli oggetti caricati nel bucket S3 LocalStack e inoltra `/api/`, `/health` e `/ready` a Nginx/Laravel. Nginx resta il proxy applicativo verso PHP-FPM e il percorso interno di compatibilità. PostgreSQL conserva lo stato persistente, Redis supporta componenti runtime a bassa latenza, mentre LocalStack fornisce servizi AWS-like in ambiente locale.

I worker Laravel consumano task asincroni da SQS e comunicano con Step Functions tramite callback task token. Questo permette di rappresentare una pipeline documentale composta da stati espliciti, retry, gestione errori, idempotenza e aggiornamento progressivo dello stato.

Expand Down Expand Up @@ -152,6 +110,18 @@ make test
make logs
```

### Serving statico LocalStack

```bash
make frontend-s3-local-deploy
make frontend-cloudfront-local-url
make frontend-serving-local-test
```

Il flusso builda Angular, provisiona il bucket S3 LocalStack via Terraform, carica `apps/frontend/dist` con cache-control differenziato (`index.html` no-cache, bundle hashati immutable) e verifica il serving attraverso il CloudFront locale su `https://localhost:8443`. La simulazione valida il pattern build → bucket → distribuzione CDN-like locale, ma non sostituisce una validazione CloudFront reale con TLS/OAC/edge propagation/invalidation AWS.

Il bucket `FRONTEND_STATIC_BUCKET` è dedicato solo alla SPA. I documenti continuano a usare `POC_DOCUMENT_DISK=s3` per S3 LocalStack o `POC_DOCUMENT_DISK=real_s3` con `AWS_REAL_*` per S3/Textract reali.

### Accesso ai servizi

| Servizio | URL |
Expand Down
5 changes: 5 additions & 0 deletions app/Console/Commands/ResetPocData.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ private function resetStorage(): void
{
$documentDisk = config('filesystems.default', 'local');

// I documenti sono salvati in "originals/" e "sub/" relativi al root del
// disk (vedi DocumentProcessingService); "documents/" resta per pulire
// eventuali dati pregressi del vecchio layout.
Storage::disk($documentDisk)->deleteDirectory('originals');
Storage::disk($documentDisk)->deleteDirectory('sub');
Storage::disk($documentDisk)->deleteDirectory('documents');
Storage::disk($documentDisk)->deleteDirectory('livewire-tmp');

Expand Down
4 changes: 2 additions & 2 deletions app/Copilot/Documents/Enums/ReviewStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ public function color(): string
{
return match ($this) {
self::NeedsReview => 'warning',
self::AutoValidated => 'success',
self::AutoValidated => 'info',
self::Quarantined => 'danger',
self::ManuallyValidated => 'info',
self::ManuallyValidated => 'success',
};
}
}
Loading
Loading