From 70a4360207029810df9b4e45d0221b01978641aa Mon Sep 17 00:00:00 2001 From: adityachandelgit <> Date: Wed, 16 Apr 2025 15:14:40 -0600 Subject: [PATCH] Implement version info endpoint and update version display in UI --- .github/workflows/docker-build-publish.yml | 2 +- Dockerfile | 11 +++-- .../controller/VersionController.java | 46 +++++++++++++++++++ .../booklore/model/dto/VersionInfo.java | 11 +++++ .../src/main/resources/application.yaml | 1 + .../src/app/core/service/version.service.ts | 22 +++++++++ .../layout-menu/app.menu.component.html | 37 +++++++++------ .../layout-menu/app.menu.component.ts | 17 ++++--- booklore-ui/src/environments/version.ts | 1 - 9 files changed, 123 insertions(+), 25 deletions(-) create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/controller/VersionController.java create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/model/dto/VersionInfo.java create mode 100644 booklore-ui/src/app/core/service/version.service.ts delete mode 100644 booklore-ui/src/environments/version.ts diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index b6fd82f10..65396c7eb 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -100,7 +100,7 @@ jobs: docker buildx create --use docker buildx build \ --platform linux/amd64,linux/arm64 \ - --build-arg UI_VERSION=${{ env.image_tag }} \ + --build-arg APP_VERSION=${{ env.image_tag }} \ --tag ghcr.io/${{ github.actor }}/booklore-app:${{ env.image_tag }} \ --push . diff --git a/Dockerfile b/Dockerfile index 59991d7df..70ffa68bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,9 +7,6 @@ COPY ./booklore-ui/package.json ./booklore-ui/package-lock.json ./ RUN npm install --force COPY ./booklore-ui /angular-app/ -ARG UI_VERSION=development -RUN echo "export const version = '${UI_VERSION}';" > /angular-app/src/environments/version.ts - RUN npm run build --configuration=production # Stage 2: Build the Spring Boot app with Gradle @@ -21,10 +18,16 @@ COPY ./booklore-api/gradlew ./booklore-api/gradle/ /springboot-app/ COPY ./booklore-api/build.gradle ./booklore-api/settings.gradle /springboot-app/ COPY ./booklore-api/gradle /springboot-app/gradle COPY ./booklore-api/src /springboot-app/src +COPY ./booklore-api/src/main/resources/application.yaml /springboot-app/src/main/resources/application.yaml + +# Inject version into application.yaml using yq +ARG APP_VERSION +RUN apk add --no-cache yq && \ + yq eval '.app.version = strenv(APP_VERSION)' -i /springboot-app/src/main/resources/application.yaml RUN ./gradlew clean build -# Stage 3: Final image combining everything +# Stage 3: Final image FROM eclipse-temurin:21.0.5_11-jre-alpine RUN apk update && apk add nginx diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/controller/VersionController.java b/booklore-api/src/main/java/com/adityachandel/booklore/controller/VersionController.java new file mode 100644 index 000000000..4f41d2499 --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/controller/VersionController.java @@ -0,0 +1,46 @@ +package com.adityachandel.booklore.controller; + +import com.adityachandel.booklore.model.dto.VersionInfo; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestClient; + +@RestController +@RequestMapping("/api/v1/version") +public class VersionController { + + @Value("${app.version:unknown}") + private String appVersion; + + @GetMapping + public ResponseEntity getVersions() { + String latestVersion = fetchLatestGitHubReleaseVersion(); + return ResponseEntity.ok(new VersionInfo(appVersion, latestVersion)); + } + + private String fetchLatestGitHubReleaseVersion() { + try { + RestClient restClient = RestClient.builder() + .defaultHeader("Accept", "application/vnd.github+json") + .defaultHeader("User-Agent", "BookLore-Version-Checker") + .build(); + + String response = restClient.get() + .uri("https://api.github.com/repos/adityachandelgit/BookLore/releases/latest") + .retrieve() + .body(String.class); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode root = objectMapper.readTree(response); + return root.has("tag_name") ? root.get("tag_name").asText() : "unknown"; + + } catch (Exception e) { + return "unknown"; + } + } +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/VersionInfo.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/VersionInfo.java new file mode 100644 index 000000000..ee25f2692 --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/VersionInfo.java @@ -0,0 +1,11 @@ +package com.adityachandel.booklore.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class VersionInfo { + private String current; + private String latest; +} diff --git a/booklore-api/src/main/resources/application.yaml b/booklore-api/src/main/resources/application.yaml index 3ee2ad6f3..52671c543 100644 --- a/booklore-api/src/main/resources/application.yaml +++ b/booklore-api/src/main/resources/application.yaml @@ -1,6 +1,7 @@ app: path-book: '/app/books' path-config: '/app/data' + version: 'development' spring: servlet: diff --git a/booklore-ui/src/app/core/service/version.service.ts b/booklore-ui/src/app/core/service/version.service.ts new file mode 100644 index 000000000..b93159383 --- /dev/null +++ b/booklore-ui/src/app/core/service/version.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import {API_CONFIG} from '../../config/api-config'; + +export interface AppVersion { + current: string; + latest: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class VersionService { + private versionUrl = `${API_CONFIG.BASE_URL}/api/v1/version`; + + constructor(private http: HttpClient) {} + + getVersion(): Observable { + return this.http.get(this.versionUrl); + } +} diff --git a/booklore-ui/src/app/layout/component/layout-menu/app.menu.component.html b/booklore-ui/src/app/layout/component/layout-menu/app.menu.component.html index 707ad6af5..fc019573f 100644 --- a/booklore-ui/src/app/layout/component/layout-menu/app.menu.component.html +++ b/booklore-ui/src/app/layout/component/layout-menu/app.menu.component.html @@ -28,17 +28,28 @@ -

- - {{ version }} - -

+ + + - - - - diff --git a/booklore-ui/src/app/layout/component/layout-menu/app.menu.component.ts b/booklore-ui/src/app/layout/component/layout-menu/app.menu.component.ts index 7a6e4136a..9da5acc19 100644 --- a/booklore-ui/src/app/layout/component/layout-menu/app.menu.component.ts +++ b/booklore-ui/src/app/layout/component/layout-menu/app.menu.component.ts @@ -8,7 +8,7 @@ import {map} from 'rxjs/operators'; import {ShelfService} from '../../../book/service/shelf.service'; import {BookService} from '../../../book/service/book.service'; import {LibraryShelfMenuService} from '../../../book/service/library-shelf-menu.service'; -import {version} from '../../../../environments/version'; +import {AppVersion, VersionService} from '../../../core/service/version.service'; @Component({ selector: 'app-menu', @@ -20,15 +20,20 @@ export class AppMenuComponent implements OnInit { shelfMenu$: Observable | undefined; homeMenu$: Observable | undefined; + versionInfo: AppVersion | null = null; + private libraryService = inject(LibraryService); private shelfService = inject(ShelfService); private bookService = inject(BookService); + private versionService = inject(VersionService); private libraryShelfMenuService = inject(LibraryShelfMenuService); - protected readonly version = version; - - ngOnInit(): void { + + this.versionService.getVersion().subscribe((data) => { + this.versionInfo = data; + }); + this.libraryMenu$ = this.libraryService.libraryState$.pipe( map((state) => [ { @@ -89,12 +94,12 @@ export class AppMenuComponent implements OnInit { ); } - getVersionUrl(version: string): string { + getVersionUrl(version: string | undefined): string { + if (!version) return '#'; if (version.startsWith('v')) { return `https://github.com/adityachandelgit/BookLore/releases/tag/${version}`; } else { return `https://github.com/adityachandelgit/BookLore/commit/${version}`; } } - } diff --git a/booklore-ui/src/environments/version.ts b/booklore-ui/src/environments/version.ts deleted file mode 100644 index daed4622c..000000000 --- a/booklore-ui/src/environments/version.ts +++ /dev/null @@ -1 +0,0 @@ -export const version = 'v0.0.0';