diff --git a/.github/workflows/build-and-push-docker.yml b/.github/workflows/build-and-push-docker.yml new file mode 100644 index 00000000..a42d7d85 --- /dev/null +++ b/.github/workflows/build-and-push-docker.yml @@ -0,0 +1,88 @@ +name: Build and Push Docker Images + +on: + pull_request: + paths-ignore: + - 'README.md' + - 'CODE_OF_CONDUCT.md' + - 'CONTRIBUTING.md' + - 'LICENSE' + - 'SECURITY.md' + - 'docs/**' + - 'keycloakify/**' + - '.github/**' + - '!.github/workflows/build-and-push-docker-image.yml' + - '!.github/workflows/dev.yml' + push: + branches: + - develop + +jobs: + build: + name: ${{ matrix.path }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - dockerfile: ./webapp/Dockerfile + image: ls1intum/Hephaestus/webapp + context: ./webapp + path: webapp + - dockerfile: ./server/intelligence-service/Dockerfile + image: ls1intum/Hephaestus/intelligence-service + context: ./server/intelligence-service + path: intelligence-service + - dockerfile: ./server/webhook-ingest/Dockerfile + image: ls1intum/Hephaestus/webhook-ingest + context: ./server/webhook-ingest + path: webhook-ingest + - dockerfile: ./server/application-server/Dockerfile + image: ls1intum/Hephaestus/application-server + context: ./server/application-server + path: application-server + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: all + + - name: Install Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ matrix.image }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=sha,prefix=,format=long + + - name: Build and push Docker Image + uses: docker/build-push-action@v6 + with: + context: ${{ matrix.context }} + file: ${{ matrix.dockerfile }} + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + push: true + cache-from: type=gha,scope=${{ matrix.image }} + cache-to: type=gha,scope=${{ matrix.image }},mode=max diff --git a/build_images.sh b/build_images.sh deleted file mode 100644 index d9fd3383..00000000 --- a/build_images.sh +++ /dev/null @@ -1 +0,0 @@ -nixpacks build -n hephaestus-application-server server/application-server \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index 33a4baef..1d5295aa 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,6 +1,31 @@ services: + webapp: + build: + context: ./webapp + ports: + - '80' + environment: + - APPLICATION_CLIENT_URL + - APPLICATION_SERVER_URL + - SENTRY_DNS + - KEYCLOAK_URL + - KEYCLOAK_REALM + - KEYCLOAK_CLIENT_ID + - KEYCLOAK_SKIP_LOGIN + - LEGAL_IMPRINT_HTML + - LEGAL_PRIVACY_HTML + - UMAMI_ENABLED + - UMAMI_SCRIPT_URL + - UMAMI_WEBSITE_ID + - UMAMI_DOMAINS + depends_on: + - application-server + networks: + - app-network + application-server: - image: hephaestus-application-server + build: + context: ./server/application-server ports: - '8080' environment: @@ -66,4 +91,7 @@ services: networks: app-network: - driver: bridge` \ No newline at end of file + driver: bridge + +volumes: + postgresql-data: \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index dc061d43..b5cc70c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,10 +1,12 @@ { "name": "hephaestus", + "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hephaestus", + "version": "0.0.1", "workspaces": [ "webapp/*" ], diff --git a/package.json b/package.json index 7ae0df07..10200163 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "version": "0.0.1", "name": "hephaestus", "workspaces": [ "webapp/*" diff --git a/server/application-server/Dockerfile b/server/application-server/Dockerfile new file mode 100644 index 00000000..19dcd62b --- /dev/null +++ b/server/application-server/Dockerfile @@ -0,0 +1,26 @@ +FROM maven:3.9.8-eclipse-temurin-21 AS build + +WORKDIR /app + +COPY pom.xml . + +# This step is to cache dependencies, avoiding re-downloading them every time +RUN mvn dependency:go-offline + +COPY src ./src + +RUN mvn clean package -DskipTests + +FROM eclipse-temurin:21 + +WORKDIR /app + +COPY --from=build /app/target/*.jar /app/server.jar + +RUN addgroup --system spring && adduser --system spring --ingroup spring + +USER spring:spring + +EXPOSE 8080 + +ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app/server.jar"] \ No newline at end of file diff --git a/server/application-server/nixpacks.toml b/server/application-server/nixpacks.toml deleted file mode 100644 index c7cd6ac8..00000000 --- a/server/application-server/nixpacks.toml +++ /dev/null @@ -1,8 +0,0 @@ -[variables] -NIXPACKS_JDK_VERSION = '21' - -[phases.setup] -aptPkgs = ["...", "wget"] - -[phases.build] -cmds = ["chmod +x ./mvnw", "./mvnw -DskipTests clean package"] \ No newline at end of file diff --git a/server/application-server/src/main/resources/application-prod.yml b/server/application-server/src/main/resources/application-prod.yml index 37fdd15c..a5bdbcb1 100644 --- a/server/application-server/src/main/resources/application-prod.yml +++ b/server/application-server/src/main/resources/application-prod.yml @@ -14,7 +14,7 @@ spring: issuer-uri: ${KEYCLOAK_URL}/realms/${KEYCLOAK_REALM} hephaestus: - host-url: ${APPLICATION_HOST_URL} + host-url: ${APPLICATION_CLIENT_URL} leaderboard: schedule: @@ -33,8 +33,8 @@ hephaestus: keycloak: url: ${KEYCLOAK_URL} realm: ${KEYCLOAK_REALM} - client-id: ${KEYCLOAK_CLIENT_ID} - client-secret: ${KEYCLOAK_CLIENT_SECRET} + client-id: ${KEYCLOAK_CONFIDENTIAL_CLIENT_ID} + client-secret: ${KEYCLOAK_CONFIDENTIAL_CLIENT_SECRET} nats: enabled: ${NATS_ENABLED:false} diff --git a/webapp/Dockerfile b/webapp/Dockerfile index 7cab5188..6e6c6088 100644 --- a/webapp/Dockerfile +++ b/webapp/Dockerfile @@ -1,60 +1,46 @@ -FROM node:latest as build +FROM node:22 AS build WORKDIR /app -COPY ./ /app/ - -# Ensure .env file exists -RUN mv .env* .env || true -RUN touch .env -RUN cat .env - -# Fix buggy replacement of COOLIFY_URL in .env -RUN COOLIFY_URL_VALUE=$(grep '^COOLIFY_URL=' .env | cut -d '=' -f2) && \ - sed -i "s|\$COOLIFY_URL|$COOLIFY_URL_VALUE|g" .env - -# Export environment variables from .env -# This assumes that .env contains lines like VARIABLE=value -# and does not contain spaces around the '=' -RUN set -a && \ - . /app/.env && \ - set +a && \ - echo "Generating environment.prod.ts" && \ - cat > src/environments/environment.prod.ts < /app/version.txt + +FROM nginx:stable-alpine + +ENV APPLICATION_CLIENT_URL=https://default-client.url +ENV APPLICATION_SERVER_URL=https://default-server.url + +ENV SENTRY_DNS=https://289f1f62feeb4f70a8878dc0101825cd@sentry.ase.in.tum.de/3 + +ENV KEYCLOAK_URL=https://default-keycloak.url +ENV KEYCLOAK_REALM=default-realm +ENV KEYCLOAK_CLIENT_ID=default-client-id +ENV KEYCLOAK_SKIP_LOGIN=false + +ENV LEGAL_IMPRINT_HTML="

Default Imprint

" +ENV LEGAL_PRIVACY_HTML="

Default Privacy

" + +ENV UMAMI_ENABLED=false +ENV UMAMI_SCRIPT_URL="" +ENV UMAMI_WEBSITE_ID="" +ENV UMAMI_DOMAINS="" COPY --from=build /app/dist/webapp/browser /usr/share/nginx/html +COPY generate_config.sh /usr/local/bin/generate_config.sh +RUN chmod +x /usr/local/bin/generate_config.sh + COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 + +ENTRYPOINT ["/usr/local/bin/generate_config.sh"] +CMD ["nginx", "-g", "daemon off;"] diff --git a/webapp/angular.json b/webapp/angular.json index a2a9e259..e58e11c3 100644 --- a/webapp/angular.json +++ b/webapp/angular.json @@ -60,12 +60,6 @@ }, "configurations": { "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], "budgets": [ { "type": "initial", diff --git a/webapp/generate_config.sh b/webapp/generate_config.sh new file mode 100644 index 00000000..c7f63c48 --- /dev/null +++ b/webapp/generate_config.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# This script generates the environment.json file for the webapp based on the environment variables during the docker container startup. + +APP_VERSION=$(cat /version.txt) + +cat < /usr/share/nginx/html/environment.json +{ + "version": "{$APP_VERSION}", + "clientUrl": "${APPLICATION_CLIENT_URL}", + "serverUrl": "${APPLICATION_SERVER_URL}", + "sentry": { + "dsn": "${SENTRY_DNS}", + "environment": "prod" + }, + "keycloak": { + "url": "${KEYCLOAK_URL}", + "realm": "${KEYCLOAK_REALM}", + "clientId": "${KEYCLOAK_CLIENT_ID}", + "skipLoginPage": ${KEYCLOAK_SKIP_LOGIN} + }, + "umami": { + "enabled": ${UMAMI_ENABLED}, + "scriptUrl": "${UMAMI_SCRIPT_URL}", + "websiteId": "${UMAMI_WEBSITE_ID}", + "domains": "${UMAMI_DOMAINS}" + }, + "legal": { + "imprintHtml": "$(echo "${LEGAL_IMPRINT_HTML}" | sed 's/"/\\"/g')", + "privacyHtml": "$(echo "${LEGAL_PRIVACY_HTML}" | sed 's/"/\\"/g')" + } +} +EOF + +exec "$@" diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 8496898b..37b1cc60 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -81,6 +81,7 @@ "@storybook/test": "8.3.4", "@tailwindcss/typography": "0.5.15", "@types/jasmine": "5.1.4", + "@types/node": "22.10.1", "@typescript-eslint/eslint-plugin": "8.2.0", "@typescript-eslint/parser": "8.2.0", "chromatic": "11.7.1", @@ -8471,12 +8472,13 @@ } }, "node_modules/@types/node": { - "version": "22.7.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", - "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", "devOptional": true, + "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.20.0" } }, "node_modules/@types/node-forge": { @@ -22094,10 +22096,11 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "devOptional": true + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "devOptional": true, + "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", diff --git a/webapp/package.json b/webapp/package.json index 9180289e..c36b61d1 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -93,6 +93,7 @@ "@storybook/test": "8.3.4", "@tailwindcss/typography": "0.5.15", "@types/jasmine": "5.1.4", + "@types/node": "22.10.1", "@typescript-eslint/eslint-plugin": "8.2.0", "@typescript-eslint/parser": "8.2.0", "chromatic": "11.7.1", diff --git a/webapp/public/environment.json b/webapp/public/environment.json new file mode 100644 index 00000000..6cea134c --- /dev/null +++ b/webapp/public/environment.json @@ -0,0 +1,25 @@ +{ + "version": "dev", + "clientUrl": "http://localhost:4200", + "serverUrl": "http://localhost:8080", + "sentry": { + "dsn": "https://289f1f62feeb4f70a8878dc0101825cd@sentry.ase.in.tum.de/3", + "environment": "prod" + }, + "keycloak": { + "url": "http://localhost:8081", + "realm": "hephaestus", + "clientId": "hephaestus", + "skipLoginPage": false + }, + "umami": { + "enabled": false, + "scriptUrl": "", + "websiteId": "", + "domains": "" + }, + "legal": { + "imprintHtml": "

This is the imprint.

", + "privacyHtml": "

This is the privacy policy.

" + } +} diff --git a/webapp/src/app/analytics.service.ts b/webapp/src/app/analytics.service.ts index b74d050e..b4194805 100644 --- a/webapp/src/app/analytics.service.ts +++ b/webapp/src/app/analytics.service.ts @@ -1,14 +1,16 @@ -import { Injectable } from '@angular/core'; -import { environment } from 'environments/environment'; +import { inject, Injectable } from '@angular/core'; +import { EnvironmentService } from './environment.service'; @Injectable({ providedIn: 'root' }) export class AnalyticsService { + private environmentService = inject(EnvironmentService); + private scriptLoaded = false; initialize(): void { - if (environment.umami.enabled) { + if (this.environmentService.env.umami.enabled) { this.loadUmamiScript(); } } @@ -20,9 +22,9 @@ export class AnalyticsService { const script = document.createElement('script'); script.defer = true; - script.src = environment.umami.scriptUrl; - script.setAttribute('data-website-id', environment.umami.websiteId); - script.setAttribute('data-domains', environment.umami.domains); + script.src = this.environmentService.env.umami.scriptUrl; + script.setAttribute('data-website-id', this.environmentService.env.umami.websiteId); + script.setAttribute('data-domains', this.environmentService.env.umami.domains); script.onload = () => { console.log('Umami analytics script loaded successfully.'); diff --git a/webapp/src/app/app.config.ts b/webapp/src/app/app.config.ts index 3da5f04e..29cc314e 100644 --- a/webapp/src/app/app.config.ts +++ b/webapp/src/app/app.config.ts @@ -3,16 +3,17 @@ import { provideRouter, Router } from '@angular/router'; import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { provideAngularQuery, QueryClient } from '@tanstack/angular-query-experimental'; -import { environment } from 'environments/environment'; import { BASE_PATH } from 'app/core/modules/openapi'; import { routes } from 'app/app.routes'; import { AnalyticsService } from './analytics.service'; import { securityInterceptor } from './core/security/security-interceptor'; +import { EnvironmentService } from './environment.service'; import { TraceService } from '@sentry/angular'; import { SentryErrorHandler } from './core/sentry/sentry.error-handler'; -function initializeAnalytics(analyticsService: AnalyticsService): () => void { +function initializeApp(environmentService: EnvironmentService, analyticsService: AnalyticsService) { return () => { + environmentService.loadEnv(); analyticsService.initialize(); }; } @@ -24,8 +25,12 @@ export const appConfig: ApplicationConfig = { provideAngularQuery(new QueryClient()), provideHttpClient(withInterceptors([securityInterceptor])), provideAnimationsAsync(), - { provide: BASE_PATH, useValue: environment.serverUrl }, - { provide: APP_INITIALIZER, useFactory: initializeAnalytics, multi: true, deps: [AnalyticsService] }, + { provide: APP_INITIALIZER, useFactory: initializeApp, multi: true, deps: [EnvironmentService, AnalyticsService] }, + { + provide: BASE_PATH, + useFactory: (environmentService: EnvironmentService) => environmentService.env.serverUrl, + deps: [EnvironmentService] + }, { provide: ErrorHandler, useClass: SentryErrorHandler }, { provide: TraceService, deps: [Router] }, { diff --git a/webapp/src/app/core/header/header.component.html b/webapp/src/app/core/header/header.component.html index 955d44af..b47af1c0 100644 --- a/webapp/src/app/core/header/header.component.html +++ b/webapp/src/app/core/header/header.component.html @@ -3,6 +3,7 @@ + {{ appVersion }} @if (user()?.roles?.includes('admin')) { Workspace diff --git a/webapp/src/app/core/header/header.component.ts b/webapp/src/app/core/header/header.component.ts index 59a6dfb7..28e8d3bc 100644 --- a/webapp/src/app/core/header/header.component.ts +++ b/webapp/src/app/core/header/header.component.ts @@ -9,9 +9,9 @@ import { BrnMenuTriggerDirective } from '@spartan-ng/ui-menu-brain'; import { SecurityStore } from '@app/core/security/security-store.service'; import { ThemeSwitcherComponent } from '@app/core/theme/theme-switcher.component'; import { RequestFeatureComponent } from './request-feature/request-feature.component'; -import { environment } from 'environments/environment'; import { lucideUser, lucideLogOut, lucideSettings } from '@ng-icons/lucide'; import { provideIcons } from '@ng-icons/core'; +import { EnvironmentService } from '@app/environment.service'; import { AiMentorComponent } from './ai-mentor/ai-mentor.component'; @Component({ @@ -40,6 +40,8 @@ import { AiMentorComponent } from './ai-mentor/ai-mentor.component'; ] }) export class HeaderComponent { + private environmentService = inject(EnvironmentService); + protected Hammer = Hammer; securityStore = inject(SecurityStore); @@ -50,12 +52,16 @@ export class HeaderComponent { this.securityStore.signOut(); } + protected get appVersion() { + return this.environmentService.env.version; + } + protected signIn() { - if (environment.keycloak.skipLoginPage) { + if (this.environmentService.env.keycloak.skipLoginPage) { const authUrl = - `${environment.keycloak.url}/realms/${environment.keycloak.realm}/protocol/openid-connect/auth` + - `?client_id=${encodeURIComponent(environment.keycloak.clientId)}` + - `&redirect_uri=${encodeURIComponent(environment.clientUrl)}` + + `${this.environmentService.env.keycloak.url}/realms/${this.environmentService.env.keycloak.realm}/protocol/openid-connect/auth` + + `?client_id=${encodeURIComponent(this.environmentService.env.keycloak.clientId)}` + + `&redirect_uri=${encodeURIComponent(this.environmentService.env.clientUrl)}` + `&response_type=code` + `&scope=openid` + `&kc_idp_hint=${encodeURIComponent('github')}`; diff --git a/webapp/src/app/core/security/keycloak.service.ts b/webapp/src/app/core/security/keycloak.service.ts index 168f3984..c7082b73 100644 --- a/webapp/src/app/core/security/keycloak.service.ts +++ b/webapp/src/app/core/security/keycloak.service.ts @@ -1,5 +1,5 @@ -import { Injectable } from '@angular/core'; -import { environment } from 'environments/environment'; +import { inject, Injectable } from '@angular/core'; +import { EnvironmentService } from '@app/environment.service'; import Keycloak from 'keycloak-js'; export interface UserProfile { @@ -17,15 +17,17 @@ export interface UserProfile { @Injectable({ providedIn: 'root' }) export class KeycloakService { + private environmentService = inject(EnvironmentService); + _keycloak: Keycloak | undefined; profile: UserProfile | undefined; get keycloak() { if (!this._keycloak) { this._keycloak = new Keycloak({ - url: environment.keycloak.url, - realm: environment.keycloak.realm, - clientId: environment.keycloak.clientId + url: this.environmentService.env.keycloak.url, + realm: this.environmentService.env.keycloak.realm, + clientId: this.environmentService.env.keycloak.clientId }); } return this._keycloak; @@ -78,6 +80,6 @@ export class KeycloakService { } logout() { - return this.keycloak.logout({ redirectUri: environment.clientUrl }); + return this.keycloak.logout({ redirectUri: this.environmentService.env.clientUrl }); } } diff --git a/webapp/src/app/core/sentry/sentry.error-handler.ts b/webapp/src/app/core/sentry/sentry.error-handler.ts index 2bde1073..357e88e7 100644 --- a/webapp/src/app/core/sentry/sentry.error-handler.ts +++ b/webapp/src/app/core/sentry/sentry.error-handler.ts @@ -1,10 +1,10 @@ -import { ErrorHandler, Injectable } from '@angular/core'; -import { environment } from 'environments/environment'; +import { ErrorHandler, inject, Injectable } from '@angular/core'; +import { EnvironmentService } from '@app/environment.service'; import * as Sentry from '@sentry/angular'; @Injectable({ providedIn: 'root' }) export class SentryErrorHandler extends ErrorHandler { - private environment = environment; + private environmentService = inject(EnvironmentService); constructor() { super(); @@ -14,7 +14,7 @@ export class SentryErrorHandler extends ErrorHandler { * Initialize Sentry with environment. */ async init() { - const env = this.environment; + const env = this.environmentService.env; if (!env || !env.version || !env.sentry?.dsn) { return; } @@ -38,7 +38,7 @@ export class SentryErrorHandler extends ErrorHandler { super.handleError(error); return; } - if (this.environment.sentry.environment !== 'local') { + if (this.environmentService.env.sentry.environment !== 'local') { const exception = error.error || error.message || error.originalError || error; Sentry.captureException(exception); } diff --git a/webapp/src/app/environment.service.ts b/webapp/src/app/environment.service.ts new file mode 100644 index 00000000..64081ca0 --- /dev/null +++ b/webapp/src/app/environment.service.ts @@ -0,0 +1,60 @@ +import { Injectable, isDevMode } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { lastValueFrom } from 'rxjs'; + +export interface Environment { + version: string; + clientUrl: string; + serverUrl: string; + sentry: { + dsn: string; + environment: string; + }; + keycloak: { + url: string; + realm: string; + clientId: string; + skipLoginPage: boolean; + }; + umami: { + enabled: boolean; + scriptUrl: string; + websiteId: string; + domains: string; + }; + legal: { + imprintHtml: string; + privacyHtml: string; + }; +} + +@Injectable({ + providedIn: 'root' +}) +export class EnvironmentService { + private environment!: Environment; + + constructor(private http: HttpClient) {} + + loadEnv() { + if (isDevMode()) { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const environment = require('../../public/environment.json') as Environment; + this.environment = environment; + return Promise.resolve(); + } else { + return lastValueFrom(this.http.get('/environment.json')) + .then((environment) => { + this.environment = environment; + console.log('Environment loaded successfully.', this.environment); + }) + .catch((error) => { + console.error('Error loading environment.', error); + }); + } + } + + get env(): Environment { + return this.environment; + } +} diff --git a/webapp/src/app/home/home.component.ts b/webapp/src/app/home/home.component.ts index 8a884ab1..eee831f9 100644 --- a/webapp/src/app/home/home.component.ts +++ b/webapp/src/app/home/home.component.ts @@ -1,5 +1,5 @@ -import dayjs from 'dayjs'; -import isoWeek from 'dayjs/plugin/isoWeek'; +import dayjs from 'dayjs/esm'; +import isoWeek from 'dayjs/esm/plugin/isoWeek'; import { lastValueFrom } from 'rxjs'; import { Component, computed, inject } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; diff --git a/webapp/src/app/home/leaderboard/filter/timeframe/timeframe.component.ts b/webapp/src/app/home/leaderboard/filter/timeframe/timeframe.component.ts index 39f7ba44..fe74151e 100644 --- a/webapp/src/app/home/leaderboard/filter/timeframe/timeframe.component.ts +++ b/webapp/src/app/home/leaderboard/filter/timeframe/timeframe.component.ts @@ -1,8 +1,8 @@ import { Component, computed, effect, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; -import dayjs from 'dayjs'; -import isoWeek from 'dayjs/plugin/isoWeek'; +import dayjs from 'dayjs/esm'; +import isoWeek from 'dayjs/esm/plugin/isoWeek'; import { BrnSelectModule } from '@spartan-ng/ui-select-brain'; import { HlmSelectModule } from '@spartan-ng/ui-select-helm'; import { HlmLabelModule } from '@spartan-ng/ui-label-helm'; diff --git a/webapp/src/app/legal/imprint.component.ts b/webapp/src/app/legal/imprint.component.ts index 1337b3d7..a07d84f8 100644 --- a/webapp/src/app/legal/imprint.component.ts +++ b/webapp/src/app/legal/imprint.component.ts @@ -1,5 +1,5 @@ -import { Component } from '@angular/core'; -import { environment } from 'environments/environment'; +import { Component, inject } from '@angular/core'; +import { EnvironmentService } from '@app/environment.service'; @Component({ selector: 'app-imprint', @@ -12,5 +12,6 @@ import { environment } from 'environments/environment'; ` }) export class ImprintComponent { - imprintHtml = environment.legal.imprintHtml; + private environmentService = inject(EnvironmentService); + imprintHtml = this.environmentService.env.legal.imprintHtml; } diff --git a/webapp/src/app/legal/privacy.component.ts b/webapp/src/app/legal/privacy.component.ts index 18a1d61b..1b1128c5 100644 --- a/webapp/src/app/legal/privacy.component.ts +++ b/webapp/src/app/legal/privacy.component.ts @@ -1,5 +1,5 @@ -import { Component } from '@angular/core'; -import { environment } from 'environments/environment'; +import { Component, inject } from '@angular/core'; +import { EnvironmentService } from '@app/environment.service'; @Component({ selector: 'app-privacy', @@ -12,5 +12,6 @@ import { environment } from 'environments/environment'; ` }) export class PrivacyComponent { - imprintHtml = environment.legal.privacyHtml; + private environmentService = inject(EnvironmentService); + imprintHtml = this.environmentService.env.legal.privacyHtml; } diff --git a/webapp/src/app/user/header/header.component.ts b/webapp/src/app/user/header/header.component.ts index f232a8a5..07243d27 100644 --- a/webapp/src/app/user/header/header.component.ts +++ b/webapp/src/app/user/header/header.component.ts @@ -7,8 +7,8 @@ import { HlmIconModule } from 'libs/ui/ui-icon-helm/src/index'; import { BrnTooltipContentDirective } from '@spartan-ng/ui-tooltip-brain'; import { HlmTooltipComponent, HlmTooltipTriggerDirective } from '@spartan-ng/ui-tooltip-helm'; import { HlmButtonModule } from '@spartan-ng/ui-button-helm'; -import dayjs from 'dayjs'; -import advancedFormat from 'dayjs/plugin/advancedFormat'; +import dayjs from 'dayjs/esm'; +import advancedFormat from 'dayjs/esm/plugin/advancedFormat'; import { RepositoryInfo, UserInfo } from '@app/core/modules/openapi'; dayjs.extend(advancedFormat); diff --git a/webapp/src/app/user/header/header.stories.ts b/webapp/src/app/user/header/header.stories.ts index 31656f02..660b1315 100644 --- a/webapp/src/app/user/header/header.stories.ts +++ b/webapp/src/app/user/header/header.stories.ts @@ -1,5 +1,5 @@ import { argsToTemplate, Meta, StoryObj } from '@storybook/angular'; -import dayjs from 'dayjs'; +import dayjs from 'dayjs/esm'; import { UserHeaderComponent } from './header.component'; type FlatArgs = { diff --git a/webapp/src/app/user/issue-card/issue-card.component.ts b/webapp/src/app/user/issue-card/issue-card.component.ts index a4b93fbb..3d200831 100644 --- a/webapp/src/app/user/issue-card/issue-card.component.ts +++ b/webapp/src/app/user/issue-card/issue-card.component.ts @@ -5,7 +5,7 @@ import { octCheck, octComment, octFileDiff, octGitPullRequest, octGitPullRequest import { HlmCardModule } from '@spartan-ng/ui-card-helm'; import { HlmSkeletonComponent } from '@spartan-ng/ui-skeleton-helm'; import { GithubLabelComponent } from '@app/ui/github-label/github-label.component'; -import dayjs from 'dayjs'; +import dayjs from 'dayjs/esm'; import { cn } from '@app/utils'; @Component({ diff --git a/webapp/src/app/user/review-activity-card/review-activity-card.component.ts b/webapp/src/app/user/review-activity-card/review-activity-card.component.ts index 241e5c90..abe52943 100644 --- a/webapp/src/app/user/review-activity-card/review-activity-card.component.ts +++ b/webapp/src/app/user/review-activity-card/review-activity-card.component.ts @@ -7,8 +7,8 @@ import { HlmSkeletonComponent } from '@spartan-ng/ui-skeleton-helm'; import { HlmIconComponent } from '@spartan-ng/ui-icon-helm'; import { HlmTooltipTriggerDirective } from '@spartan-ng/ui-tooltip-helm'; import { HlmButtonModule } from '@spartan-ng/ui-button-helm'; -import dayjs from 'dayjs'; -import relativeTime from 'dayjs/plugin/relativeTime'; +import dayjs from 'dayjs/esm'; +import relativeTime from 'dayjs/esm/plugin/relativeTime'; import { lucideAward } from '@ng-icons/lucide'; dayjs.extend(relativeTime); diff --git a/webapp/src/app/user/review-activity-card/review-activity-card.stories.ts b/webapp/src/app/user/review-activity-card/review-activity-card.stories.ts index 93884e71..d7eb743c 100644 --- a/webapp/src/app/user/review-activity-card/review-activity-card.stories.ts +++ b/webapp/src/app/user/review-activity-card/review-activity-card.stories.ts @@ -1,6 +1,6 @@ import { argsToTemplate, Meta, StoryObj } from '@storybook/angular'; import { ReviewActivityCardComponent } from './review-activity-card.component'; -import dayjs from 'dayjs'; +import dayjs from 'dayjs/esm'; type FlatArgs = { isLoading: boolean; diff --git a/webapp/src/environments/environment.prod.ts b/webapp/src/environments/environment.prod.ts deleted file mode 100644 index a409952e..00000000 --- a/webapp/src/environments/environment.prod.ts +++ /dev/null @@ -1,25 +0,0 @@ -export const environment = { - clientUrl: 'http://localhost:4200', - serverUrl: 'http://localhost:8080', - version: '0.0.1', - sentry: { - dsn: 'https://289f1f62feeb4f70a8878dc0101825cd@sentry.ase.in.tum.de/3', - environment: 'prod' - }, - keycloak: { - url: 'http://localhost:8081', - realm: 'hephaestus', - clientId: 'hephaestus', - skipLoginPage: false // If true, it will directly use github IDP for login - }, - umami: { - enabled: false, - scriptUrl: '', - websiteId: '', - domains: '' - }, - legal: { - imprintHtml: '

This is the imprint.

', - privacyHtml: '

This is the privacy policy.

' - } -}; diff --git a/webapp/src/environments/environment.ts b/webapp/src/environments/environment.ts deleted file mode 100644 index a409952e..00000000 --- a/webapp/src/environments/environment.ts +++ /dev/null @@ -1,25 +0,0 @@ -export const environment = { - clientUrl: 'http://localhost:4200', - serverUrl: 'http://localhost:8080', - version: '0.0.1', - sentry: { - dsn: 'https://289f1f62feeb4f70a8878dc0101825cd@sentry.ase.in.tum.de/3', - environment: 'prod' - }, - keycloak: { - url: 'http://localhost:8081', - realm: 'hephaestus', - clientId: 'hephaestus', - skipLoginPage: false // If true, it will directly use github IDP for login - }, - umami: { - enabled: false, - scriptUrl: '', - websiteId: '', - domains: '' - }, - legal: { - imprintHtml: '

This is the imprint.

', - privacyHtml: '

This is the privacy policy.

' - } -}; diff --git a/webapp/tsconfig.app.json b/webapp/tsconfig.app.json index 3775b37e..e2722e2f 100644 --- a/webapp/tsconfig.app.json +++ b/webapp/tsconfig.app.json @@ -4,7 +4,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "types": [] + "types": ["node"] }, "files": [ "src/main.ts" diff --git a/webapp/tsconfig.json b/webapp/tsconfig.json index 90dcda75..4f92768e 100644 --- a/webapp/tsconfig.json +++ b/webapp/tsconfig.json @@ -2,6 +2,7 @@ "compileOnSave": false, "compilerOptions": { "outDir": "./dist/out-tsc", + "types": ["node"], "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true,