Skip to content

Commit 024d4cf

Browse files
committed
chore: prep repo
1 parent 037232e commit 024d4cf

19 files changed

Lines changed: 364 additions & 10 deletions

‎.dockerignore‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.gradle
2+
.idea
3+
build
4+
*.iml
5+
.git
6+
.gitignore
7+
HELP.md

‎.github/workflows/ci.yml‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
verify:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- uses: actions/setup-java@v4
17+
with:
18+
distribution: temurin
19+
java-version: "21"
20+
cache: gradle
21+
22+
- name: Run Gradle tests
23+
run: ./gradlew test
24+
25+
- name: Start local stack
26+
run: docker compose up --build -d postgres app
27+
28+
- name: Run Hurl suite
29+
run: ./scripts/run-hurl.sh
30+
31+
- name: Export OpenAPI document
32+
run: ./scripts/export-openapi.sh
33+
34+
- name: Upload docs artifact
35+
uses: actions/upload-artifact@v4
36+
with:
37+
name: docs-site
38+
path: docs-site
39+
40+
- name: Stop local stack
41+
if: always()
42+
run: docker compose down -v

‎.github/workflows/deploy-docs.yml‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: deploy-docs
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
deploy:
11+
runs-on: ubuntu-latest
12+
if: ${{ vars.CF_PAGES_PROJECT_NAME != '' }}
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- uses: actions/setup-node@v4
18+
with:
19+
node-version: "22"
20+
21+
- name: Start local stack
22+
run: docker compose up --build -d postgres app
23+
24+
- name: Run Hurl suite
25+
run: ./scripts/run-hurl.sh
26+
27+
- name: Export OpenAPI document
28+
run: ./scripts/export-openapi.sh
29+
30+
- name: Deploy docs site to Cloudflare Pages
31+
env:
32+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
33+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
34+
run: npx wrangler pages deploy docs-site --project-name "${{ vars.CF_PAGES_PROJECT_NAME }}"
35+
36+
- name: Stop local stack
37+
if: always()
38+
run: docker compose down -v

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ out/
3838

3939
### Claude Code ###
4040
.claude/
41+
42+
### Cloudflare ###
43+
.wrangler/

‎Dockerfile‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM eclipse-temurin:21-jdk AS build
2+
WORKDIR /workspace
3+
COPY gradlew gradlew
4+
COPY gradle gradle
5+
COPY build.gradle settings.gradle ./
6+
COPY src src
7+
RUN ./gradlew --no-daemon bootJar
8+
9+
FROM eclipse-temurin:21-jre
10+
WORKDIR /app
11+
COPY --from=build /workspace/build/libs/*.jar app.jar
12+
EXPOSE 8080
13+
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

‎README.md‎

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# RealWorld Spring Boot API
2+
3+
Backend implementation of the [RealWorld](https://github.com/realworld-apps/realworld)
4+
spec using Spring Boot 3, Java 21, PostgreSQL, Flyway, Docker, and Hurl.
5+
6+
This repository is built against the RealWorld backend contract. It is not
7+
presented here as an official RealWorld listing.
8+
9+
## Stack
10+
11+
- Java 21
12+
- Spring Boot 3
13+
- PostgreSQL
14+
- Flyway
15+
- Docker Compose
16+
- Hurl
17+
- OpenAPI via springdoc
18+
- Scalar API reference
19+
20+
## What is here
21+
22+
- RealWorld backend endpoints for auth, profiles, articles, comments, tags, favorites, and feed
23+
- Docker-based local stack with PostgreSQL
24+
- Hurl verification suite against the running API
25+
- Static docs site artifact for separate publishing
26+
27+
## API docs
28+
29+
The app serves an interactive reference at `/`, but live request execution is
30+
disabled so the page behaves like hosted documentation instead of an API client.
31+
32+
The repository also includes a static docs site in [`docs-site/`](./docs-site)
33+
that can be published separately, for example on `realworld.dhev.dev`.
34+
35+
## Local development
36+
37+
Run the full stack with Docker Compose:
38+
39+
```sh
40+
docker compose up --build -d postgres app
41+
```
42+
43+
The API will be available at `http://localhost:8080` and the app will use the
44+
Compose-managed PostgreSQL container instead of requiring a host-installed
45+
database.
46+
47+
Run the Hurl verification suite against the Compose stack:
48+
49+
```sh
50+
./scripts/run-hurl.sh
51+
```
52+
53+
Export the OpenAPI document for the static docs site:
54+
55+
```sh
56+
./scripts/export-openapi.sh
57+
```
58+
59+
Stop everything:
60+
61+
```sh
62+
docker compose down
63+
```
64+
65+
If you want to remove the local PostgreSQL volume too:
66+
67+
```sh
68+
docker compose down -v
69+
```
70+
71+
## Local build without Docker
72+
73+
The app can still run against any reachable PostgreSQL instance through env vars.
74+
Without overrides, it defaults to `jdbc:postgresql://localhost:5432/realworld`.
75+
76+
```sh
77+
./gradlew bootJar
78+
```
79+
80+
## CI and docs deploy
81+
82+
GitHub Actions includes:
83+
84+
- `ci.yml` to run Gradle tests, boot the Docker stack, run the Hurl suite, and export the OpenAPI document
85+
- `deploy-docs.yml` to deploy `docs-site/` to Cloudflare Pages
86+
87+
To use the docs deploy workflow, set:
88+
89+
- repository variable `CF_PAGES_PROJECT_NAME`
90+
- repository secrets `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`
91+
92+
Custom domain mapping such as `realworld.dhev.dev` is configured in Cloudflare
93+
Pages, not in this repository.
94+
95+
## Deploy
96+
97+
Use a Docker-capable web host and a managed PostgreSQL database.
98+
99+
Minimal environment:
100+
101+
```text
102+
SPRING_DATASOURCE_URL=jdbc:postgresql://<host>/<database>?sslmode=require
103+
SPRING_DATASOURCE_USERNAME=<username>
104+
SPRING_DATASOURCE_PASSWORD=<password>
105+
JWT_SECRET=<base64-encoded-strong-secret>
106+
```
107+
108+
For a quick public demo, use Render Free for the web service and Neon Free for
109+
PostgreSQL. The app reads the platform `PORT` env var automatically.

‎compose.yaml‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
services:
2+
postgres:
3+
image: postgres:16-alpine
4+
environment:
5+
POSTGRES_DB: realworld
6+
POSTGRES_USER: postgres
7+
POSTGRES_PASSWORD: postgres
8+
healthcheck:
9+
test: ["CMD-SHELL", "pg_isready -U postgres -d realworld"]
10+
interval: 5s
11+
timeout: 5s
12+
retries: 10
13+
volumes:
14+
- postgres-data:/var/lib/postgresql/data
15+
16+
app:
17+
build:
18+
context: .
19+
depends_on:
20+
postgres:
21+
condition: service_healthy
22+
environment:
23+
PORT: 8080
24+
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/realworld
25+
SPRING_DATASOURCE_USERNAME: postgres
26+
SPRING_DATASOURCE_PASSWORD: postgres
27+
JWT_SECRET: MkdhNr+BZydYUbnFnTMCd9ddPPVShVdsDT0322Y5hlQvb/Y6gnywB2TcsuqjdAO6AI4SxtV4Kcqduk8YT4SNSg==
28+
ports:
29+
- "8080:8080"
30+
31+
hurl:
32+
image: ghcr.io/orange-opensource/hurl:latest
33+
profiles: ["verify"]
34+
depends_on:
35+
postgres:
36+
condition: service_healthy
37+
app:
38+
condition: service_started
39+
volumes:
40+
- ./hurl:/hurl:ro
41+
entrypoint:
42+
- sh
43+
- -lc
44+
command: >
45+
uid=$$(date +%s%N) &&
46+
hurl --test
47+
--retry 30
48+
--retry-interval 1000
49+
--variable host=http://app:8080
50+
--variable uid=$$uid
51+
/hurl/*.hurl
52+
53+
volumes:
54+
postgres-data:

‎docs-site/index.html‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<title>RealWorld API Reference</title>
5+
<meta charset="utf-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
</head>
8+
<body>
9+
<script
10+
id="api-reference"
11+
data-url="./openapi.json"
12+
data-configuration='{
13+
"theme": "default",
14+
"forceDarkModeState": "dark",
15+
"hideModels": true,
16+
"expandAllResponses": true,
17+
"expandAllModelSections": true,
18+
"hideTestRequestButton": true,
19+
"hideClientButton": true,
20+
"documentDownloadType": "none",
21+
"showDeveloperTools": "never",
22+
"agent": { "disabled": true }
23+
}'
24+
></script>
25+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
26+
</body>
27+
</html>

‎docs-site/openapi.json‎

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

‎scripts/export-openapi.sh‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5+
OUTPUT_DIR="$ROOT_DIR/docs-site"
6+
OUTPUT_FILE="$OUTPUT_DIR/openapi.json"
7+
API_URL="${API_URL:-http://localhost:8080/v3/api-docs}"
8+
9+
mkdir -p "$OUTPUT_DIR"
10+
curl --fail --silent --show-error "$API_URL" > "$OUTPUT_FILE"
11+
echo "OpenAPI document written to $OUTPUT_FILE"

0 commit comments

Comments
 (0)