feat(ui): add app version display and API
- Add `getGitHash` utility to extract the git commit hash from the `.git/HEAD` file. - Expose a new `/api/app-version` endpoint on the server to return the current version. - Implement a `useAppVersion` hook to fetch the version asynchronously. - Display the app version (e.g., `app version: a1b2c3d`) in the User Center UI for both authenticated and unauthenticated users. - This helps in tracking which version of the application is currently running in production.
This commit is contained in:
parent
05200c8d48
commit
f7cd596084
9
src/hooks/useAppVersion.ts
Normal file
9
src/hooks/useAppVersion.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { useRequest } from "ahooks";
|
||||||
|
|
||||||
|
export const useAppVersion = () => {
|
||||||
|
const versionRequest = useRequest(
|
||||||
|
() => fetch('/api/app-version').then(res => res.json()).then(json => json.version),
|
||||||
|
{ debounceWait: 300 }
|
||||||
|
);
|
||||||
|
return versionRequest.data;
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { serve } from "bun";
|
import { serve } from "bun";
|
||||||
import { getMatchInfo, verifyLogtoToken, xcxApi } from "./utils/server";
|
import { getGitHash, getMatchInfo, verifyLogtoToken, xcxApi } from "./utils/server";
|
||||||
import ics from 'ics';
|
import ics from 'ics';
|
||||||
import index from "./index.html";
|
import index from "./index.html";
|
||||||
import { getUidScore } from "./services/uidScoreStore";
|
import { getUidScore } from "./services/uidScoreStore";
|
||||||
@ -13,12 +13,17 @@ import type { IEventInfo } from "./types";
|
|||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
|
console.debug('AppVersion: %s', await getGitHash(8));
|
||||||
const server = serve({
|
const server = serve({
|
||||||
port: process.env.PORT || 3000,
|
port: process.env.PORT || 3000,
|
||||||
routes: {
|
routes: {
|
||||||
// Serve index.html for all unmatched routes.
|
// Serve index.html for all unmatched routes.
|
||||||
"/*": index,
|
"/*": index,
|
||||||
|
"/api/app-version": {
|
||||||
|
async GET() {
|
||||||
|
return Response.json({ version: await getGitHash(8) });
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/club/find": {
|
"/api/club/find": {
|
||||||
async GET(req) {
|
async GET(req) {
|
||||||
const searchParams = new URL(req.url).searchParams;
|
const searchParams = new URL(req.url).searchParams;
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useLocation, useNavigate } from "react-router";
|
|||||||
import { AUTH_CALLBACK_URL, USER_CENTER_URL } from "../utils/front";
|
import { AUTH_CALLBACK_URL, USER_CENTER_URL } from "../utils/front";
|
||||||
import useAutoLogin from "../hooks/useAutoLogin";
|
import useAutoLogin from "../hooks/useAutoLogin";
|
||||||
import { LOGTO_DOMAIN } from "../utils/common";
|
import { LOGTO_DOMAIN } from "../utils/common";
|
||||||
|
import { useAppVersion } from "../hooks/useAppVersion";
|
||||||
|
|
||||||
enum modifyRoutes {
|
enum modifyRoutes {
|
||||||
username = '/account/username',
|
username = '/account/username',
|
||||||
@ -18,6 +19,20 @@ enum modifyRoutes {
|
|||||||
|
|
||||||
const redirect = encodeURIComponent(USER_CENTER_URL);
|
const redirect = encodeURIComponent(USER_CENTER_URL);
|
||||||
|
|
||||||
|
function AppVersion({ version }: { version?: string }) {
|
||||||
|
if (!version) return null;
|
||||||
|
return (
|
||||||
|
<Divider>
|
||||||
|
<Typography.Text type="secondary">
|
||||||
|
<Flex gap={4}>
|
||||||
|
<span style={{ textTransform: 'uppercase' }}>app version:</span>
|
||||||
|
{version}
|
||||||
|
</Flex>
|
||||||
|
</Typography.Text>
|
||||||
|
</Divider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const UserCenter = () => {
|
export const UserCenter = () => {
|
||||||
const { signIn, isAuthenticated, signOut, getIdTokenClaims } = useLogto();
|
const { signIn, isAuthenticated, signOut, getIdTokenClaims } = useLogto();
|
||||||
const { autoSignIn } = useAutoLogin();
|
const { autoSignIn } = useAutoLogin();
|
||||||
@ -42,6 +57,7 @@ export const UserCenter = () => {
|
|||||||
window.location.href = `${LOGTO_DOMAIN}${url}?redirect=${redirect}`;
|
window.location.href = `${LOGTO_DOMAIN}${url}?redirect=${redirect}`;
|
||||||
}, []);
|
}, []);
|
||||||
const app = App.useApp();
|
const app = App.useApp();
|
||||||
|
const version = useAppVersion();
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
@ -54,6 +70,7 @@ export const UserCenter = () => {
|
|||||||
>
|
>
|
||||||
登录
|
登录
|
||||||
</Button>
|
</Button>
|
||||||
|
<AppVersion version={version} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -102,6 +119,7 @@ export const UserCenter = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<AppVersion version={version} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2,7 +2,7 @@ import type { Player } from "../types";
|
|||||||
import * as cheerio from "cheerio";
|
import * as cheerio from "cheerio";
|
||||||
import { XCXAPI } from "../services/xcxApi";
|
import { XCXAPI } from "../services/xcxApi";
|
||||||
import { KAIQIU_BASE_URL, LOGTO_DOMAIN } from "./common";
|
import { KAIQIU_BASE_URL, LOGTO_DOMAIN } from "./common";
|
||||||
import { RedisClient } from "bun";
|
import { RedisClient, file } from "bun";
|
||||||
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
||||||
import { LOGTO_RESOURCE } from "./constants";
|
import { LOGTO_RESOURCE } from "./constants";
|
||||||
|
|
||||||
@ -11,11 +11,11 @@ const REQUIRED_ENVS = [
|
|||||||
process.env.REDIS,
|
process.env.REDIS,
|
||||||
];
|
];
|
||||||
|
|
||||||
console.debug('ENVS: \n%s', REQUIRED_ENVS.join('\n'));
|
|
||||||
if (!REQUIRED_ENVS.every(v => !!v)) {
|
if (!REQUIRED_ENVS.every(v => !!v)) {
|
||||||
console.error('Missing required environment variables. Please check your .env');
|
console.error('Missing required environment variables. Please check your .env');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const REDIS_CACHE_HOUR = Number(process.env.REDIS_CACHE_HOUR) || 8;
|
export const REDIS_CACHE_HOUR = Number(process.env.REDIS_CACHE_HOUR) || 8;
|
||||||
|
|
||||||
console.debug('Cache hour: %s', REDIS_CACHE_HOUR);
|
console.debug('Cache hour: %s', REDIS_CACHE_HOUR);
|
||||||
@ -119,3 +119,23 @@ export const verifyLogtoToken = async (headers: Headers) => {
|
|||||||
// Sub is the user ID, used for user identification
|
// Sub is the user ID, used for user identification
|
||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getGitHash(length = 6): Promise<string> {
|
||||||
|
try {
|
||||||
|
// 1. 读取 HEAD 指针
|
||||||
|
const headContent = await file(".git/HEAD").text();
|
||||||
|
|
||||||
|
// 情况 A: 处于分支上 (内容格式为 "ref: refs/heads/master")
|
||||||
|
if (headContent.startsWith("ref:")) {
|
||||||
|
const refPath = headContent.replace("ref: ", "").trim();
|
||||||
|
const fullHash = await file(`.git/${refPath}`).text();
|
||||||
|
return fullHash.trim().substring(0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况 B: 处于分离头指针状态 (内容直接就是 Hash)
|
||||||
|
return headContent.trim().substring(0, length);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("无法读取 Git 信息:", e);
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user