feat: version banner UI + GET /api/v1/version backend endpoint

This commit is contained in:
Александр Зимин
2026-05-10 16:50:50 +00:00
parent 17386ba0b4
commit e8f98230f2
9 changed files with 355 additions and 0 deletions
@@ -0,0 +1,81 @@
package cloud.nstart.terravault.ordinis.restapi.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.info.BuildProperties;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Instant;
import java.util.Optional;
/**
* {@code GET /api/v1/version} — экспонирует server build identity для UI
* update-detection banner.
*
* <p>Frontend (admin-ui) bundle'ит свой commit SHA в build-time через Vite
* {@code define.__APP_COMMIT__}. Periodic poll этого endpoint'а сравнивает
* server commit vs client commit. Mismatch → "Доступна новая версия" banner с
* CTA {@code Обновить} (window.location.reload).
*
* <p>Source данных:
* <ul>
* <li>{@code BuildProperties} bean создаётся Spring Boot когда есть
* {@code META-INF/build-info.properties} (генерируется
* spring-boot-maven-plugin {@code build-info} goal в ordinis-app pom).</li>
* <li>Дополнительные {@code commit}, {@code branch}, {@code tag}
* resolved из CI env vars (см. additionalProperties в plugin config).</li>
* </ul>
*
* <p>Если build-info.properties отсутствует (e.g., запуск в IDE без mvn package)
* — возвращает sensible defaults чтобы UI не падал.
*
* <p>Public endpoint — auth НЕ required (нужен на login screen чтобы клиент
* сразу знал stale ли его bundle).
*/
@RestController
@RequestMapping("/api/v1/version")
public class VersionController {
private final Optional<BuildProperties> buildProps;
// BuildProperties может отсутствовать в dev — оптимально через Optional.
public VersionController(@Autowired(required = false) BuildProperties buildProps) {
this.buildProps = Optional.ofNullable(buildProps);
}
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public VersionInfo current() {
return buildProps.map(b -> new VersionInfo(
b.getVersion(),
Optional.ofNullable(b.get("commit")).orElse("unknown"),
Optional.ofNullable(b.get("branch")).orElse(""),
Optional.ofNullable(b.get("tag")).orElse(""),
b.getTime() != null ? b.getTime().toString() : ""
)).orElseGet(() -> new VersionInfo(
"0.0.0-dev",
"unknown",
"",
"",
Instant.now().toString()
));
}
/**
* Stable JSON shape для UI client.
*
* @param version Maven {@code project.version} (e.g. "0.1.0-SNAPSHOT" или "1.1.0")
* @param commit short git SHA (e.g. "07c5ca0") — primary update detection key
* @param branch git branch (e.g. "main"), empty в tag builds
* @param tag git tag if applicable (e.g. "v1.1.0"), empty в branch builds
* @param builtAt ISO-8601 instant
*/
public record VersionInfo(
String version,
String commit,
String branch,
String tag,
String builtAt
) {}
}