{"id":30738727,"url":"https://github.com/marynahl/pawtel-platform","last_synced_at":"2026-04-10T07:39:52.663Z","repository":{"id":312164366,"uuid":"1046559261","full_name":"MarynaHl/pawtel-platform","owner":"MarynaHl","description":"Two Spring Boot microservices for a “dog hotel” demo—auth (JWT) + data, Postgres, Docker Compose, tests.","archived":false,"fork":false,"pushed_at":"2025-08-28T22:24:10.000Z","size":13,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-29T03:43:48.905Z","etag":null,"topics":["docker","docker-compose","java","jwt","maven","microservice","postgres","rest-api","security","spring-boot"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MarynaHl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-28T21:40:03.000Z","updated_at":"2025-08-28T22:24:14.000Z","dependencies_parsed_at":"2025-08-29T03:43:56.405Z","dependency_job_id":"3c42557e-4036-4cd5-a9e0-7a8f32bf3527","html_url":"https://github.com/MarynaHl/pawtel-platform","commit_stats":null,"previous_names":["marynahl/pawtel-platform"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/MarynaHl/pawtel-platform","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarynaHl%2Fpawtel-platform","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarynaHl%2Fpawtel-platform/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarynaHl%2Fpawtel-platform/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarynaHl%2Fpawtel-platform/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MarynaHl","download_url":"https://codeload.github.com/MarynaHl/pawtel-platform/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarynaHl%2Fpawtel-platform/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273517598,"owners_count":25119801,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-03T02:00:09.631Z","response_time":76,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","docker-compose","java","jwt","maven","microservice","postgres","rest-api","security","spring-boot"],"created_at":"2025-09-03T22:02:52.144Z","updated_at":"2026-04-10T07:39:52.628Z","avatar_url":"https://github.com/MarynaHl.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pawtel Platform — Auth API + Data API (Docker Compose)\n\nTwo small Spring Boot services with a Postgres database, wired together via docker-compose.  \nThis meets the WinWin Travel backend mini test requirements.\n\n---\n\n## Architecture\n\n- auth-api (Service A)\n  - Spring Boot: Web, Security, JPA\n  - Endpoints:\n    - POST /api/auth/register – register user (email + password, BCrypt)\n    - POST /api/auth/login – login, returns JWT\n    - POST /api/process – protected via JWT; calls data-api and stores a log row\n  - Persists to Postgres: tables `users` and `processing_log`\n\n- data-api (Service B)\n  - Spring Boot: Web\n  - Endpoint:\n    - POST /api/transform – transforms text (reverse + uppercase)\n  - Accepts requests only when header `X-Internal-Token: \u003csecret\u003e` matches env\n\n- postgres\n  - DB name: `pawtel`, user: `pawtel`, password: `pawtel`\n\nBoth services run in one Docker network; auth-api reaches data-api at `http://data-api:8081`.\n\n---\n\n## Quick Start\n\nFrom the repository root:\n\n    # 1) (Optional) Build jars locally for faster docker builds\n    mvn -f auth-api/pom.xml clean package -DskipTests\n    mvn -f data-api/pom.xml clean package -DskipTests\n\n    # 2) Start everything\n    docker compose up -d --build\n\n    # 3) Check containers\n    docker compose ps\n\nExpected ports:\n- auth-api → http://localhost:8080  \n- data-api → http://localhost:8081  \n- Postgres → internal only (no host port binding required)\n\n---\n\n## How to Test\n\n1) Register a user\n\n    curl -i -X POST http://localhost:8080/api/auth/register \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\"email\":\"a@a.com\",\"password\":\"pass\"}'\n\n   Expected: HTTP/1.1 201 and `{\"message\":\"User registered successfully\"}`  \n   (If user already exists → 409 and `{\"message\":\"User already exists\"}`)\n\n2) Login and capture JWT\n\n    TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/login \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\"email\":\"a@a.com\",\"password\":\"pass\"}' | sed -E 's/.*\"token\":\"([^\"]+)\".*/\\1/')\n    echo \"$TOKEN\"\n\n3) Call protected /api/process\n\n    curl -i -X POST http://localhost:8080/api/process \\\n      -H \"Authorization: Bearer $TOKEN\" \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\"text\":\"hello world\"}'\n\n   Expected JSON:\n\n    {\"result\":\"DLROW OLLEH\"}\n\n4) (Optional) Call data-api directly to see the 403 without internal token\n\n    curl -i -X POST http://localhost:8081/api/transform \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\"text\":\"hello\"}'     # → 403\n\n   With the internal token:\n\n    curl -i -X POST http://localhost:8081/api/transform \\\n      -H \"Content-Type: application/json\" \\\n      -H \"X-Internal-Token: topsecret\" \\\n      -d '{\"text\":\"hello\"}'     # → {\"result\":\"OLLEH\"}\n\n5) Verify processing log in Postgres\n\n    docker compose exec -it postgres psql -U pawtel -d pawtel -c \\\n    \"select id, user_id, input_text, output_text, created_at\n     from processing_log\n     order by created_at desc\n     limit 5;\"\n\n---\n\n## Configuration\n\nEnvironment variables are provided in `docker-compose.yml`:\n\n- auth-api\n  - POSTGRES_URL=jdbc:postgresql://postgres:5432/pawtel\n  - POSTGRES_USER=pawtel\n  - POSTGRES_PASSWORD=pawtel\n  - JWT_SECRET=mysecretkey\n  - INTERNAL_TOKEN=topsecret\n  - DATA_API_URL=http://data-api:8081\n\n- data-api\n  - INTERNAL_TOKEN=topsecret\n\n- postgres\n  - POSTGRES_DB=pawtel\n  - POSTGRES_USER=pawtel\n  - POSTGRES_PASSWORD=pawtel\n\nApp properties (resolved from env in containers):\n\n- auth-api `/src/main/resources/application.properties`:\n\n    spring.datasource.url=${POSTGRES_URL:jdbc:postgresql://localhost:5433/pawtel}\n    spring.datasource.username=${POSTGRES_USER:pawtel}\n    spring.datasource.password=${POSTGRES_PASSWORD:pawtel}\n    spring.jpa.hibernate.ddl-auto=update\n    spring.jpa.show-sql=true\n\n    jwt.secret=${JWT_SECRET:mysecretkey}\n\n    app.data-api-url=${DATA_API_URL:http://data-api:8081}\n    app.internal-token=${INTERNAL_TOKEN:topsecret}\n\n- data-api `/src/main/resources/application.properties`:\n\n    server.port=8081\n    app.internal-token=${INTERNAL_TOKEN:topsecret}\n\n---\n\n## Endpoints\n\n- auth-api\n  - POST /api/auth/register → 201 | 409\n  - POST /api/auth/login → 200 {\"token\":\"...\"} | 401\n  - POST /api/process (JWT) → 200 {\"result\":\"...\"} | 401\n\n- data-api\n  - POST /api/transform (requires `X-Internal-Token`) → 200 {\"result\":\"...\"} | 403\n\n---\n\n## Docker\n\n- docker-compose.yml (root) starts three services: postgres, auth-api, data-api\n- Each service has its own Dockerfile:\n  - `auth-api/Dockerfile` builds with Maven, then runs with Temurin JRE 21\n  - `data-api/Dockerfile` same pattern; exposes 8081\n\nTypical lifecycle:\n\n    # rebuild one service\n    docker compose build auth-api\n    docker compose up -d auth-api\n\n    # logs\n    docker compose logs -f auth-api\n    docker compose logs -f data-api\n    docker compose logs -f postgres\n\n    # stop / remove\n    docker compose down\n    # drop volumes as well (removes DB data)\n    docker compose down -v\n\n---\n\n## Troubleshooting\n\n- Port already in use (8080/8081/5432)\n  \n      lsof -nP -iTCP:8081 | grep LISTEN\n      kill -9 \u003cPID\u003e\n      docker compose up -d --build\n\n- 401 on `/api/process`  \n  Use a **fresh** token and include header: `Authorization: Bearer \u003ctoken\u003e`.\n\n- 403 on `/api/transform`  \n  Include header `X-Internal-Token` and make sure it matches both services’ `INTERNAL_TOKEN`.\n\n- DB table empty  \n  Ensure you actually invoked `/api/process` successfully and that `spring.jpa.hibernate.ddl-auto=update` is set.\n\n---\n\n## Acceptance Criteria Mapping\n\n- Register/login with BCrypt + JWT ✅  \n- Protected `/process` calls Service B with `X-Internal-Token` ✅  \n- Service B rejects requests without/with wrong token ✅  \n- Service A stores `processing_log` rows in Postgres ✅  \n- All runs via `docker compose up` and tested with `curl` ✅\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarynahl%2Fpawtel-platform","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarynahl%2Fpawtel-platform","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarynahl%2Fpawtel-platform/lists"}