Full-stack Confluence Publisher App implementing the course project (Chapter 06).
- Backend: Spring Boot 3.4, JPA/Hibernate, SQLite
- Frontend: Angular 20 + TypeScript + TailwindCSS
- Java 21+
- Gradle 8+ (or use included gradlew)
- Node.js 18+ and npm
- Start backend
cd backend
./gradlew bootRun- Frontend
cd frontend
npm install
npm start
# or
ng serveOpen http://localhost:4200 and ensure the backend is running on http://localhost:8080.
backend/
src/main/java/com/confluence/publisher/
controller/ # REST controllers (Page, Attachment, Schedule, Confluence, AI)
service/ # Business logic (PageService, AttachmentService, PublishService, ScheduleService)
repository/ # JPA repositories
entity/ # JPA entities (Page, Attachment, Schedule, PublishLog, etc.)
dto/ # Request/Response DTOs
provider/ # Provider adapter pattern (BaseProvider, ConfluenceStubProvider)
scheduler/ # Background scheduler for queued pages
config/ # Spring configuration and properties
exception/ # Global exception handling
src/main/resources/
application.yml # Application configuration
frontend/
src/pages/ # ComposePage, Schedules
src/app/ # app component and routing
data/ # sqlite database (gitignored)
storage/attachments/ # uploaded files (gitignored)
- Health: GET /api/health
- Upload attachment: POST /api/attachments (multipart: file, description?)
- Create page: POST /api/pages { title, content, spaceKey, attachmentIds }
- Publish now: POST /api/confluence/publish { pageId }
- Schedule: POST /api/schedules { pageId, scheduledAt? }
- List schedules: GET /api/schedules
cd backend
./gradlew test- The Confluence provider is a stub by default; configure real Confluence API integration for production.
- For production, configure a proper database and storage, secure API tokens, and harden CORS.
- Confluence URL and space configuration available via environment variables.
The repo includes Dockerfiles for backend and frontend and a docker-compose.yml for local runs with Docker or Podman.
- Build and run
docker compose up --build
- Open
- Frontend: http://localhost:4200
- Backend: http://localhost:8080/api/health
- Stop / clean
docker compose down # stop containers docker compose down -v # also remove volumes (DB/attachments)
- macOS hosts require the Podman VM:
podman machine start
- Build and run
podman-compose -f podman-compose.yaml up --build
- Open
- Frontend: http://localhost:4200
- Backend: http://localhost:8080/api/health
- Stop / clean
podman-compose -f podman-compose.yaml down podman-compose -f podman-compose.yaml down -v
Note: Both containers run as non-root users for rootless Podman compatibility. Nginx listens on port 8080 (non-privileged) inside the container.
- backend
- Image: built from backend/Dockerfile (Spring Boot 3.4, JDK 21)
- Port: 8080:8080 (host:container)
- Runs as non-root user (
appuser, UID 1000) - Volumes: named
data(SQLite at /data/app.db),attachments(/storage/attachments) - Env (loaded from
.envfile):CONFLUENCE_URL,CONFLUENCE_USERNAME,CONFLUENCE_API_TOKENCONFLUENCE_DEFAULT_SPACE,CONFLUENCE_PROVIDERSCHEDULER_INTERVAL_SECONDS,CORS_ORIGINS
- frontend
- Image: built from frontend/Dockerfile (Angular 20, nginx)
- Port: 4200:8080 (host:container, nginx on non-privileged port)
- Build arg:
NG_APP_API_BASE=http://localhost:8080so the browser calls the backend via host port 8080.
- Backend environment variables (Spring Boot properties with
APP_prefix):APP_DATABASE_URL(defaultjdbc:sqlite:./data/app.dbin dev,jdbc:sqlite:///data/app.dbin container)APP_ATTACHMENT_DIR(defaultstorage/attachments, container uses/storage/attachments)APP_CONFLUENCE_URL(Confluence instance URL)APP_CONFLUENCE_DEFAULT_SPACE(default Confluence space key)APP_CONFLUENCE_API_TOKEN(API token for authentication)APP_PROVIDER(confluence-stubby default)APP_SCHEDULER_INTERVAL_SECONDS(default5)APP_CORS_ORIGINScomma-separated list of allowed origins
- Frontend API base
- Set at build time via compose
build.args.NG_APP_API_BASE(defaulthttp://localhost:8080). - Change when deploying behind another host/port, then rebuild the frontend image.
- Set at build time via compose
# Backend image
docker build -t confluence-backend -f backend/Dockerfile .
docker run --rm -p 8080:8080 \
-e APP_DATABASE_URL=jdbc:sqlite:///data/app.db \
-e APP_ATTACHMENT_DIR=/storage/attachments \
-v confluence_data:/data -v confluence_attachments:/storage/attachments \
confluence-backend
# Frontend image
docker build -t confluence-frontend \
--build-arg NG_APP_API_BASE=http://localhost:8080 \
-f frontend/Dockerfile .
docker run --rm -p 4200:8080 confluence-frontend- Compose uses named volumes:
datafor the SQLite DB andattachmentsfor uploaded files. - Remove volumes with
docker compose down -v(orpodman compose down -v). This will delete your DB and uploaded attachments.
- Port already in use
- Change host ports in docker-compose.yml (e.g.,
8081:8080for backend,4201:8080for frontend).
- Change host ports in docker-compose.yml (e.g.,
- CORS blocked
- Add your origin to
CORS_ORIGINSin.envfile and recreate containers.
- Add your origin to
- Frontend cannot reach backend
- Ensure backend is mapped to host port 8080 and frontend is built with
NG_APP_API_BASEpointing tohttp://localhost:8080. - Rebuild the frontend image after changing
NG_APP_API_BASE.
- Ensure backend is mapped to host port 8080 and frontend is built with
- Podman on SELinux hosts (Linux)
- If you bind host directories instead of volumes, append
:Zto volume mounts for proper labeling.
- If you bind host directories instead of volumes, append
- macOS Podman
- Ensure
podman machine startbefore running compose commands.
- Ensure
- Slow or stale frontend
- Rebuild frontend:
docker compose build frontend(or Podman equivalent).
- Rebuild frontend:
- Overview: doc/course/README.md
- Chapters