Bulletin board service.
Fork policy: this upstream repository is read-only. We do not review or merge pull requests and we do not accept infrastructure changes (Dockerfiles, Ansible roles, CI/CD workflows, etc.). To experiment or extend the project, fork it and work inside your own repository.
The default dev profile uses an in-memory H2 database and seeds 10 sample bulletins through DataInitializer, so the API works immediately after startup.
API documentation is available via Swagger UI at http://localhost:8080/swagger-ui/index.html.
- Backend (Spring Boot) lives in the repository root.
- Frontend (React Admin + Vite) is located in
frontend/. - Shared static assets for the backend are served from
src/main/resources/static(populated by the frontend build when needed).
Keep this structure in mind when running commands—backend tooling (gradlew, make run, tests) run from the root, frontend tooling (npm, vite) runs from frontend/.
Key variables are read directly by Spring Boot (see src/main/resources/application.yml and application-prod.yml for defaults):
| Variable | Description | Default |
|---|---|---|
SPRING_PROFILES_ACTIVE |
Active Spring profile (dev, prod, etc.) |
dev |
SPRING_DATASOURCE_URL |
JDBC URL for PostgreSQL in prod |
jdbc:postgresql://localhost:5432/bulletins |
SPRING_DATASOURCE_USERNAME |
DB username | postgres |
SPRING_DATASOURCE_PASSWORD |
DB password | postgres |
STORAGE_S3_BUCKET |
Bucket name for bulletin images | empty |
STORAGE_S3_REGION |
Region for the S3-compatible storage | empty |
STORAGE_S3_ENDPOINT |
Optional custom endpoint | empty |
STORAGE_S3_ACCESSKEY |
Access key ID | empty |
STORAGE_S3_SECRETKEY |
Secret key | empty |
STORAGE_S3_CDNURL |
Optional public CDN prefix | empty |
MANAGEMENT_SERVER_PORT |
Port for Spring Actuator endpoints (health, metrics, etc.) | 9090 |
JAVA_OPTS |
Extra JVM parameters (heap, -Dspring.profiles.active, etc.) |
empty |
All other variables supported by Spring Boot can be overridden the same way; check the application configuration files if you need to confirm a property name.
- JDK 21+.
- Gradle 9.2.1.
- PostgreSQL only if you run the
prodprofile with an external database. - Make.
- NodeJS 20+
-
Install prerequisites from the Requirements section.
-
From the repository root start the backend:
make run
-
Explore the API:
GET http://localhost:8080/api/bulletinsGET http://localhost:8080/api/bulletins?page=1&perPage=9&sort=createdAt&order=DESC&state=PUBLISHED&search=laptop- Swagger UI:
http://localhost:8080/swagger-ui/index.html
/api/bulletins accepts pagination (page, perPage), sorting (sort, order) and filters (state, search). Filters are processed via JPA Specifications so the same contract is available to the React Admin frontend.
-
Open a second terminal and move into the frontend directory:
cd frontend make install # npm install make start # Vite dev server on http://localhost:5173
-
The dev server proxies
/apirequests tohttp://localhost:8080, so keep the backend running.
-
Export the environment variables from the table above (DB access, S3 storage,
JAVA_OPTS, etc.). The defaults inapplication-prod.ymlshow the exact property names if you need to double-check. -
Build and launch the backend:
make build java -jar build/libs/project-devops-deploy-0.0.1-SNAPSHOT.jar
-
Serve the frontend either from the same JVM (see Build and serve from the Java app) or deploy it separately (any static hosting/CDN works once
frontend/distis uploaded).
JAVA_OPTS can be used to control heap size, GC, or add any -D system properties without editing the manifest.
See Makefile
-
Install Node.js 24 LTS (or newer) and npm.
-
Install dependencies and start the Vite dev server:
cd frontend make install make start -
The dev server proxies
/apirequests tohttp://localhost:8080, so keep the backend running viamake run(or./gradlew bootRun) in another terminal.
- Upload files via
POST /api/files/upload(multipart form field namedfile). - The response contains
keyand a temporaryurl. Persist thekeyin theimageKeyfield when creating or updating bulletins; the backend stores only that identifier. - When you need a fresh link, call
GET /api/files/view?key=...to receive a new URL (the backend issues presigned links on demand).
-
Build the production bundle:
cd frontend make install # run once make build # outputs to frontend/dist
-
Copy the compiled assets into Spring Boot’s static resources (served from
src/main/resources/static):rm -rf src/main/resources/static mkdir -p src/main/resources/static cp -R frontend/dist/* src/main/resources/static/ -
Restart the backend (
make run) and openhttp://localhost:8080/— the React app will now be served directly by the Java application.
Pass JVM flags via JAVA_OPTS:
docker run --rm -p 8080:8080 \
-e JAVA_OPTS="-Xms256m -Xmx512m -Dspring.profiles.active=prod" \
...Useful JVM options:
-Xms/-Xmx— set memory limits inside the container.-XX:+UseContainerSupport/-XX:ActiveProcessorCount(these respect cgroup limits by default).-Dspring.profiles.active=prod— switch the profile without recompiling.-Dlogging.level.root=INFOor Spring environment variables (SPRING_DATASOURCE_URL,STORAGE_S3_BUCKET, etc.) — configure external services.
- Application traffic still uses port
8080by default. Actuator endpoints (health, metrics, Prometheus scrape, logfile) listen onMANAGEMENT_SERVER_PORT(defaults to9090for every profile). Override it via env vars when you need a different port. - If your deployment does not include Prometheus/Grafana yet, you can ignore the management port entirely; the application starts normally even if nothing scrapes
/actuator. Simply avoid publishing the management port in Docker/Kubernetes until you need it. - When monitoring is enabled, expose both ports, e.g.
docker run -p 8080:8080 -p 9090:9090 ...and point Prometheus tohttp://<host>:9090/actuator/prometheus. - Health probes are available at
/actuator/health/livenessand/actuator/health/readiness; Grafana/Loki integrations should use the same port/env variable.
With the app running locally (make run), the management port defaults to http://localhost:9090. Useful URLs:
http://localhost:9090/actuator— index of exposed endpoints.http://localhost:9090/actuator/health,/actuator/health/liveness,/actuator/health/readiness— readiness/liveness probes.http://localhost:9090/actuator/metricsandhttp://localhost:9090/actuator/metrics/http.server.requests— raw Micrometer metrics.http://localhost:9090/actuator/prometheus— Prometheus scrape output (open in browser orcurlto confirm it renders).http://localhost:9090/actuator/logfile— current application log (same JSON that goes to stdout).
Override the host/port with MANAGEMENT_SERVER_PORT if you changed it; no Prometheus or Grafana instance is needed just to inspect these endpoints.
- The backend ships with
src/main/resources/logback-spring.xml, which writes structured JSON events tostdout. Every record containstimestamp,app,environment,instance,logger,thread, message arguments, MDC, and stack traces so Promtail/Loki (or any log shipper) can parse them without extra processing. - No extra variables are required, but you can supply a different configuration via Spring Boot’s standard options (
LOGGING_CONFIG,logging.config, or by overridinglogback-spring.xmlon the classpath). - Container runtimes should forward
stdout/stderrto your logging pipeline. Avoid redirecting logs to files unless your platform explicitly demands it.
- Start backend:
make run(uses in-memory H2 and local filesystem storage under/tmp/bulletin-images). - Start frontend dev server:
cd frontend && npm install && npm run dev. - In React Admin:
- Create a bulletin or edit an existing one.
- Use the “Upload image” field; after save, the image preview should load via the generated
imageUrl.
- Verify backend log: look for
Stored imageentries or check/tmp/bulletin-imagesfor a new file. Refresh the bulletin show page to ensure the presigned/local URL still renders.
- Ensure the S3-related variables from the table above (bucket, region, access/secret keys, optional endpoint/CDN URL) are exported alongside the
prodprofile settings. - Deploy backend (e.g.,
java -jar build/libs/project-devops-deploy-0.0.1-SNAPSHOT.jar). - In the frontend (local or deployed), upload an image for a bulletin.
- Confirm expected behavior:
- Response from
/api/files/uploadcontains a non-emptykey. - Image shows up in bulletin show view (URL should either point to CDN or be a presigned S3 link).
- Object exists in S3 bucket (check via AWS console or
aws s3 ls s3://your-bucket/bulletins/...).
- Response from
- Optional: run
curl -I "$(curl -s .../api/files/view?key=... | jq -r .url)"to ensure the presigned URL is valid from the production environment.