diff --git a/README.md b/README.md index 87a75c3..a53fb85 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,111 @@ # 🧩 SOA – Service-Oriented Architecture Project +## πŸ—οΈ Architecture Overview + +This project implements a **Service-Oriented Architecture (SOA)** for an art gallery management system using Docker containerization and microservices architecture. + +### System Components + +``` + 🌐 HTTPS (Port 443) + β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Apache Reverse Proxy β”‚ +β”‚ (SSL Termination + OIDC Validation) β”‚ +β”‚ β”‚ +β”‚ /api/public/* ──────┐ β”Œβ”€β”€β”€β”€β”€β”€ /api/private/* β”‚ +β”‚ (No Auth) β”‚ β”‚ (OIDC Required) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”‚ β”‚ (+ OIDC Headers) + β”‚ β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β” β”Œβ”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ β”‚ β”‚ + β”‚ Public API β”‚ β”‚ Private API β”‚ + β”‚ (Laravel) β”‚ β”‚ (Flask) β”‚ + β”‚ Port 5001 β”‚ β”‚ Port 5002 β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ MySQL β”‚ β”‚ Keycloak β”‚ + β”‚ (Application DB) β”‚ β”‚ + Postgres β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Redis β”‚ + β”‚ (Cache + Events) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Technology Stack + +| Component | Technology | Purpose | +|-----------|------------|---------| +| **Reverse Proxy** | Apache HTTP Server | SSL termination, request routing | +| **Authentication** | Keycloak | OIDC/OAuth2 identity provider | +| **Public API** | Laravel 12 (PHP) | Public galleries and artworks API | +| **Private API** | Flask (Python) | User management and private content | +| **Databases** | PostgreSQL + MySQL | Data persistence | +| **Cache/Queue** | Redis | Caching and event messaging | // NOT IMPLEMENTED YET + +### Service Architecture + +#### πŸ”“ **Public API Service** (Laravel) +- **Port**: Internal 5001 (via Apache) +- **Database**: MySQL +- **Purpose**: Public access to galleries and artworks +- **Features**: + - RESTful API for public galleries + - Artist directory + - Public artwork browsing + - Pagination and filtering + +#### πŸ”’ **Private API Service** (Flask) +- **Port**: Internal 5002 (via Apache) +- **Database**: MySQL +- **Authentication**: OIDC via Apache mod_auth_openidc +- **Purpose**: Authenticated user operations +- **Features**: + - User profile management + - Gallery creation and management + - Artwork upload and editing + - Review system for galleries/artworks + - Invitation system for gallery members + +#### πŸ” **Authentication Service** (Keycloak) +- **Port**: 8080 +- **Database**: PostgreSQL +- **Purpose**: Centralized identity and access management +- **Features**: + - OIDC/OAuth2 provider + - User registration and login + - Token-based authentication + - Single Sign-On (SSO) + +### Request Flow + +All requests enter through **Apache on port 443** (HTTPS) and are processed as follows: + +1. **SSL Termination**: Apache handles SSL/TLS encryption +2. **Request Routing**: Based on URL path: + - `/api/public/*` β†’ Routes to **Public API** (Laravel on port 5001) + - `/api/private/*` β†’ Routes to **Private API** (Flask on port 5002) +3. **OIDC Authentication Check**: + - **Public API**: No authentication required + - **Private API**: Apache mod_auth_openidc validates OIDC tokens with Keycloak +4. **Header Injection**: Apache injects user info headers (OIDC_email, OIDC_user) for authenticated requests +5. **API Processing**: Backend services handle business logic +6. **Data Persistence**: MySQL stores application data +7. **Event Publishing**: Redis handles inter-service communication + +### Security Model + +- **SSL/TLS**: All external communication encrypted +- **OIDC Authentication**: Industry standard OAuth2/OIDC flow +- **Token-based Authorization**: JWT tokens for API access +- **Network Isolation**: Services communicate via internal network +- **Database Security**: Separate databases for auth and application data + ## πŸš€ Quick Start 1. **Start the application stack:** diff --git a/apache/conf/extra/httpd-vhosts.conf b/apache/conf/extra/httpd-vhosts.conf index 9e0bc2d..6db7bca 100644 --- a/apache/conf/extra/httpd-vhosts.conf +++ b/apache/conf/extra/httpd-vhosts.conf @@ -50,6 +50,7 @@ Listen 443 ServerName api.local ErrorLog ${APACHE_LOG_DIR}/api_error.log CustomLog ${APACHE_LOG_DIR}/api_access.log combined + SSLEngine on SSLCertificateFile /usr/local/apache2/conf/server.crt SSLCertificateKeyFile /usr/local/apache2/conf/server.key @@ -67,18 +68,36 @@ Listen 443 OIDCScope "openid email profile" OIDCSessionInactivityTimeout 86400 OIDCSSLValidateServer Off + + # Configure OAuth2 Bearer token validation + OIDCOAuth2IntrospectionEndpoint https://auth.local/realms/master/protocol/openid-connect/token/introspect + OIDCOAuth2IntrospectionEndpointAuth client_secret_basic + OIDCOAuth2IntrospectionClientID soa + OIDCOAuth2IntrospectionClientSecret mysecret + # Proxy public API (no auth) ProxyPass /api/public http://public_api:5001/ ProxyPassReverse /api/public http://public_api:5001/ - # Proxy private API (OIDC protected) + # Proxy private API (supports both OIDC and Bearer tokens) ProxyPass /api/private http://private_api:5002/api/private ProxyPassReverse /api/private http://private_api:5002/api/private - AuthType openid-connect + # Accept both OIDC sessions and OAuth2 Bearer tokens + AuthType auth-openidc Require valid-user + + # Allow both authentication methods + OIDCUnAuthAction auth + OIDCUnAutzAction 401 + + # Pass user info as headers for both auth types RequestHeader set X-User-Email "%{HTTP_OIDC_EMAIL}i" RequestHeader set X-User-Name "%{HTTP_OIDC_PREFERRED_USERNAME}i" + + # Also pass OAuth2 token info + RequestHeader set X-OAuth2-Email "%{HTTP_OAUTH2_EMAIL}i" + RequestHeader set X-OAuth2-Username "%{HTTP_OAUTH2_PREFERRED_USERNAME}i" \ No newline at end of file diff --git a/bruno/SOA/environments/env.bru b/bruno/SOA/environments/env.bru index c81fccb..2ec0e7b 100644 --- a/bruno/SOA/environments/env.bru +++ b/bruno/SOA/environments/env.bru @@ -1,4 +1,6 @@ vars { gallery_id: 6 URL: http://localhost:8000 + baseUrl: http://api.local/api/private + authUrl: http://auth.local:8080 } diff --git a/bruno/SOA/private/artwork-reviews/artwork-reviews.bru b/bruno/SOA/private/artwork-reviews/artwork-reviews.bru new file mode 100644 index 0000000..f6b935e --- /dev/null +++ b/bruno/SOA/private/artwork-reviews/artwork-reviews.bru @@ -0,0 +1,14 @@ +meta { + name: Get Artwork Reviews + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/artwork/1/reviews +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/artwork-reviews/create-artwork-review.bru b/bruno/SOA/private/artwork-reviews/create-artwork-review.bru new file mode 100644 index 0000000..d15de9b --- /dev/null +++ b/bruno/SOA/private/artwork-reviews/create-artwork-review.bru @@ -0,0 +1,23 @@ +meta { + name: Create Artwork Review + type: http + seq: 2 +} + +post { + url: {{baseUrl}}/artwork/1/review +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "grade": 5, + "description": "Absolutely stunning artwork! The colors and technique are magnificent.", + "parent_ar_id": null + } +} diff --git a/bruno/SOA/private/artwork-reviews/given-artwork-reviews.bru b/bruno/SOA/private/artwork-reviews/given-artwork-reviews.bru new file mode 100644 index 0000000..554fc96 --- /dev/null +++ b/bruno/SOA/private/artwork-reviews/given-artwork-reviews.bru @@ -0,0 +1,14 @@ +meta { + name: Get Given Artwork Reviews + type: http + seq: 4 +} + +get { + url: {{baseUrl}}/artworks/reviews/given +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/artwork-reviews/received-artwork-reviews.bru b/bruno/SOA/private/artwork-reviews/received-artwork-reviews.bru new file mode 100644 index 0000000..564c59b --- /dev/null +++ b/bruno/SOA/private/artwork-reviews/received-artwork-reviews.bru @@ -0,0 +1,14 @@ +meta { + name: Get Received Artwork Reviews + type: http + seq: 5 +} + +get { + url: {{baseUrl}}/artworks/reviews/received +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/artwork-reviews/update-artwork-review.bru b/bruno/SOA/private/artwork-reviews/update-artwork-review.bru new file mode 100644 index 0000000..0de735d --- /dev/null +++ b/bruno/SOA/private/artwork-reviews/update-artwork-review.bru @@ -0,0 +1,22 @@ +meta { + name: Update Artwork Review + type: http + seq: 3 +} + +put { + url: {{baseUrl}}/artworks/review/1 +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "grade": 4, + "description": "Updated: Very beautiful artwork! Great technique and composition." + } +} diff --git a/bruno/SOA/private/artworks/create-artwork.bru b/bruno/SOA/private/artworks/create-artwork.bru new file mode 100644 index 0000000..7e17202 --- /dev/null +++ b/bruno/SOA/private/artworks/create-artwork.bru @@ -0,0 +1,29 @@ +meta { + name: Create Artwork + type: http + seq: 3 +} + +post { + url: {{baseUrl}}/gallery/1/artwork +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "title": "Sunset Over Mountains", + "description": "A beautiful landscape painting capturing the golden hour", + "image_url": "https://example.com/artwork1.jpg", + "medium": "Oil on Canvas", + "dimensions": "24x36 inches", + "creation_year": 2024, + "price": 1500.00, + "is_visible": true, + "is_for_sale": true + } +} diff --git a/bruno/SOA/private/artworks/gallery-artworks.bru b/bruno/SOA/private/artworks/gallery-artworks.bru new file mode 100644 index 0000000..c0b84e3 --- /dev/null +++ b/bruno/SOA/private/artworks/gallery-artworks.bru @@ -0,0 +1,14 @@ +meta { + name: Get Gallery Artworks + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/gallery/1/artworks +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/artworks/get-artwork.bru b/bruno/SOA/private/artworks/get-artwork.bru new file mode 100644 index 0000000..6173af0 --- /dev/null +++ b/bruno/SOA/private/artworks/get-artwork.bru @@ -0,0 +1,14 @@ +meta { + name: Get Artwork Details + type: http + seq: 2 +} + +get { + url: {{baseUrl}}/artwork/1 +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/artworks/my-artworks.bru b/bruno/SOA/private/artworks/my-artworks.bru new file mode 100644 index 0000000..c8288d2 --- /dev/null +++ b/bruno/SOA/private/artworks/my-artworks.bru @@ -0,0 +1,14 @@ +meta { + name: Get My Artworks + type: http + seq: 5 +} + +get { + url: {{baseUrl}}/artworks/mine +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/artworks/update-artwork.bru b/bruno/SOA/private/artworks/update-artwork.bru new file mode 100644 index 0000000..ac2b8af --- /dev/null +++ b/bruno/SOA/private/artworks/update-artwork.bru @@ -0,0 +1,24 @@ +meta { + name: Update Artwork + type: http + seq: 4 +} + +put { + url: {{baseUrl}}/artwork/1 +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "title": "Updated Sunset Over Mountains", + "description": "An updated beautiful landscape painting", + "price": 1800.00, + "is_for_sale": false + } +} diff --git a/bruno/SOA/private/auth/debug-headers.bru b/bruno/SOA/private/auth/debug-headers.bru new file mode 100644 index 0000000..66ebb02 --- /dev/null +++ b/bruno/SOA/private/auth/debug-headers.bru @@ -0,0 +1,14 @@ +meta { + name: Debug Headers + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/debug-headers +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/auth/redirect.bru b/bruno/SOA/private/auth/redirect.bru new file mode 100644 index 0000000..cfe1d60 --- /dev/null +++ b/bruno/SOA/private/auth/redirect.bru @@ -0,0 +1,13 @@ +meta { + name: OIDC Redirect + type: http + seq: 2 +} + +get { + url: {{baseUrl}}/redirect +} + +params:query { + code: authorization_code_here +} diff --git a/bruno/SOA/private/environments/Token User.bru b/bruno/SOA/private/environments/Token User.bru new file mode 100644 index 0000000..7f02675 --- /dev/null +++ b/bruno/SOA/private/environments/Token User.bru @@ -0,0 +1,17 @@ +meta { + name: Token User + type: http + seq: 2 +} + +post { + url: {{authUrl}}/realms/master/protocol/openid-connect/token + body: formUrlEncoded + auth: inherit +} + +body:form-urlencoded { + grant_type: client_credentials + client_id: soa + client_secret: mysecret +} diff --git a/bruno/SOA/private/environments/Token.bru b/bruno/SOA/private/environments/Token.bru new file mode 100644 index 0000000..35e12b6 --- /dev/null +++ b/bruno/SOA/private/environments/Token.bru @@ -0,0 +1,11 @@ +meta { + name: Token + type: http + seq: 1 +} + +get { + url: /realms/master/protocol/openid-connect/token + body: none + auth: inherit +} diff --git a/bruno/SOA/private/environments/local.bru b/bruno/SOA/private/environments/local.bru new file mode 100644 index 0000000..01246ec --- /dev/null +++ b/bruno/SOA/private/environments/local.bru @@ -0,0 +1,5 @@ +vars { + baseUrl: https://api.local/api/private + email: alexis@example.com + username: alexis +} diff --git a/bruno/SOA/private/galleries/create-gallery.bru b/bruno/SOA/private/galleries/create-gallery.bru new file mode 100644 index 0000000..9f73ec6 --- /dev/null +++ b/bruno/SOA/private/galleries/create-gallery.bru @@ -0,0 +1,24 @@ +meta { + name: Create Gallery + type: http + seq: 3 +} + +post { + url: {{baseUrl}}/gallery +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "title": "Modern Art Collection", + "description": "A curated collection of contemporary artworks", + "is_public": true, + "publication_date": "2025-06-28T10:00:00Z" + } +} diff --git a/bruno/SOA/private/galleries/gallery-members.bru b/bruno/SOA/private/galleries/gallery-members.bru new file mode 100644 index 0000000..9f90890 --- /dev/null +++ b/bruno/SOA/private/galleries/gallery-members.bru @@ -0,0 +1,14 @@ +meta { + name: Get Gallery Members + type: http + seq: 6 +} + +get { + url: {{baseUrl}}/gallery/1/members +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/galleries/get-gallery.bru b/bruno/SOA/private/galleries/get-gallery.bru new file mode 100644 index 0000000..16d7e40 --- /dev/null +++ b/bruno/SOA/private/galleries/get-gallery.bru @@ -0,0 +1,14 @@ +meta { + name: Get Gallery Details + type: http + seq: 2 +} + +get { + url: {{baseUrl}}/gallery/1 +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/galleries/list-galleries.bru b/bruno/SOA/private/galleries/list-galleries.bru new file mode 100644 index 0000000..4c98e8e --- /dev/null +++ b/bruno/SOA/private/galleries/list-galleries.bru @@ -0,0 +1,14 @@ +meta { + name: List All Galleries + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/galleries +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/galleries/my-galleries.bru b/bruno/SOA/private/galleries/my-galleries.bru new file mode 100644 index 0000000..3a4c302 --- /dev/null +++ b/bruno/SOA/private/galleries/my-galleries.bru @@ -0,0 +1,14 @@ +meta { + name: Get My Galleries + type: http + seq: 5 +} + +get { + url: {{baseUrl}}/galleries/mine +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/galleries/update-gallery.bru b/bruno/SOA/private/galleries/update-gallery.bru new file mode 100644 index 0000000..9727e5f --- /dev/null +++ b/bruno/SOA/private/galleries/update-gallery.bru @@ -0,0 +1,23 @@ +meta { + name: Update Gallery + type: http + seq: 4 +} + +put { + url: {{baseUrl}}/gallery/1 +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "title": "Updated Modern Art Collection", + "description": "An updated curated collection of contemporary artworks", + "is_public": false + } +} diff --git a/bruno/SOA/private/gallery-reviews/create-gallery-review.bru b/bruno/SOA/private/gallery-reviews/create-gallery-review.bru new file mode 100644 index 0000000..273074a --- /dev/null +++ b/bruno/SOA/private/gallery-reviews/create-gallery-review.bru @@ -0,0 +1,23 @@ +meta { + name: Create Gallery Review + type: http + seq: 2 +} + +post { + url: {{baseUrl}}/gallery/1/review +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "grade": 5, + "description": "Excellent gallery with amazing artwork collection!", + "parent_gr_id": null + } +} diff --git a/bruno/SOA/private/gallery-reviews/gallery-reviews.bru b/bruno/SOA/private/gallery-reviews/gallery-reviews.bru new file mode 100644 index 0000000..c47a904 --- /dev/null +++ b/bruno/SOA/private/gallery-reviews/gallery-reviews.bru @@ -0,0 +1,14 @@ +meta { + name: Get Gallery Reviews + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/gallery/1/reviews +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/gallery-reviews/given-gallery-reviews.bru b/bruno/SOA/private/gallery-reviews/given-gallery-reviews.bru new file mode 100644 index 0000000..d4d5a36 --- /dev/null +++ b/bruno/SOA/private/gallery-reviews/given-gallery-reviews.bru @@ -0,0 +1,14 @@ +meta { + name: Get Given Gallery Reviews + type: http + seq: 4 +} + +get { + url: {{baseUrl}}/galleries/reviews/given +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/gallery-reviews/received-gallery-reviews.bru b/bruno/SOA/private/gallery-reviews/received-gallery-reviews.bru new file mode 100644 index 0000000..5073cec --- /dev/null +++ b/bruno/SOA/private/gallery-reviews/received-gallery-reviews.bru @@ -0,0 +1,14 @@ +meta { + name: Get Received Gallery Reviews + type: http + seq: 5 +} + +get { + url: {{baseUrl}}/galleries/reviews/received +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/gallery-reviews/update-gallery-review.bru b/bruno/SOA/private/gallery-reviews/update-gallery-review.bru new file mode 100644 index 0000000..dd2e3fa --- /dev/null +++ b/bruno/SOA/private/gallery-reviews/update-gallery-review.bru @@ -0,0 +1,22 @@ +meta { + name: Update Gallery Review + type: http + seq: 3 +} + +put { + url: {{baseUrl}}/galleries/review/1 +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "grade": 4, + "description": "Updated: Very good gallery with great artwork collection!" + } +} diff --git a/bruno/SOA/private/invitations/invite-user.bru b/bruno/SOA/private/invitations/invite-user.bru new file mode 100644 index 0000000..e88eafe --- /dev/null +++ b/bruno/SOA/private/invitations/invite-user.bru @@ -0,0 +1,22 @@ +meta { + name: Invite User to Gallery + type: http + seq: 1 +} + +post { + url: {{baseUrl}}/gallery/1/invite +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "user_id": 2, + "role": "viewer" + } +} diff --git a/bruno/SOA/private/invitations/received-invitations.bru b/bruno/SOA/private/invitations/received-invitations.bru new file mode 100644 index 0000000..f8c8c57 --- /dev/null +++ b/bruno/SOA/private/invitations/received-invitations.bru @@ -0,0 +1,14 @@ +meta { + name: Get Received Invitations + type: http + seq: 3 +} + +get { + url: {{baseUrl}}/invitations/received +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/invitations/respond-invitation.bru b/bruno/SOA/private/invitations/respond-invitation.bru new file mode 100644 index 0000000..fb58263 --- /dev/null +++ b/bruno/SOA/private/invitations/respond-invitation.bru @@ -0,0 +1,21 @@ +meta { + name: Respond to Invitation + type: http + seq: 2 +} + +put { + url: {{baseUrl}}/invitations/1/respond +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "status": "accepted" + } +} diff --git a/bruno/SOA/private/user/get-me.bru b/bruno/SOA/private/user/get-me.bru new file mode 100644 index 0000000..73e9972 --- /dev/null +++ b/bruno/SOA/private/user/get-me.bru @@ -0,0 +1,14 @@ +meta { + name: Get My Profile + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/me +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} diff --git a/bruno/SOA/private/user/update-me.bru b/bruno/SOA/private/user/update-me.bru new file mode 100644 index 0000000..55bd77e --- /dev/null +++ b/bruno/SOA/private/user/update-me.bru @@ -0,0 +1,25 @@ +meta { + name: Update My Profile + type: http + seq: 2 +} + +put { + url: {{baseUrl}}/me +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "alias": "Alexis Updated", + "first_name": "Alexis", + "last_name": "Doe", + "bio": "Art enthusiast and gallery curator", + "profile_picture_url": "https://example.com/avatar.jpg" + } +} diff --git a/bruno/bruno.sh b/bruno/bruno.sh new file mode 100755 index 0000000..2294970 --- /dev/null +++ b/bruno/bruno.sh @@ -0,0 +1,765 @@ +#!/bin/bash + +# Bruno API Collection Generator for Private API +# Creates a complete Bruno collection structure for testing the private API endpoints + +set -e + +# Configuration +COLLECTION_NAME="private" +BASE_URL="https://api.local/api/private" +DEFAULT_EMAIL="alexis@example.com" +DEFAULT_USERNAME="alexis" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo_info() { + echo -e "${BLUE}ℹ️ $1${NC}" +} + +echo_success() { + echo -e "${GREEN}βœ… $1${NC}" +} + +echo_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +echo_error() { + echo -e "${RED}❌ $1${NC}" +} + +# Function to create directory structure +create_directories() { + local base_dir="$1" + + echo_info "Creating directory structure..." + + mkdir -p "${base_dir}"/{environments,auth,user,invitations,galleries,artworks,gallery-reviews,artwork-reviews} + + echo_success "Directory structure created" +} + +# Function to create a file with content +create_file() { + local filepath="$1" + local content="$2" + + echo "$content" > "$filepath" + echo_success "Created: $filepath" +} + +# Generate collection files +generate_files() { + local base_dir="$1" + + echo_info "Generating Bruno collection files..." + + # Main collection file + cat > "${base_dir}/bruno.json" << EOF +{ + "version": "1", + "name": "Private API", + "type": "collection" +} +EOF + echo_success "Created: ${base_dir}/bruno.json" + + # Environment file + cat > "${base_dir}/environments/local.bru" << EOF +vars { + baseUrl: ${BASE_URL} + email: ${DEFAULT_EMAIL} + username: ${DEFAULT_USERNAME} +} +EOF + echo_success "Created: ${base_dir}/environments/local.bru" + + # Auth files + cat > "${base_dir}/auth/debug-headers.bru" << 'EOF' +meta { + name: Debug Headers + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/debug-headers +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/auth/debug-headers.bru" + + cat > "${base_dir}/auth/redirect.bru" << 'EOF' +meta { + name: OIDC Redirect + type: http + seq: 2 +} + +get { + url: {{baseUrl}}/redirect +} + +params:query { + code: authorization_code_here +} +EOF + echo_success "Created: ${base_dir}/auth/redirect.bru" + + # User files + cat > "${base_dir}/user/get-me.bru" << 'EOF' +meta { + name: Get My Profile + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/me +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/user/get-me.bru" + + cat > "${base_dir}/user/update-me.bru" << 'EOF' +meta { + name: Update My Profile + type: http + seq: 2 +} + +put { + url: {{baseUrl}}/me +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "alias": "Alexis Updated", + "first_name": "Alexis", + "last_name": "Doe", + "bio": "Art enthusiast and gallery curator", + "profile_picture_url": "https://example.com/avatar.jpg" + } +} +EOF + echo_success "Created: ${base_dir}/user/update-me.bru" + + # Invitation files + cat > "${base_dir}/invitations/invite-user.bru" << 'EOF' +meta { + name: Invite User to Gallery + type: http + seq: 1 +} + +post { + url: {{baseUrl}}/gallery/1/invite +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "user_id": 2, + "role": "viewer" + } +} +EOF + echo_success "Created: ${base_dir}/invitations/invite-user.bru" + + cat > "${base_dir}/invitations/respond-invitation.bru" << 'EOF' +meta { + name: Respond to Invitation + type: http + seq: 2 +} + +put { + url: {{baseUrl}}/invitations/1/respond +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "status": "accepted" + } +} +EOF + echo_success "Created: ${base_dir}/invitations/respond-invitation.bru" + + cat > "${base_dir}/invitations/received-invitations.bru" << 'EOF' +meta { + name: Get Received Invitations + type: http + seq: 3 +} + +get { + url: {{baseUrl}}/invitations/received +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/invitations/received-invitations.bru" + + # Gallery files + cat > "${base_dir}/galleries/list-galleries.bru" << 'EOF' +meta { + name: List All Galleries + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/galleries +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/galleries/list-galleries.bru" + + cat > "${base_dir}/galleries/get-gallery.bru" << 'EOF' +meta { + name: Get Gallery Details + type: http + seq: 2 +} + +get { + url: {{baseUrl}}/gallery/1 +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/galleries/get-gallery.bru" + + cat > "${base_dir}/galleries/create-gallery.bru" << 'EOF' +meta { + name: Create Gallery + type: http + seq: 3 +} + +post { + url: {{baseUrl}}/gallery +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "title": "Modern Art Collection", + "description": "A curated collection of contemporary artworks", + "is_public": true, + "publication_date": "2025-06-28T10:00:00Z" + } +} +EOF + echo_success "Created: ${base_dir}/galleries/create-gallery.bru" + + cat > "${base_dir}/galleries/update-gallery.bru" << 'EOF' +meta { + name: Update Gallery + type: http + seq: 4 +} + +put { + url: {{baseUrl}}/gallery/1 +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "title": "Updated Modern Art Collection", + "description": "An updated curated collection of contemporary artworks", + "is_public": false + } +} +EOF + echo_success "Created: ${base_dir}/galleries/update-gallery.bru" + + cat > "${base_dir}/galleries/my-galleries.bru" << 'EOF' +meta { + name: Get My Galleries + type: http + seq: 5 +} + +get { + url: {{baseUrl}}/galleries/mine +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/galleries/my-galleries.bru" + + cat > "${base_dir}/galleries/gallery-members.bru" << 'EOF' +meta { + name: Get Gallery Members + type: http + seq: 6 +} + +get { + url: {{baseUrl}}/gallery/1/members +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/galleries/gallery-members.bru" + + # Artwork files + cat > "${base_dir}/artworks/gallery-artworks.bru" << 'EOF' +meta { + name: Get Gallery Artworks + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/gallery/1/artworks +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/artworks/gallery-artworks.bru" + + cat > "${base_dir}/artworks/get-artwork.bru" << 'EOF' +meta { + name: Get Artwork Details + type: http + seq: 2 +} + +get { + url: {{baseUrl}}/artwork/1 +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/artworks/get-artwork.bru" + + cat > "${base_dir}/artworks/create-artwork.bru" << 'EOF' +meta { + name: Create Artwork + type: http + seq: 3 +} + +post { + url: {{baseUrl}}/gallery/1/artwork +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "title": "Sunset Over Mountains", + "description": "A beautiful landscape painting capturing the golden hour", + "image_url": "https://example.com/artwork1.jpg", + "medium": "Oil on Canvas", + "dimensions": "24x36 inches", + "creation_year": 2024, + "price": 1500.00, + "is_visible": true, + "is_for_sale": true + } +} +EOF + echo_success "Created: ${base_dir}/artworks/create-artwork.bru" + + cat > "${base_dir}/artworks/update-artwork.bru" << 'EOF' +meta { + name: Update Artwork + type: http + seq: 4 +} + +put { + url: {{baseUrl}}/artwork/1 +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "title": "Updated Sunset Over Mountains", + "description": "An updated beautiful landscape painting", + "price": 1800.00, + "is_for_sale": false + } +} +EOF + echo_success "Created: ${base_dir}/artworks/update-artwork.bru" + + cat > "${base_dir}/artworks/my-artworks.bru" << 'EOF' +meta { + name: Get My Artworks + type: http + seq: 5 +} + +get { + url: {{baseUrl}}/artworks/mine +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/artworks/my-artworks.bru" + + # Gallery review files + cat > "${base_dir}/gallery-reviews/gallery-reviews.bru" << 'EOF' +meta { + name: Get Gallery Reviews + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/gallery/1/reviews +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/gallery-reviews/gallery-reviews.bru" + + cat > "${base_dir}/gallery-reviews/create-gallery-review.bru" << 'EOF' +meta { + name: Create Gallery Review + type: http + seq: 2 +} + +post { + url: {{baseUrl}}/gallery/1/review +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "grade": 5, + "description": "Excellent gallery with amazing artwork collection!", + "parent_gr_id": null + } +} +EOF + echo_success "Created: ${base_dir}/gallery-reviews/create-gallery-review.bru" + + cat > "${base_dir}/gallery-reviews/update-gallery-review.bru" << 'EOF' +meta { + name: Update Gallery Review + type: http + seq: 3 +} + +put { + url: {{baseUrl}}/galleries/review/1 +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "grade": 4, + "description": "Updated: Very good gallery with great artwork collection!" + } +} +EOF + echo_success "Created: ${base_dir}/gallery-reviews/update-gallery-review.bru" + + cat > "${base_dir}/gallery-reviews/given-gallery-reviews.bru" << 'EOF' +meta { + name: Get Given Gallery Reviews + type: http + seq: 4 +} + +get { + url: {{baseUrl}}/galleries/reviews/given +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/gallery-reviews/given-gallery-reviews.bru" + + cat > "${base_dir}/gallery-reviews/received-gallery-reviews.bru" << 'EOF' +meta { + name: Get Received Gallery Reviews + type: http + seq: 5 +} + +get { + url: {{baseUrl}}/galleries/reviews/received +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/gallery-reviews/received-gallery-reviews.bru" + + # Artwork review files + cat > "${base_dir}/artwork-reviews/artwork-reviews.bru" << 'EOF' +meta { + name: Get Artwork Reviews + type: http + seq: 1 +} + +get { + url: {{baseUrl}}/artwork/1/reviews +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/artwork-reviews/artwork-reviews.bru" + + cat > "${base_dir}/artwork-reviews/create-artwork-review.bru" << 'EOF' +meta { + name: Create Artwork Review + type: http + seq: 2 +} + +post { + url: {{baseUrl}}/artwork/1/review +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "grade": 5, + "description": "Absolutely stunning artwork! The colors and technique are magnificent.", + "parent_ar_id": null + } +} +EOF + echo_success "Created: ${base_dir}/artwork-reviews/create-artwork-review.bru" + + cat > "${base_dir}/artwork-reviews/update-artwork-review.bru" << 'EOF' +meta { + name: Update Artwork Review + type: http + seq: 3 +} + +put { + url: {{baseUrl}}/artworks/review/1 +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} + Content-Type: application/json +} + +body:json { + { + "grade": 4, + "description": "Updated: Very beautiful artwork! Great technique and composition." + } +} +EOF + echo_success "Created: ${base_dir}/artwork-reviews/update-artwork-review.bru" + + cat > "${base_dir}/artwork-reviews/given-artwork-reviews.bru" << 'EOF' +meta { + name: Get Given Artwork Reviews + type: http + seq: 4 +} + +get { + url: {{baseUrl}}/artworks/reviews/given +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/artwork-reviews/given-artwork-reviews.bru" + + cat > "${base_dir}/artwork-reviews/received-artwork-reviews.bru" << 'EOF' +meta { + name: Get Received Artwork Reviews + type: http + seq: 5 +} + +get { + url: {{baseUrl}}/artworks/reviews/received +} + +headers { + OIDC_email: {{email}} + OIDC_user: {{username}} +} +EOF + echo_success "Created: ${base_dir}/artwork-reviews/received-artwork-reviews.bru" +} + +# Main function +main() { + echo_info "πŸš€ Generating Bruno API Collection for Private API..." + echo_info "πŸ“ Collection name: ${COLLECTION_NAME}" + echo_info "🌐 Base URL: ${BASE_URL}" + echo_info "πŸ‘€ Default user: ${DEFAULT_USERNAME} (${DEFAULT_EMAIL})" + echo "" + + # Get current directory or use provided path + local target_dir="${1:-$(pwd)}" + local base_dir="${target_dir}/${COLLECTION_NAME}" + + echo_info "πŸ“ Creating collection in: ${base_dir}" + echo "" + + # Create directory structure + create_directories "$base_dir" + echo "" + + # Generate all files + generate_files "$base_dir" + echo "" + + echo_success "Bruno collection generated successfully!" + echo "" + echo_info "πŸ“‹ Next steps:" + echo " 1. Open Bruno and import the collection" + echo " 2. Select the 'local' environment" + echo " 3. Update variables in environments/local.bru if needed" + echo " 4. Start testing with auth/debug-headers.bru" + echo "" + echo_info "πŸ”§ To customize:" + echo " - Edit environments/local.bru to change baseUrl, email, username" + echo " - Update gallery/artwork IDs in individual requests" + echo " - Modify request bodies to match your test data" + echo "" + echo_warning "πŸ’‘ Don't forget to update the OIDC headers if your user changes!" +} + +# Help function +show_help() { + echo "Bruno API Collection Generator" + echo "" + echo "Usage: $0 [target_directory]" + echo "" + echo "Arguments:" + echo " target_directory Directory where to create the collection (default: current directory)" + echo "" + echo "Examples:" + echo " $0 # Create in current directory" + echo " $0 ~/bruno # Create in ~/bruno directory" + echo " $0 /path/to/bruno # Create in specific path" + echo "" + echo "Configuration (edit script to change):" + echo " Collection name: ${COLLECTION_NAME}" + echo " Base URL: ${BASE_URL}" + echo " Default email: ${DEFAULT_EMAIL}" + echo " Default username: ${DEFAULT_USERNAME}" +} + +# Check for help flag +if [[ "$1" == "-h" || "$1" == "--help" ]]; then + show_help + exit 0 +fi + +# Run main function +main "$@"} diff --git a/docker-compose.yml b/docker-compose.yml index dc2882e..85a4f46 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,7 @@ services: public_api: build: - context: ./laravel + context: ./public depends_on: - keycloak - mysql @@ -104,3 +104,4 @@ volumes: postgres_data: mysql_data: + diff --git a/keyclock-setup.sh b/keyclock-setup.sh index 50fc58b..024b170 100755 --- a/keyclock-setup.sh +++ b/keyclock-setup.sh @@ -1,5 +1,4 @@ #!/bin/bash - # Variables KC_HOST="http://localhost:8080" REALM="master" @@ -7,6 +6,7 @@ CLIENT_ID="soa" CLIENT_SECRET="mysecret" USERNAME="alexis" PASSWORD="password" +PERSONAL_TOKEN="personaltoken" # Fonction d'attente wait_for_keycloak() { @@ -28,9 +28,16 @@ get_admin_token() { jq -r .access_token } +# GΓ©nΓ©rer une date d'expiration (1 an Γ  partir de maintenant) +generate_expiry_date() { + date -d "+1 year" --iso-8601=seconds +} + # CrΓ©er un realm, client et utilisateur setup_keycloak() { TOKEN=$(get_admin_token) + CURRENT_DATE=$(date --iso-8601=seconds) + EXPIRY_DATE=$(generate_expiry_date) echo "πŸ› οΈ CrΓ©ation du realm $REALM..." curl -s -X POST "$KC_HOST/admin/realms" \ @@ -48,28 +55,52 @@ setup_keycloak() { \"publicClient\": false, \"secret\": \"$CLIENT_SECRET\", \"redirectUris\": [\"*\"], - \"standardFlowEnabled\": true + \"standardFlowEnabled\": true, + \"serviceAccountsEnabled\": true, + \"authorizationServicesEnabled\": false }" > /dev/null - echo "πŸ‘€ CrΓ©ation de l'utilisateur $USERNAME..." + echo "πŸ‘€ CrΓ©ation de l'utilisateur $USERNAME avec token personnel..." curl -s -X POST "$KC_HOST/admin/realms/$REALM/users" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"username\": \"$USERNAME\", \"enabled\": true, + \"emailVerified\": true, + \"attributes\": { + \"api_token\": [\"$PERSONAL_TOKEN\"], + \"token_created\": [\"$CURRENT_DATE\"], + \"token_expires\": [\"$EXPIRY_DATE\"], + \"created_by\": [\"setup_script\"], + \"department\": [\"IT\"], + \"access_level\": [\"developer\"] + }, \"credentials\": [{ \"type\": \"password\", \"value\": \"$PASSWORD\", \"temporary\": false }] - }" > /dev/null + }" echo "βœ… Configuration terminΓ©e !" echo "πŸ” Utilisateur: $USERNAME / $PASSWORD" echo "πŸͺͺ Client secret: $CLIENT_SECRET" + echo "🎫 Personal Access Token: $PERSONAL_TOKEN" + echo "πŸ“… Token créé le: $CURRENT_DATE" + echo "⏰ Token expire le: $EXPIRY_DATE" +} + +# Fonction pour tester le token +test_personal_token() { + + echo "Pour accΓ©der Γ  une ressource protΓ©gΓ©e:" + echo "curl -X GET http://localhost:3000/api/protected" + echo " -H \"Authorization: Bearer $PERSONAL_TOKEN\"" + } # Lancer le setup wait_for_keycloak setup_keycloak +test_personal_token diff --git a/laravel/Dockerfile b/laravel/Dockerfile deleted file mode 100644 index 7692cbf..0000000 --- a/laravel/Dockerfile +++ /dev/null @@ -1,73 +0,0 @@ -# ---------- Stage 1: Build with Composer ---------- -FROM php:8.2-cli-alpine AS build - -WORKDIR /app - -# Install Composer and build dependencies -RUN apk add --no-cache \ - libzip-dev zip unzip curl git oniguruma-dev libxml2-dev - -# Install PHP extensions for Laravel -RUN docker-php-ext-install zip mbstring xml - -# Install Composer -RUN curl -sS https://getcomposer.org/installer | php && \ - mv composer.phar /usr/local/bin/composer - -# Copy project files and install dependencies -COPY . . -RUN composer install --no-dev --optimize-autoloader --no-interaction - - -# ---------- Stage 2: Production Image ---------- -FROM php:8.2-fpm-alpine - -# Set working directory -WORKDIR /var/www - -# Install system and PHP dependencies -RUN apk add --no-cache \ - nginx \ - supervisor \ - bash \ - mysql-client \ - libpng-dev \ - libjpeg-turbo-dev \ - freetype-dev \ - libxml2-dev \ - oniguruma-dev \ - libzip-dev \ - curl \ - git \ - openssh \ - php-pear \ - gcc g++ make autoconf libtool linux-headers - -# Install PHP extensions -RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \ - docker-php-ext-install pdo pdo_mysql mbstring gd xml zip && \ - pecl install redis && \ - docker-php-ext-enable redis - - -# Clean up build tools -RUN apk del gcc g++ make autoconf libtool - -# Install Ansible -RUN apk add --no-cache ansible -# Copy built app from previous stage -COPY --from=build /app /var/www - -# Set proper permissions for Laravel -RUN chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache /var/www/database && \ - chmod -R 755 /var/www/storage /var/www/bootstrap/cache /var/www/database - -# Copy config files -COPY nginx.conf /etc/nginx/nginx.conf -COPY supervisord.conf /etc/supervisord.conf - -# Expose HTTP port -EXPOSE 80 - -# Start services -CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] diff --git a/laravel/.editorconfig b/public/.editorconfig similarity index 100% rename from laravel/.editorconfig rename to public/.editorconfig diff --git a/laravel/.env.example b/public/.env.example similarity index 100% rename from laravel/.env.example rename to public/.env.example diff --git a/laravel/.gitattributes b/public/.gitattributes similarity index 100% rename from laravel/.gitattributes rename to public/.gitattributes diff --git a/laravel/.gitignore b/public/.gitignore similarity index 100% rename from laravel/.gitignore rename to public/.gitignore diff --git a/public/Dockerfile b/public/Dockerfile index ea1e2e4..7692cbf 100644 --- a/public/Dockerfile +++ b/public/Dockerfile @@ -1,9 +1,73 @@ -FROM python:3.11-slim +# ---------- Stage 1: Build with Composer ---------- +FROM php:8.2-cli-alpine AS build WORKDIR /app + +# Install Composer and build dependencies +RUN apk add --no-cache \ + libzip-dev zip unzip curl git oniguruma-dev libxml2-dev + +# Install PHP extensions for Laravel +RUN docker-php-ext-install zip mbstring xml + +# Install Composer +RUN curl -sS https://getcomposer.org/installer | php && \ + mv composer.phar /usr/local/bin/composer + +# Copy project files and install dependencies COPY . . +RUN composer install --no-dev --optimize-autoloader --no-interaction -RUN pip install redis flask flask_sqlalchemy pyjwt requests pymysql cryptography -CMD ["python", "app.py"] +# ---------- Stage 2: Production Image ---------- +FROM php:8.2-fpm-alpine +# Set working directory +WORKDIR /var/www + +# Install system and PHP dependencies +RUN apk add --no-cache \ + nginx \ + supervisor \ + bash \ + mysql-client \ + libpng-dev \ + libjpeg-turbo-dev \ + freetype-dev \ + libxml2-dev \ + oniguruma-dev \ + libzip-dev \ + curl \ + git \ + openssh \ + php-pear \ + gcc g++ make autoconf libtool linux-headers + +# Install PHP extensions +RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \ + docker-php-ext-install pdo pdo_mysql mbstring gd xml zip && \ + pecl install redis && \ + docker-php-ext-enable redis + + +# Clean up build tools +RUN apk del gcc g++ make autoconf libtool + +# Install Ansible +RUN apk add --no-cache ansible +# Copy built app from previous stage +COPY --from=build /app /var/www + +# Set proper permissions for Laravel +RUN chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache /var/www/database && \ + chmod -R 755 /var/www/storage /var/www/bootstrap/cache /var/www/database + +# Copy config files +COPY nginx.conf /etc/nginx/nginx.conf +COPY supervisord.conf /etc/supervisord.conf + +# Expose HTTP port +EXPOSE 80 + +# Start services +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] diff --git a/laravel/README.md b/public/README.md similarity index 100% rename from laravel/README.md rename to public/README.md diff --git a/laravel/app/Http/Controllers/Api/V1/ArtistController.php b/public/app/Http/Controllers/Api/V1/ArtistController.php similarity index 100% rename from laravel/app/Http/Controllers/Api/V1/ArtistController.php rename to public/app/Http/Controllers/Api/V1/ArtistController.php diff --git a/laravel/app/Http/Controllers/Api/V1/GalleryController.php b/public/app/Http/Controllers/Api/V1/GalleryController.php similarity index 100% rename from laravel/app/Http/Controllers/Api/V1/GalleryController.php rename to public/app/Http/Controllers/Api/V1/GalleryController.php diff --git a/laravel/app/Http/Controllers/Controller.php b/public/app/Http/Controllers/Controller.php similarity index 100% rename from laravel/app/Http/Controllers/Controller.php rename to public/app/Http/Controllers/Controller.php diff --git a/laravel/app/Http/Resources/ArtworkResource.php b/public/app/Http/Resources/ArtworkResource.php similarity index 100% rename from laravel/app/Http/Resources/ArtworkResource.php rename to public/app/Http/Resources/ArtworkResource.php diff --git a/laravel/app/Http/Resources/GalleryResource.php b/public/app/Http/Resources/GalleryResource.php similarity index 100% rename from laravel/app/Http/Resources/GalleryResource.php rename to public/app/Http/Resources/GalleryResource.php diff --git a/laravel/app/Http/Resources/UserResource.php b/public/app/Http/Resources/UserResource.php similarity index 100% rename from laravel/app/Http/Resources/UserResource.php rename to public/app/Http/Resources/UserResource.php diff --git a/laravel/app/Models/Artwork.php b/public/app/Models/Artwork.php similarity index 100% rename from laravel/app/Models/Artwork.php rename to public/app/Models/Artwork.php diff --git a/laravel/app/Models/Gallery.php b/public/app/Models/Gallery.php similarity index 100% rename from laravel/app/Models/Gallery.php rename to public/app/Models/Gallery.php diff --git a/laravel/app/Models/User.php b/public/app/Models/User.php similarity index 100% rename from laravel/app/Models/User.php rename to public/app/Models/User.php diff --git a/laravel/app/Providers/AppServiceProvider.php b/public/app/Providers/AppServiceProvider.php similarity index 100% rename from laravel/app/Providers/AppServiceProvider.php rename to public/app/Providers/AppServiceProvider.php diff --git a/laravel/artisan b/public/artisan similarity index 100% rename from laravel/artisan rename to public/artisan diff --git a/laravel/bootstrap/app.php b/public/bootstrap/app.php similarity index 100% rename from laravel/bootstrap/app.php rename to public/bootstrap/app.php diff --git a/laravel/bootstrap/cache/.gitignore b/public/bootstrap/cache/.gitignore similarity index 100% rename from laravel/bootstrap/cache/.gitignore rename to public/bootstrap/cache/.gitignore diff --git a/laravel/bootstrap/providers.php b/public/bootstrap/providers.php similarity index 100% rename from laravel/bootstrap/providers.php rename to public/bootstrap/providers.php diff --git a/laravel/composer.json b/public/composer.json similarity index 100% rename from laravel/composer.json rename to public/composer.json diff --git a/laravel/composer.lock b/public/composer.lock similarity index 100% rename from laravel/composer.lock rename to public/composer.lock diff --git a/laravel/config/app.php b/public/config/app.php similarity index 100% rename from laravel/config/app.php rename to public/config/app.php diff --git a/laravel/config/auth.php b/public/config/auth.php similarity index 100% rename from laravel/config/auth.php rename to public/config/auth.php diff --git a/laravel/config/cache.php b/public/config/cache.php similarity index 100% rename from laravel/config/cache.php rename to public/config/cache.php diff --git a/laravel/config/database.php b/public/config/database.php similarity index 100% rename from laravel/config/database.php rename to public/config/database.php diff --git a/laravel/config/filesystems.php b/public/config/filesystems.php similarity index 100% rename from laravel/config/filesystems.php rename to public/config/filesystems.php diff --git a/laravel/config/logging.php b/public/config/logging.php similarity index 100% rename from laravel/config/logging.php rename to public/config/logging.php diff --git a/laravel/config/mail.php b/public/config/mail.php similarity index 100% rename from laravel/config/mail.php rename to public/config/mail.php diff --git a/laravel/config/queue.php b/public/config/queue.php similarity index 100% rename from laravel/config/queue.php rename to public/config/queue.php diff --git a/laravel/config/services.php b/public/config/services.php similarity index 100% rename from laravel/config/services.php rename to public/config/services.php diff --git a/laravel/config/session.php b/public/config/session.php similarity index 100% rename from laravel/config/session.php rename to public/config/session.php diff --git a/laravel/database/.gitignore b/public/database/.gitignore similarity index 100% rename from laravel/database/.gitignore rename to public/database/.gitignore diff --git a/laravel/database/GalleryMember.php b/public/database/GalleryMember.php similarity index 100% rename from laravel/database/GalleryMember.php rename to public/database/GalleryMember.php diff --git a/laravel/database/factories/UserFactory.php b/public/database/factories/UserFactory.php similarity index 100% rename from laravel/database/factories/UserFactory.php rename to public/database/factories/UserFactory.php diff --git a/laravel/database/migrations/0001_01_01_000000_create_users_table.php b/public/database/migrations/0001_01_01_000000_create_users_table.php similarity index 100% rename from laravel/database/migrations/0001_01_01_000000_create_users_table.php rename to public/database/migrations/0001_01_01_000000_create_users_table.php diff --git a/laravel/database/migrations/0001_01_01_000001_create_cache_table.php b/public/database/migrations/0001_01_01_000001_create_cache_table.php similarity index 100% rename from laravel/database/migrations/0001_01_01_000001_create_cache_table.php rename to public/database/migrations/0001_01_01_000001_create_cache_table.php diff --git a/laravel/database/migrations/0001_01_01_000002_create_jobs_table.php b/public/database/migrations/0001_01_01_000002_create_jobs_table.php similarity index 100% rename from laravel/database/migrations/0001_01_01_000002_create_jobs_table.php rename to public/database/migrations/0001_01_01_000002_create_jobs_table.php diff --git a/laravel/database/migrations/2025_06_17_230516_create_artworks_table.php b/public/database/migrations/2025_06_17_230516_create_artworks_table.php similarity index 100% rename from laravel/database/migrations/2025_06_17_230516_create_artworks_table.php rename to public/database/migrations/2025_06_17_230516_create_artworks_table.php diff --git a/laravel/database/migrations/2025_06_17_230516_create_galleries_table.php b/public/database/migrations/2025_06_17_230516_create_galleries_table.php similarity index 100% rename from laravel/database/migrations/2025_06_17_230516_create_galleries_table.php rename to public/database/migrations/2025_06_17_230516_create_galleries_table.php diff --git a/laravel/database/migrations/2025_06_17_230516_create_gallery_members_table.php b/public/database/migrations/2025_06_17_230516_create_gallery_members_table.php similarity index 100% rename from laravel/database/migrations/2025_06_17_230516_create_gallery_members_table.php rename to public/database/migrations/2025_06_17_230516_create_gallery_members_table.php diff --git a/laravel/database/seeders/ArtworkSeeder.php b/public/database/seeders/ArtworkSeeder.php similarity index 100% rename from laravel/database/seeders/ArtworkSeeder.php rename to public/database/seeders/ArtworkSeeder.php diff --git a/laravel/database/seeders/DatabaseSeeder.php b/public/database/seeders/DatabaseSeeder.php similarity index 100% rename from laravel/database/seeders/DatabaseSeeder.php rename to public/database/seeders/DatabaseSeeder.php diff --git a/laravel/database/seeders/GalleryMemberSeeder.php b/public/database/seeders/GalleryMemberSeeder.php similarity index 100% rename from laravel/database/seeders/GalleryMemberSeeder.php rename to public/database/seeders/GalleryMemberSeeder.php diff --git a/laravel/database/seeders/GallerySeeder.php b/public/database/seeders/GallerySeeder.php similarity index 100% rename from laravel/database/seeders/GallerySeeder.php rename to public/database/seeders/GallerySeeder.php diff --git a/laravel/database/seeders/UserSeeder.php b/public/database/seeders/UserSeeder.php similarity index 100% rename from laravel/database/seeders/UserSeeder.php rename to public/database/seeders/UserSeeder.php diff --git a/laravel/nginx.conf b/public/nginx.conf similarity index 100% rename from laravel/nginx.conf rename to public/nginx.conf diff --git a/laravel/package-lock.json b/public/package-lock.json similarity index 100% rename from laravel/package-lock.json rename to public/package-lock.json diff --git a/laravel/package.json b/public/package.json similarity index 100% rename from laravel/package.json rename to public/package.json diff --git a/laravel/phpunit.xml b/public/phpunit.xml similarity index 100% rename from laravel/phpunit.xml rename to public/phpunit.xml diff --git a/laravel/public/.htaccess b/public/public/.htaccess similarity index 100% rename from laravel/public/.htaccess rename to public/public/.htaccess diff --git a/laravel/public/favicon.ico b/public/public/favicon.ico similarity index 100% rename from laravel/public/favicon.ico rename to public/public/favicon.ico diff --git a/laravel/public/index.php b/public/public/index.php similarity index 100% rename from laravel/public/index.php rename to public/public/index.php diff --git a/laravel/public/robots.txt b/public/public/robots.txt similarity index 100% rename from laravel/public/robots.txt rename to public/public/robots.txt diff --git a/laravel/resources/css/app.css b/public/resources/css/app.css similarity index 100% rename from laravel/resources/css/app.css rename to public/resources/css/app.css diff --git a/laravel/resources/js/app.js b/public/resources/js/app.js similarity index 100% rename from laravel/resources/js/app.js rename to public/resources/js/app.js diff --git a/laravel/resources/js/bootstrap.js b/public/resources/js/bootstrap.js similarity index 100% rename from laravel/resources/js/bootstrap.js rename to public/resources/js/bootstrap.js diff --git a/laravel/resources/views/welcome.blade.php b/public/resources/views/welcome.blade.php similarity index 100% rename from laravel/resources/views/welcome.blade.php rename to public/resources/views/welcome.blade.php diff --git a/laravel/routes/api.php b/public/routes/api.php similarity index 100% rename from laravel/routes/api.php rename to public/routes/api.php diff --git a/laravel/routes/console.php b/public/routes/console.php similarity index 100% rename from laravel/routes/console.php rename to public/routes/console.php diff --git a/laravel/routes/web.php b/public/routes/web.php similarity index 100% rename from laravel/routes/web.php rename to public/routes/web.php diff --git a/laravel/storage/app/.gitignore b/public/storage/app/.gitignore similarity index 100% rename from laravel/storage/app/.gitignore rename to public/storage/app/.gitignore diff --git a/laravel/storage/app/private/.gitignore b/public/storage/app/private/.gitignore similarity index 100% rename from laravel/storage/app/private/.gitignore rename to public/storage/app/private/.gitignore diff --git a/laravel/storage/app/public/.gitignore b/public/storage/app/public/.gitignore similarity index 100% rename from laravel/storage/app/public/.gitignore rename to public/storage/app/public/.gitignore diff --git a/laravel/storage/framework/.gitignore b/public/storage/framework/.gitignore similarity index 100% rename from laravel/storage/framework/.gitignore rename to public/storage/framework/.gitignore diff --git a/laravel/storage/framework/cache/.gitignore b/public/storage/framework/cache/.gitignore similarity index 100% rename from laravel/storage/framework/cache/.gitignore rename to public/storage/framework/cache/.gitignore diff --git a/laravel/storage/framework/cache/data/.gitignore b/public/storage/framework/cache/data/.gitignore similarity index 100% rename from laravel/storage/framework/cache/data/.gitignore rename to public/storage/framework/cache/data/.gitignore diff --git a/laravel/storage/framework/sessions/.gitignore b/public/storage/framework/sessions/.gitignore similarity index 100% rename from laravel/storage/framework/sessions/.gitignore rename to public/storage/framework/sessions/.gitignore diff --git a/laravel/storage/framework/testing/.gitignore b/public/storage/framework/testing/.gitignore similarity index 100% rename from laravel/storage/framework/testing/.gitignore rename to public/storage/framework/testing/.gitignore diff --git a/laravel/storage/framework/views/.gitignore b/public/storage/framework/views/.gitignore similarity index 100% rename from laravel/storage/framework/views/.gitignore rename to public/storage/framework/views/.gitignore diff --git a/laravel/storage/logs/.gitignore b/public/storage/logs/.gitignore similarity index 100% rename from laravel/storage/logs/.gitignore rename to public/storage/logs/.gitignore diff --git a/laravel/supervisord.conf b/public/supervisord.conf similarity index 100% rename from laravel/supervisord.conf rename to public/supervisord.conf diff --git a/laravel/tests/Feature/ExampleTest.php b/public/tests/Feature/ExampleTest.php similarity index 100% rename from laravel/tests/Feature/ExampleTest.php rename to public/tests/Feature/ExampleTest.php diff --git a/laravel/tests/TestCase.php b/public/tests/TestCase.php similarity index 100% rename from laravel/tests/TestCase.php rename to public/tests/TestCase.php diff --git a/laravel/tests/Unit/ExampleTest.php b/public/tests/Unit/ExampleTest.php similarity index 100% rename from laravel/tests/Unit/ExampleTest.php rename to public/tests/Unit/ExampleTest.php diff --git a/laravel/vite.config.js b/public/vite.config.js similarity index 100% rename from laravel/vite.config.js rename to public/vite.config.js diff --git a/setup-keycloak.sh b/setup-keycloak.sh deleted file mode 100755 index 309bc6a..0000000 --- a/setup-keycloak.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -# Wait for Keycloak to be ready -echo "Waiting for Keycloak to be ready..." -until curl -s http://localhost:8080/health/ready; do - echo "Waiting for Keycloak..." - sleep 5 -done - -# Get admin token -echo "Getting admin token..." -TOKEN=$(curl -s -X POST http://localhost:8080/realms/master/protocol/openid-connect/token \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "username=admin" \ - -d "password=admin" \ - -d "grant_type=password" \ - -d "client_id=admin-cli" | grep -o '"access_token":"[^"]*' | sed 's/"access_token":"//') - -# Create realm if it doesn't exist -echo "Creating realm..." -curl -s -X POST http://localhost:8080/admin/realms \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "realm": "soa", - "enabled": true - }' - -# Create client -echo "Creating client..." -curl -s -X POST http://localhost:8080/admin/realms/soa/clients \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "clientId": "soa", - "secret": "NuLgdHzPldRauqIln0I0TN5216PgX3Ty", - "redirectUris": ["https://api.local/*"], - "webOrigins": ["https://api.local"], - "publicClient": false, - "directAccessGrantsEnabled": true, - "serviceAccountsEnabled": true, - "standardFlowEnabled": true, - "implicitFlowEnabled": true, - "bearerOnly": false, - "consentRequired": false, - "protocol": "openid-connect" - }' - -# Create user in soa realm -echo "Creating user..." -curl -s -X POST http://localhost:8080/admin/realms/soa/users \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "username": "admin", - "enabled": true, - "credentials": [ - { - "type": "password", - "value": "admin", - "temporary": false - } - ] - }' - -echo "Setup completed!"