diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 6392f2fe6..2ecf3c437 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,10 +1,13 @@ -FROM mcr.microsoft.com/devcontainers/java:17 +FROM eclipse-temurin:17-jdk-jammy -RUN su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle " +RUN apt-get update -y \ + && apt-get install -y curl wget tar git unzip ca-certificates gnupg \ + && apt-get clean && rm -rf /var/lib/apt/lists/* -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends +ENV GRADLE_USER_HOME=/home/gradle +RUN useradd -ms /bin/bash dev \ + && mkdir -p ${GRADLE_USER_HOME} \ + && chown -R dev:dev ${GRADLE_USER_HOME} -# [Optional] Uncomment this line to install global node packages. -# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 \ No newline at end of file +USER dev +WORKDIR /workspace diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 16642354b..66a4f20b5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,37 +1,31 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/java-postgres { - "name": "Java & PostgreSQL", - "dockerComposeFile": "docker-compose.yml", - "service": "app", - "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", - // Features to add to the dev container. More info: https://containers.dev/features. - "features": { - "ghcr.io/devcontainers/features/java:1.6.0": { - "installGradle": "true" - } - }, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // This can be used to network with other containers or with the host. - "forwardPorts": [ - 1080, // Mailcatcher - 5432, // PostgreSQL - 8080, // Events - 8082 // Adminer + "name": "events-dev", + "dockerComposeFile": [ + "../docker-compose.yml", + "docker-compose.yml" ], - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "gradle bootRun", - // Configure tool-specific properties. - // "customizations": {}, - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - "remoteUser": "root", + "service": "app", + "workspaceFolder": "/workspace", + "overrideCommand": true, "customizations": { "vscode": { "extensions": [ - "vmware.vscode-boot-dev-pack", - "vscjava.vscode-gradle", - "vscjava.vscode-java-pack" - ] + "vscjava.vscode-java-pack", + "redhat.java", + "vscjava.vscode-gradle" + ], + "settings": { + "java.configuration.runtimes": [ + { + "name": "JavaSE-17", + "path": "/opt/java/openjdk" + } + ], + "java.jdt.ls.java.home": "/opt/java/openjdk" + } } - } + }, + "forwardPorts": [3090], + "postCreateCommand": "git config --global --add safe.directory /workspace || true", + "remoteUser": "dev" } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index ca18713e7..a8c3c59a3 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,49 +1,25 @@ -version: "3.8" - -volumes: - postgres-data: - services: app: - container_name: javadev build: context: . - dockerfile: Dockerfile - environment: - SPRING_PROFILES_ACTIVE: devcontainer - - volumes: - - ../..:/workspaces:cached - - # Overrides default command so things don't shut down after the process ends. + dockerfile: .devcontainer/Dockerfile + container_name: events-app + working_dir: /workspace command: sleep infinity - - # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. - network_mode: service:postgres - - depends_on: - - postgres - - mailcatcher - - postgres: - image: docker.io/library/postgres:latest - restart: unless-stopped + init: true volumes: - - postgres-data:/var/lib/postgresql/data + - .:/workspace:cached + - gradle-cache:/home/dev/.gradle + - m2-cache:/home/dev/.m2 environment: - POSTGRES_USER: postgres - POSTGRES_DB: events - POSTGRES_HOST_AUTH_METHOD: trust - - adminer: - image: docker.io/library/adminer:latest - restart: always - depends_on: - - postgres + SPRING_PROFILES_ACTIVE: dev,devcontainer + depends_on: + - db + - mailcatcher + - oidc ports: - - "8082:8080" + - "3090:3090" - mailcatcher: - image: docker.io/schickling/mailcatcher:latest - restart: on-failure - network_mode: service:postgres +volumes: + gradle-cache: + m2-cache: diff --git a/.run/Application [dev].run.xml b/.run/Application [dev].run.xml new file mode 100644 index 000000000..bc36cb196 --- /dev/null +++ b/.run/Application [dev].run.xml @@ -0,0 +1,16 @@ + + + + diff --git a/.run/Application [devcontainer].run.xml b/.run/Application [devcontainer].run.xml new file mode 100644 index 000000000..ff504e3d3 --- /dev/null +++ b/.run/Application [devcontainer].run.xml @@ -0,0 +1,16 @@ + + + + diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..16db22bc2 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Application [dev]", + "request": "launch", + "mainClass": "ch.wisv.events.EventsApplication", + "args": "--spring.profiles.active=dev" + }, + { + "type": "java", + "name": "Application [devcontainer]", + "request": "launch", + "mainClass": "ch.wisv.events.EventsApplication", + "args": "--spring.profiles.active=dev,devcontainer" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 954c0f82c..000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "bootRun", - "type": "shell", - "command": "gradle bootRun" - } - ] -} \ No newline at end of file diff --git a/README.md b/README.md index 5cd705fb4..e3e06bc87 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,95 @@ Uses [WISVCH/bootstrap-theme](https://github.com/WISVCH/bootstrap-theme) for sty All the documentation is on [https://wisvch.github.io/events/](https://wisvch.github.io/events/). +## Development + +You can work on Events in three different ways: + +- **Dev Container** (containerized workspace + dependencies). +- **Bare metal development with services in Docker** (local IDE + `docker compose` dependencies). +- **All bare metal** (manual local setup of all dependencies). + +### Option 1 – Dev Container (VS Code or IntelliJ) + +1. **Prerequisites** + - Docker Desktop (Linux users: Docker Engine + Docker Compose v2). + - For VS Code: install the *Dev Containers* extension. + For IntelliJ IDEA: install JetBrains Gateway with the *Dev Containers* plugin. +2. **Open the workspace** + - VS Code: `Dev Containers: Rebuild and Reopen in Container`. + - IntelliJ: `File ▸ Open… ▸ .devcontainer/devcontainer.json` via Gateway. +3. The dev container boots the project plus all dependencies using the included Compose files. No local tooling besides Docker is required. +4. **Run the application** with the supplied run configurations: + - IntelliJ: select `Application [devcontainer]` in the dropdown at the top-right, then click the green play button. + - VS Code: `Run and Debug ▸ Application [devcontainer]` (Not `Application [dev]`). + +While the container is running: + +| Service | Host URL / Port | Notes | +| --- | --- | --- | +| Events app | http://localhost:3090/events | Main web app | +| PostgreSQL 15 | localhost:35433 | Primary database (`postgres/postgres`) | +| pgAdmin | http://localhost:3091 | `admin@example.com` / `admin` | +| Mailcatcher UI | http://localhost:3092 | Test inbox | +| Mailcatcher SMTP | localhost:3588 | SMTP endpoint for dev mail | +| Mock OIDC | http://localhost:3093 | Login provider for local auth flow | + +The mock OIDC service includes two ready-made accounts: + +| Username | Password | Roles | +| --- | --- | --- | +| `constantijn` | `pwd` | Admin (beheer) | +| `christiaan` | `pwd` | Standard user | + +### Option 2 – Docker Compose Dependencies (Local IDE) + +1. Install JDK 17, Docker Desktop/Compose, and your preferred editor. +2. Start the shared services: + + ```bash + docker compose up -d db pgadmin mailcatcher oidc + ``` + + (Stop them with `docker compose down` when you are done.) +3. Open the project in VS Code or IntelliJ. The project is already Gradle based, so it will import automatically. +4. Run the backend with the supplied run configurations: + - IntelliJ: `Run ▸ Run 'Application [dev]'`. + - VS Code: `Run and Debug ▸ Application [dev]`. + +All services listen on the same host ports listed in the table above. + +### Option 3 – Manual Setup + +Configure everything yourself only if you cannot run Docker: + +1. **PostgreSQL** – create a database named `events` reachable via `localhost:35433` (or update the connection in `config/application-dev.yml`). +2. **Mail** – run a local SMTP server or update `spring.mail.*` in `config/application-dev.yml` with your provider. +3. **OIDC provider** – register a Keycloak or other OIDC issuer and update: + + ```yaml + spring: + security: + oauth2: + client: + registration: + wisvchconnect: + client-id: + client-secret: + provider: + wisvchconnect: + issuer-uri: https://login.ch.tudelft.nl/realms/wisvch + ``` + +4. **Mollie/CH Pay** – set `mollie.apikey`, and update `wisvch.chpay.*` endpoints/keys as needed. +5. **Base URL** – match `server.port` (usually `3090`) and `server.servlet.context-path` (`/events`). +6. Once the configuration is in place, build and run with: + + ```bash + ./gradlew bootRun --args='--spring.profiles.active=dev' + ``` + + or reuse the IDE run configurations. + ## Contribution If you want to contribute, awesome! First, pick an issue and self-assign it. Make your changes in a new branch, with the following naming convention: @@ -18,4 +107,4 @@ Implementing a new feature? > "feature_short-description-of-feature" Once you're satisfied with your changes, create a pull request and give it the label "ready for inspection". You can assign any of the maintainers in specific or wait for someone to pick it up. Make sure to include tests and documentation. -Got stuck with your implementation? Create a pull request with an explanation and give it the label "help wanted". \ No newline at end of file +Got stuck with your implementation? Create a pull request with an explanation and give it the label "help wanted". diff --git a/config/.gitignore b/config/.gitignore index fae361353..5fddaf145 100644 --- a/config/.gitignore +++ b/config/.gitignore @@ -4,4 +4,5 @@ !.gitignore !application.yml.example !application-test.yml +!application-dev.yml !application-devcontainer.yml diff --git a/config/application-dev.yml b/config/application-dev.yml new file mode 100644 index 000000000..fea652d12 --- /dev/null +++ b/config/application-dev.yml @@ -0,0 +1,100 @@ +spring: + datasource: + driverClassName: org.postgresql.Driver + url: jdbc:postgresql://localhost:35433/events + username: postgres + password: postgres + + data.jpa.repositories.enabled: true + + flyway: + enabled: false + + jpa: + show-sql: false + database-platform: org.hibernate.dialect.PostgreSQLDialect + generate-ddl: true + hibernate.ddl-auto: update + properties.hibernate.default_schema: public + + thymeleaf: + prefix: file:src/main/resources/templates/ + cache: false + check-template-location: true + suffix: .html + mode: HTML + encoding: UTF-8 + content-type: text/html + + web: + resources: + static-locations: file:src/main/resources/static/ + cache: + cachecontrol: + max-age: 0 + no-cache: true + no-store: true + + mail: + host: localhost + port: 3588 + username: events.devcontainer@mailcatcher.local + password: "" + properties: + mail.smtp.auth: false + mail.smtp.starttls.enable: false + mail.smtp.starttls.required: false + + security: + oauth2: + client: + provider: + wisvchconnect: + issuer-uri: http://localhost:3093 + clientUri: http://localhost:3090/events + registration: + wisvchconnect: + client-id: events.devcontainer + client-secret: + scope: + - openid + - profile + - email + - roles + +logging.level.web: DEBUG + +server: + port: 3090 + servlet.context-path: /events + +wisvch.connect: + admin-groups: + - bestuur + - vc + - beheer + claimName: groups + +wisvch.events: + image.path: http://localhost:3090/events/api/v1/documents/ + +wisvch.chpay: + uri: http://localhost:3080/api/v1 + api-key: "CHANGE_ME" + clientUri: http://localhost:3090/events + +mollie: + apikey: test + clientUri: http://localhost:3090/events + +links: + gtc: https://ch.tudelft.nl/wp-content/uploads/Deelnemersvoorwaarden_versie_12_06_2023.pdf + passes: https://ch.tudelft.nl/passes + +googleWallet: + serviceKeyPath: ./googleservicekey.json + issuerId: 3388000000022297569 + origin: http://localhost:3090/events + baseUrl: https://ch.tudelft.nl/events + +administrationCosts: 0.35 diff --git a/config/application-devcontainer.yml b/config/application-devcontainer.yml index 6ce639c31..01343b11e 100644 --- a/config/application-devcontainer.yml +++ b/config/application-devcontainer.yml @@ -1,54 +1,9 @@ -############## Production properties ############################################################# -# This is the default property file which can be used as a template for default configuration. # -# Copy this file to `/config/application.properties`, with the runnable jar in the `/` directory # -################################################################################################## - spring: datasource: - # DATABASE PROPERTIES - driverClassName: org.postgresql.Driver - # Change with the name of your database name - url: jdbc:postgresql://localhost:5432/events - - # Username to log in to the database - # It's recommended to create a dedicated Database user instead of using the default postgres user - username: postgres - - # Password to log in with in database - password: - - data.jpa.repositories.enabled: true - - flyway: - enabled: false - - jpa: - # Set to false in production to prevent spamming of console - show-sql: false - hibernate.type: trace - type: trace - database-platform: org.hibernate.dialect.PostgreSQLDialect - generate-ddl: true - hibernate.ddl-auto: update - - # This setting defines the database creation/update behaviour. Change with caution. - properties.hibernate.default_schema: public - - thymeleaf: - # THYMELEAF (ThymeleafAutoConfiguration) - check-template-location: true - suffix: .html - mode: HTML - encoding: UTF-8 - content-type: text/html - - web: - resources: - static-locations[0]: "file:src/main/resources/static/" - static-locations[1]: "classpath:/static/" + url: jdbc:postgresql://db:5432/events mail: - host: 127.0.0.1 + host: mailcatcher port: 1025 security: @@ -56,60 +11,5 @@ spring: client: provider: wisvchconnect: - issuer-uri: https://connect.ch.tudelft.nl - clientUri: http://localhost:8080/events - registration: - wisvchconnect: - client-id: events-devcontainer - client-secret: AN6tZXQea8gFFfDgs_hoUDeiBXPh6CZuvZFE9kBgxzQ2MoHgVzWkPrtSMrNpn75Ems9v4iaz6gRFf0ECY4Q8YEE - scope: - - auth - - profile - - email - - openid - - auth - -logging.level.web: DEBUG - -# Spring mail -# Database migration automation. Set to false for development - -# Serve connect -server: - port: 8080 - servlet.context-path: / - -# CH Connect Configuration -wisvch.connect: - # Change to a Google group you're in (e.g. hoothub, dienst2, etc.) - admin-groups: - - hoothub - - beheer - claimName: google_groups - -# CH Events Configuration -wisvch.events: - image.path: http://localhost:8080/api/v1/documents/ - -# CH Pay Configuration -wisvch.chpay: - uri: http://localhost:8080/api/v1 - api-key: test - clientUri: http://localhost:8080/events/ - -# CH mollie api key -mollie: - apikey: test - clientUri: http://localhost:8080 - -links: - gtc: https://ch.tudelft.nl/wp-content/uploads/General_terms_and_conditions-4.pdf - passes: https://ch.tudelft.nl/passes - -googleWallet: - serviceKeyPath: ./googleservicekey.json - issuerId: 3388000000022297569 - origin: http://localhost:8080 - baseUrl: https://ch.tudelft.nl/events - -administrationCosts: 0.35 \ No newline at end of file + issuer-uri: http://oidc:8080 + authorization-uri: http://localhost:3093/connect/authorize diff --git a/config/application-test.yml b/config/application-test.yml index 58161de78..a988572bf 100644 --- a/config/application-test.yml +++ b/config/application-test.yml @@ -71,7 +71,7 @@ wisvch.events: wisvch.chpay: uri: http://localhost:8080/api/v1 api-key: - clientUri: http://localhost:8080/events/ + clientUri: http://localhost:8080/events # CH mollie api key mollie: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..5f1e64746 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,144 @@ +services: + db: + image: postgres:15 + container_name: events-db + environment: + POSTGRES_DB: events + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + volumes: + - db-data:/var/lib/postgresql/data + ports: + - "35433:5432" + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U postgres -d events" ] + interval: 5s + timeout: 5s + retries: 20 + + pgadmin: + image: dpage/pgadmin4:9 + container_name: events-pgadmin + entrypoint: ["/bin/sh", "/bootstrap-pgpass.sh"] + environment: + PGADMIN_DEFAULT_EMAIL: admin@example.com + PGADMIN_DEFAULT_PASSWORD: admin + PGADMIN_CONFIG_SESSION_COOKIE_NAME: "'events_pga4_session'" + PGADMIN_SERVER_JSON_FILE: /pgadmin4/servers.json + PGADMIN_REPLACE_SERVERS_ON_STARTUP: "True" + PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False" + PGADMIN_CONFIG_SERVER_MODE: "False" + depends_on: + - db + ports: + - "3091:80" + volumes: + - pgadmin-data:/var/lib/pgadmin + - ./pgadmin/servers.json:/pgadmin4/servers.json:ro + - ./pgadmin/pgpassfile:/pgpassfile.template:ro + - ./pgadmin/bootstrap-pgpass.sh:/bootstrap-pgpass.sh:ro + + mailcatcher: + image: dockage/mailcatcher:latest + container_name: events-mailcatcher + hostname: mailcatcher + ports: + - "3092:1080" + - "3588:1025" + + oidc: + image: xdevsoftware/oidc-server-mock:1.2 + container_name: events-oidc + ports: + - "3093:8080" + environment: + ASPNETCORE_ENVIRONMENT: Development + + SERVER_OPTIONS_INLINE: | + { + "AccessTokenJwtType": "JWT", + "Discovery": { "ShowKeySet": true }, + "Authentication": { "CookieSameSiteMode": "Lax", "CheckSessionCookieSameSiteMode": "Lax" } + } + + IDENTITY_RESOURCES_INLINE: | + - Name: student + ClaimTypes: + - netid + - study + - student_number + - Name: auth + ClaimTypes: + - google_groups + - google_username + - Name: organization + ClaimTypes: + - organization + - Name: roles + ClaimTypes: + - role + - groups + + USERS_CONFIGURATION_INLINE: | + [ + { + "SubjectId": "WISVCH.0066", + "Username": "christiaan", + "Password": "pwd", + "Claims": [ + { "Type": "name", "Value": "Christiaan Huygens", "ValueType": "string" }, + { "Type": "given_name", "Value": "Christiaan", "ValueType": "string" }, + { "Type": "family_name", "Value": "Huygens", "ValueType": "string" }, + { "Type": "preferred_username", "Value": "christiaanh", "ValueType": "string" }, + { "Type": "google_username", "Value": "christiaanh", "ValueType": "string" }, + { "Type": "email", "Value": "christiaan@example.com", "ValueType": "string" }, + { "Type": "email_verified", "Value": "true", "ValueType": "boolean" }, + { "Type": "groups", "Value": "[\"sjaarcie\"]", "ValueType": "json" }, + { "Type": "netid", "Value": "c123456", "ValueType": "string" }, + { "Type": "study", "Value": "CE", "ValueType": "string" }, + { "Type": "student_number", "Value": "S000001", "ValueType": "string" }, + { "Type": "organization", "Value": "W.I.S.V. 'Christiaan Huygens'", "ValueType": "string" } + ] + }, + { + "SubjectId": "WISVCH.0067", + "Username": "constantijn", + "Password": "pwd", + "Claims": [ + { "Type": "name", "Value": "Constantijn Huygens", "ValueType": "string" }, + { "Type": "given_name", "Value": "Constantijn", "ValueType": "string" }, + { "Type": "family_name", "Value": "Huygens", "ValueType": "string" }, + { "Type": "preferred_username", "Value": "constantijnh", "ValueType": "string" }, + { "Type": "google_username", "Value": "constantijnh", "ValueType": "string" }, + { "Type": "email", "Value": "constantijn@example.com", "ValueType": "string" }, + { "Type": "email_verified", "Value": "true", "ValueType": "boolean" }, + { "Type": "groups", "Value": "[\"sjaarcie\", \"beheer\"]", "ValueType": "json" }, + { "Type": "netid", "Value": "c654321", "ValueType": "string" }, + { "Type": "study", "Value": "CE", "ValueType": "string" }, + { "Type": "student_number", "Value": "S000002", "ValueType": "string" }, + { "Type": "organization", "Value": "W.I.S.V. 'Christiaan Huygens'", "ValueType": "string" } + ] + } + ] + + CLIENTS_CONFIGURATION_INLINE: | + [ + { + "ClientId": "events.devcontainer", + "Description": "Events Spring Boot devcontainer client", + "AllowedGrantTypes": ["authorization_code"], + "RequirePkce": false, + "RequireClientSecret": false, + "AllowAccessTokensViaBrowser": true, + "RedirectUris": ["*"], + "PostLogoutRedirectUris": ["*"], + "AllowedScopes": ["openid","profile","email","phone","address","student","auth","organization","roles"], + "IdentityTokenLifetime": 600, + "AccessTokenLifetime": 3600, + "AlwaysIncludeUserClaimsInIdToken": true + } + ] + +volumes: + db-data: + pgadmin-data: diff --git a/pgadmin/bootstrap-pgpass.sh b/pgadmin/bootstrap-pgpass.sh new file mode 100755 index 000000000..98020ecd6 --- /dev/null +++ b/pgadmin/bootstrap-pgpass.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -eu + +TEMPLATE="/pgpassfile.template" +TARGET="/var/lib/pgadmin/.pgpass" + +cp "${TEMPLATE}" "${TARGET}" +chmod 600 "${TARGET}" + +exec /entrypoint.sh "$@" diff --git a/pgadmin/pgpassfile b/pgadmin/pgpassfile new file mode 100644 index 000000000..cfda965ae --- /dev/null +++ b/pgadmin/pgpassfile @@ -0,0 +1 @@ +*:5432:*:postgres:postgres diff --git a/pgadmin/servers.json b/pgadmin/servers.json new file mode 100644 index 000000000..fe4b329c5 --- /dev/null +++ b/pgadmin/servers.json @@ -0,0 +1,14 @@ +{ + "Servers": { + "1": { + "Name": "events-db", + "Group": "Servers", + "Host": "db", + "Port": 5432, + "MaintenanceDB": "postgres", + "Username": "postgres", + "SSLMode": "prefer", + "PassFile": "/var/lib/pgadmin/.pgpass" + } + } +}