fix(auth): unify scheme creator display
This commit is contained in:
@@ -146,6 +146,7 @@ const App = (props: React.PropsWithChildren<AppProps>) => {
|
||||
const { user } = data;
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
name: user.name,
|
||||
avatar: user.image,
|
||||
};
|
||||
|
||||
@@ -66,6 +66,7 @@ const authOptions: NextAuthOptions = {
|
||||
profile(profile) {
|
||||
return {
|
||||
id: profile.sub,
|
||||
username: profile.preferred_username,
|
||||
name: profile.name ?? profile.preferred_username,
|
||||
email: profile.email,
|
||||
image: Avatar.src,
|
||||
@@ -79,6 +80,12 @@ const authOptions: NextAuthOptions = {
|
||||
if (profile?.sub) {
|
||||
token.sub = profile.sub;
|
||||
}
|
||||
const preferredUsername = (
|
||||
profile as { preferred_username?: unknown } | undefined
|
||||
)?.preferred_username;
|
||||
if (typeof preferredUsername === "string") {
|
||||
token.username = preferredUsername;
|
||||
}
|
||||
|
||||
if (account) {
|
||||
if (account.access_token) {
|
||||
@@ -104,6 +111,9 @@ const authOptions: NextAuthOptions = {
|
||||
if (session.user && token.sub) {
|
||||
session.user.id = token.sub;
|
||||
}
|
||||
if (session.user && token.username) {
|
||||
session.user.username = token.username;
|
||||
}
|
||||
if (token.accessToken) {
|
||||
session.accessToken = token.accessToken;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
BurstSchemeRecord,
|
||||
} from "./types";
|
||||
import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units";
|
||||
import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName";
|
||||
|
||||
interface Props {
|
||||
onViewResult: (result: BurstLocationResult) => void;
|
||||
@@ -86,6 +87,7 @@ const SchemeQuery: React.FC<Props> = ({
|
||||
const simulationBurstIdsByName = queryState.simulationBurstIdsByName ?? {};
|
||||
const [internalSchemes, setInternalSchemes] = useState<BurstSchemeRecord[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const creatorName = useSchemeCreatorName();
|
||||
const schemes = externalSchemes !== undefined ? externalSchemes : internalSchemes;
|
||||
const setSchemes = onSchemesChange || setInternalSchemes;
|
||||
const sortedSchemes = useMemo(
|
||||
@@ -526,7 +528,7 @@ const SchemeQuery: React.FC<Props> = ({
|
||||
用户:
|
||||
</Typography>
|
||||
<Typography variant="caption" className="font-medium text-gray-900">
|
||||
{scheme.username || "-"}
|
||||
{creatorName(scheme.username)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -21,7 +21,7 @@ const scheme: SchemeRecord = {
|
||||
id: 17,
|
||||
schemeName: "burst-report-demo",
|
||||
type: "burst_analysis",
|
||||
user: "operator",
|
||||
username: "operator",
|
||||
create_time: "2026-07-30T08:00:00+08:00",
|
||||
startTime: "2026-07-30T09:00:00+08:00",
|
||||
schemeDetail: {
|
||||
|
||||
@@ -255,7 +255,7 @@ const ReportDocument: React.FC<ReportDocumentProps> = ({
|
||||
{[
|
||||
["管网", NETWORK_NAME],
|
||||
["方案名称", scheme.schemeName],
|
||||
["方案创建人", scheme.user || "未记录"],
|
||||
["方案创建人", scheme.username || "未记录"],
|
||||
["方案创建时间", formatDateTime(scheme.create_time)],
|
||||
["模拟开始时间", formatDateTime(scheme.startTime)],
|
||||
["模拟持续时间", formatDuration(duration)],
|
||||
|
||||
@@ -18,7 +18,7 @@ jest.mock("./SchemeQuery", () => ({
|
||||
id,
|
||||
schemeName,
|
||||
type: "burst_analysis",
|
||||
user: "operator",
|
||||
username: "operator",
|
||||
create_time: "2026-07-30T08:00:00+08:00",
|
||||
startTime: "2026-07-30T09:00:00+08:00",
|
||||
schemeDetail: {
|
||||
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
getPipeDiameterDisplay,
|
||||
type PipeDiameterMap,
|
||||
} from "./schemePipeDiameters";
|
||||
import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName";
|
||||
|
||||
interface SchemeQueryProps {
|
||||
schemes?: SchemeRecord[];
|
||||
@@ -113,6 +114,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
|
||||
const [internalSchemes, setInternalSchemes] = useState<SchemeRecord[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const creatorName = useSchemeCreatorName();
|
||||
const [mapContainer, setMapContainer] = useState<HTMLElement | null>(null); // 地图容器元素
|
||||
const [pipeDiametersByScheme, setPipeDiametersByScheme] = useState<
|
||||
Record<number, PipeDiameterMap>
|
||||
@@ -177,7 +179,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
id: item.scheme_id,
|
||||
schemeName: item.scheme_name,
|
||||
type: item.scheme_type,
|
||||
user: item.username,
|
||||
username: item.username,
|
||||
create_time: item.create_time,
|
||||
startTime: item.scheme_start_time,
|
||||
schemeDetail: item.scheme_detail,
|
||||
@@ -723,7 +725,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
variant="caption"
|
||||
className="font-medium text-gray-900"
|
||||
>
|
||||
{scheme.user}
|
||||
{creatorName(scheme.username)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box className="flex items-center gap-2">
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface SchemeRecord {
|
||||
id: number;
|
||||
schemeName: string;
|
||||
type: string;
|
||||
user: string;
|
||||
username: string;
|
||||
create_time: string;
|
||||
startTime: string;
|
||||
// 详情信息
|
||||
|
||||
@@ -40,6 +40,7 @@ import Feature from "ol/Feature";
|
||||
import { bbox, featureCollection } from "@turf/turf";
|
||||
import Timeline from "@components/olmap/core/Controls/Timeline";
|
||||
import { ContaminantSchemaItem, ContaminantSchemeRecord } from "./types";
|
||||
import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName";
|
||||
|
||||
interface SchemeQueryProps {
|
||||
schemes?: ContaminantSchemeRecord[];
|
||||
@@ -101,6 +102,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [mapContainer, setMapContainer] = useState<HTMLElement | null>(null);
|
||||
const creatorName = useSchemeCreatorName();
|
||||
|
||||
const { open } = useNotification();
|
||||
const map = useMap();
|
||||
@@ -242,7 +244,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
id: item.scheme_id,
|
||||
schemeName: item.scheme_name,
|
||||
type: item.scheme_type,
|
||||
user: item.username,
|
||||
username: item.username,
|
||||
create_time: item.create_time,
|
||||
startTime: item.scheme_start_time,
|
||||
schemeDetail: item.scheme_detail,
|
||||
@@ -598,7 +600,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
variant="caption"
|
||||
className="font-medium text-gray-900"
|
||||
>
|
||||
{scheme.user}
|
||||
{creatorName(scheme.username)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box className="flex items-center gap-2">
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface ContaminantSchemeRecord {
|
||||
id: number;
|
||||
schemeName: string;
|
||||
type: string;
|
||||
user: string;
|
||||
username: string;
|
||||
create_time: string;
|
||||
startTime: string;
|
||||
schemeDetail?: ContaminantSchemeDetail;
|
||||
|
||||
@@ -26,6 +26,7 @@ import { NETWORK_NAME, config } from "@config/config";
|
||||
import { useControllableObjectState } from "@components/olmap/core/useControllableState";
|
||||
import { LeakageResultDetail, LeakageSchemeRecord } from "./types";
|
||||
import { FLOW_DISPLAY_UNIT, toM3h } from "@utils/units";
|
||||
import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName";
|
||||
|
||||
interface Props {
|
||||
onViewResult: (result: LeakageResultDetail) => void;
|
||||
@@ -63,6 +64,7 @@ const SchemeQuery: React.FC<Props> = ({
|
||||
const { queryAll, queryDate, expandedId } = queryState;
|
||||
const [internalSchemes, setInternalSchemes] = useState<LeakageSchemeRecord[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const creatorName = useSchemeCreatorName();
|
||||
const schemes = externalSchemes !== undefined ? externalSchemes : internalSchemes;
|
||||
const setSchemes = onSchemesChange || setInternalSchemes;
|
||||
const sortedSchemes = useMemo(
|
||||
@@ -265,7 +267,7 @@ const SchemeQuery: React.FC<Props> = ({
|
||||
用户:
|
||||
</Typography>
|
||||
<Typography variant="caption" className="font-medium text-gray-900">
|
||||
{scheme.username || "-"}
|
||||
{creatorName(scheme.username)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box className="grid grid-cols-[78px_1fr] items-center gap-x-2">
|
||||
|
||||
@@ -42,6 +42,7 @@ import { bbox, featureCollection } from "@turf/turf";
|
||||
import Timeline from "@components/olmap/core/Controls/Timeline";
|
||||
import { SchemeRecord, SchemaItem } from "./types";
|
||||
import { FLOW_DISPLAY_UNIT } from "@utils/units";
|
||||
import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName";
|
||||
|
||||
interface SchemeQueryProps {
|
||||
schemes?: SchemeRecord[];
|
||||
@@ -100,6 +101,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
const [highlightFeatures, setHighlightFeatures] = useState<Feature[]>([]);
|
||||
|
||||
const [mapContainer, setMapContainer] = useState<HTMLElement | null>(null);
|
||||
const creatorName = useSchemeCreatorName();
|
||||
|
||||
const { open } = useNotification();
|
||||
const map = useMap();
|
||||
@@ -290,7 +292,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
id: item.scheme_id,
|
||||
schemeName: item.scheme_name,
|
||||
type: item.scheme_type,
|
||||
user: item.username,
|
||||
username: item.username,
|
||||
create_time: item.create_time,
|
||||
startTime: item.scheme_start_time,
|
||||
schemeDetail: item.scheme_detail,
|
||||
@@ -479,7 +481,8 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
variant="caption"
|
||||
className="text-gray-500 block"
|
||||
>
|
||||
用户: {scheme.user} · 时间: {formatTime(scheme.create_time)}
|
||||
用户: {creatorName(scheme.username)} · 时间:{" "}
|
||||
{formatTime(scheme.create_time)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box className="flex gap-1 ml-2">
|
||||
@@ -579,7 +582,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
用户:
|
||||
</Typography>
|
||||
<Typography variant="caption" className="font-medium text-gray-900">
|
||||
{scheme.user}
|
||||
{creatorName(scheme.username)}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface SchemeRecord {
|
||||
id: number;
|
||||
schemeName: string;
|
||||
type: string;
|
||||
user: string;
|
||||
username: string;
|
||||
create_time: string;
|
||||
startTime: string;
|
||||
// 详情信息
|
||||
|
||||
@@ -37,6 +37,7 @@ import VectorSource from "ol/source/Vector";
|
||||
import { Style, Icon, Circle, Fill, Stroke } from "ol/style";
|
||||
import Feature, { FeatureLike } from "ol/Feature";
|
||||
import { bbox, featureCollection } from "@turf/turf";
|
||||
import { useSchemeCreatorName } from "@components/olmap/core/useSchemeCreatorName";
|
||||
|
||||
interface SchemeRecord {
|
||||
id: number;
|
||||
@@ -96,6 +97,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
const { queryAll, queryDate, expandedId } = queryState;
|
||||
const [internalSchemes, setInternalSchemes] = useState<SchemeRecord[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const creatorName = useSchemeCreatorName();
|
||||
|
||||
const { open } = useNotification();
|
||||
const map = useMap();
|
||||
@@ -399,8 +401,9 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
variant="caption"
|
||||
className="text-gray-500 block"
|
||||
>
|
||||
最小半径: {scheme.minDiameter} · 用户: {scheme.user} ·
|
||||
日期: {formatShortDate(scheme.create_time)}
|
||||
最小半径: {scheme.minDiameter} · 用户:{" "}
|
||||
{creatorName(scheme.user)} · 日期:{" "}
|
||||
{formatShortDate(scheme.create_time)}
|
||||
</Typography>
|
||||
</Box>
|
||||
{/* 操作按钮 */}
|
||||
@@ -490,7 +493,7 @@ const SchemeQuery: React.FC<SchemeQueryProps> = ({
|
||||
variant="caption"
|
||||
className="font-medium text-gray-900"
|
||||
>
|
||||
{scheme.user}
|
||||
{creatorName(scheme.user)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box className="flex items-center gap-2">
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { resolveSchemeCreatorName } from "./useSchemeCreatorName";
|
||||
|
||||
describe("resolveSchemeCreatorName", () => {
|
||||
it("shows the current viewer's display name for their own scheme", () => {
|
||||
expect(
|
||||
resolveSchemeCreatorName("tjwater", {
|
||||
username: "tjwater",
|
||||
name: "Test Account",
|
||||
}),
|
||||
).toBe("Test Account");
|
||||
});
|
||||
|
||||
it("keeps the stored username for another user's scheme", () => {
|
||||
expect(
|
||||
resolveSchemeCreatorName("operator", {
|
||||
username: "tjwater",
|
||||
name: "Test Account",
|
||||
}),
|
||||
).toBe("operator");
|
||||
});
|
||||
|
||||
it("falls back safely when identity data is incomplete", () => {
|
||||
expect(
|
||||
resolveSchemeCreatorName("tjwater", {
|
||||
username: "tjwater",
|
||||
name: " ",
|
||||
}),
|
||||
).toBe("tjwater");
|
||||
expect(resolveSchemeCreatorName(undefined, undefined)).toBe("-");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useGetIdentity } from "@refinedev/core";
|
||||
import { useCallback } from "react";
|
||||
|
||||
export interface SchemeViewerIdentity {
|
||||
name?: string | null;
|
||||
username?: string | null;
|
||||
}
|
||||
|
||||
export const resolveSchemeCreatorName = (
|
||||
schemeUsername: string | null | undefined,
|
||||
viewer: SchemeViewerIdentity | null | undefined,
|
||||
) => {
|
||||
const username = schemeUsername?.trim();
|
||||
if (!username) return "-";
|
||||
|
||||
const viewerUsername = viewer?.username?.trim();
|
||||
const viewerName = viewer?.name?.trim();
|
||||
return viewerUsername === username && viewerName ? viewerName : username;
|
||||
};
|
||||
|
||||
export const useSchemeCreatorName = () => {
|
||||
const { data: viewer } = useGetIdentity<SchemeViewerIdentity>();
|
||||
|
||||
return useCallback(
|
||||
(schemeUsername: string | null | undefined) =>
|
||||
resolveSchemeCreatorName(schemeUsername, viewer),
|
||||
[viewer],
|
||||
);
|
||||
};
|
||||
Vendored
+3
@@ -7,6 +7,7 @@ declare module "next-auth" {
|
||||
error?: "RefreshAccessTokenError";
|
||||
user?: {
|
||||
id?: string;
|
||||
username?: string;
|
||||
name?: string | null;
|
||||
email?: string | null;
|
||||
image?: string | null;
|
||||
@@ -15,12 +16,14 @@ declare module "next-auth" {
|
||||
|
||||
interface User {
|
||||
id?: string;
|
||||
username?: string;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "next-auth/jwt" {
|
||||
interface JWT {
|
||||
sub?: string;
|
||||
username?: string;
|
||||
accessToken?: string;
|
||||
refreshToken?: string;
|
||||
accessTokenExpires?: number;
|
||||
|
||||
Reference in New Issue
Block a user