Implement version info endpoint and update version display in UI

This commit is contained in:
adityachandelgit
2025-04-16 15:14:40 -06:00
committed by Aditya Chandel
parent 6d1384aac4
commit 70a4360207
9 changed files with 123 additions and 25 deletions

View File

@@ -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 .

View File

@@ -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

View File

@@ -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<VersionInfo> 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";
}
}
}

View File

@@ -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;
}

View File

@@ -1,6 +1,7 @@
app:
path-book: '/app/books'
path-config: '/app/data'
version: 'development'
spring:
servlet:

View File

@@ -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<AppVersion> {
return this.http.get<AppVersion>(this.versionUrl);
}
}

View File

@@ -28,17 +28,28 @@
</ul>
</div>
<p style="margin-top: auto;" class="p-4 text-center w-full">
<a
[href]="getVersionUrl(version)"
target="_blank"
rel="noopener noreferrer"
class="text-gray-300 hover:underline hover:text-gray-500">
{{ version }}
</a>
</p>
<ng-container *ngIf="versionInfo">
<div style="margin-top: auto;" class="p-4 text-center w-full text-sm">
<div class="text-gray-200">
<a
[href]="getVersionUrl(versionInfo.current)"
target="_blank"
rel="noopener noreferrer"
class="hover:underline hover:text-gray-100 text-sm"
>
{{ versionInfo.current }}
</a>
</div>
<div *ngIf="versionInfo.latest && versionInfo.latest !== versionInfo.current">
<a
[href]="getVersionUrl(versionInfo.latest)"
target="_blank"
rel="noopener noreferrer"
class="text-orange-400 hover:underline hover:text-orange-300 text-xs"
>
(Update)
</a>
</div>
</div>
</ng-container>
</div>

View File

@@ -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<any> | undefined;
homeMenu$: Observable<any> | 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}`;
}
}
}

View File

@@ -1 +0,0 @@
export const version = 'v0.0.0';