Hooking get users with golang
This commit is contained in:
parent
be1df88e75
commit
0c148b328d
@ -24,6 +24,8 @@ export async function POST() {
|
||||
|
||||
const data = await resp.json();
|
||||
|
||||
console.log("[DEBUG] [VALIDATE-AUTH][ROUTE][data]: ", data);
|
||||
|
||||
return NextResponse.json(data, { status: resp.status });
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : "Unknown error";
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
// mockData.ts
|
||||
export type User = {
|
||||
merchantId: number;
|
||||
id: string;
|
||||
name?: string;
|
||||
username?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
jobTitle?: string;
|
||||
enabled?: boolean;
|
||||
authorities?: string[];
|
||||
allowedMerchantIds?: number[];
|
||||
created?: string;
|
||||
disabledBy?: string | null;
|
||||
disabledDate?: string | null;
|
||||
disabledReason?: string | null;
|
||||
incidentNotes?: boolean;
|
||||
lastLogin?: string;
|
||||
lastMandatoryUpdated?: string;
|
||||
marketingNewsletter?: boolean;
|
||||
releaseNotes?: boolean;
|
||||
requiredActions?: string[];
|
||||
twoFactorCondition?: string;
|
||||
twoFactorCredentials?: unknown[]; // narrow if you know the shape
|
||||
};
|
||||
|
||||
export const users: User[] = [
|
||||
{
|
||||
merchantId: 100987998,
|
||||
id: "bc6a8a55-13bc-4538-8255-cd0cec3bb4e9",
|
||||
name: "Jacob",
|
||||
username: "lspaddy",
|
||||
firstName: "Paddy",
|
||||
lastName: "Man",
|
||||
email: "patrick@omegasys.eu",
|
||||
phone: "",
|
||||
jobTitle: "",
|
||||
enabled: true,
|
||||
authorities: [
|
||||
"ROLE_IIN",
|
||||
"ROLE_FIRST_APPROVER",
|
||||
"ROLE_RULES_ADMIN",
|
||||
"ROLE_TRANSACTION_VIEWER",
|
||||
"ROLE_IIN_ADMIN",
|
||||
"ROLE_USER_PSP_ACCOUNT",
|
||||
],
|
||||
allowedMerchantIds: [100987998],
|
||||
created: "2025-05-04T15:32:48.432Z",
|
||||
disabledBy: null,
|
||||
disabledDate: null,
|
||||
disabledReason: null,
|
||||
incidentNotes: false,
|
||||
lastLogin: "",
|
||||
lastMandatoryUpdated: "2025-05-04T15:32:48.332Z",
|
||||
marketingNewsletter: false,
|
||||
releaseNotes: false,
|
||||
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PASSWORD"],
|
||||
twoFactorCondition: "required",
|
||||
twoFactorCredentials: [],
|
||||
},
|
||||
];
|
||||
50
app/api/dashboard/admin/users/route.ts
Normal file
50
app/api/dashboard/admin/users/route.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const BE_BASE_URL = process.env.BE_BASE_URL || "http://localhost:5000";
|
||||
const COOKIE_NAME = "auth_token";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const { cookies } = await import("next/headers");
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get(COOKIE_NAME)?.value;
|
||||
|
||||
if (!token) {
|
||||
return NextResponse.json(
|
||||
{ message: "Missing Authorization header" },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
// Preserve query string when proxying
|
||||
const url = new URL(request.url);
|
||||
const queryString = url.search ? url.search : "";
|
||||
|
||||
console.log(
|
||||
"[DEBUG] - URL",
|
||||
`${BE_BASE_URL}/api/v1/users/list${queryString}`
|
||||
);
|
||||
|
||||
const response = await fetch(
|
||||
`${BE_BASE_URL}/api/v1/users/list${queryString}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
cache: "no-store",
|
||||
}
|
||||
);
|
||||
|
||||
console.log("[DEBUG] - Response", response);
|
||||
const data = await response.json();
|
||||
return NextResponse.json(data, { status: response.status });
|
||||
} catch (err: unknown) {
|
||||
console.error("Proxy GET /api/v1/users/list error:", err);
|
||||
const errorMessage = err instanceof Error ? err.message : "Unknown error";
|
||||
return NextResponse.json(
|
||||
{ message: "Internal server error", error: errorMessage },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,60 @@
|
||||
import Users from "@/app/features/Pages/Admin/Users/users";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export default async function BackOfficeUsersPage() {
|
||||
// const baseUrl = process.env.NEXT_PUBLIC_BASE_URL
|
||||
// ? `${process.env.NEXT_PUBLIC_BASE_URL}`
|
||||
// : "http://localhost:4000";
|
||||
// const res = await fetch(`${baseUrl}/api/dashboard/admin/users`, {
|
||||
// cache: "no-store", // 👈 disables caching for SSR freshness
|
||||
// });
|
||||
// const users = await res.json();
|
||||
export default async function BackOfficeUsersPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||
}) {
|
||||
// Await searchParams and extract pagination
|
||||
const params = await searchParams;
|
||||
const limit = params.limit || "10";
|
||||
const page = params.page || "1";
|
||||
|
||||
// Build absolute URL for server-side fetch
|
||||
// In server components, fetch requires absolute URLs
|
||||
const port = process.env.PORT || "3000";
|
||||
const baseUrl =
|
||||
process.env.NEXT_PUBLIC_BASE_URL ||
|
||||
(process.env.VERCEL_URL
|
||||
? `https://${process.env.VERCEL_URL}`
|
||||
: `http://localhost:${port}`);
|
||||
|
||||
const url = new URL(`${baseUrl}/api/dashboard/admin/users`);
|
||||
url.searchParams.set("limit", typeof limit === "string" ? limit : limit[0]);
|
||||
url.searchParams.set("page", typeof page === "string" ? page : page[0]);
|
||||
|
||||
// Forward cookies for auth when calling the internal API route
|
||||
const cookieStore = await cookies();
|
||||
const cookieHeader = cookieStore
|
||||
.getAll()
|
||||
.map((c: { name: string; value: string }) => `${c.name}=${c.value}`)
|
||||
.join("; ");
|
||||
|
||||
const res = await fetch(url.toString(), {
|
||||
headers: {
|
||||
Cookie: cookieHeader,
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
console.error("Failed to fetch users:", res.status, res.statusText);
|
||||
return (
|
||||
<div>
|
||||
<Users users={[]} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
console.log("[DEBUG] - DATA", data);
|
||||
// Handle different response structures: could be array directly, or wrapped in data/users property
|
||||
// const users = Array.isArray(data) ? data : data.users || data.data || [];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Users users={data?.users} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,27 +1,12 @@
|
||||
export interface IUser {
|
||||
merchantId: number;
|
||||
name?: string;
|
||||
id: string;
|
||||
username: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
username: string;
|
||||
phone: string;
|
||||
jobTitle: string;
|
||||
job_title: string;
|
||||
groups: string[]; // or a more specific type if you know what’s inside
|
||||
merchants: string[]; // likewise, refine this if needed
|
||||
enabled: boolean;
|
||||
authorities: string[];
|
||||
allowedMerchantIds: number[];
|
||||
created: string;
|
||||
disabledBy: string | null;
|
||||
disabledDate: string | null;
|
||||
disabledReason: string | null;
|
||||
incidentNotes: boolean;
|
||||
lastLogin: string;
|
||||
lastMandatoryUpdated: string;
|
||||
marketingNewsletter: boolean;
|
||||
releaseNotes: boolean;
|
||||
requiredActions: string[];
|
||||
twoFactorCondition: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
twoFactorCredentials: any[]; // Assuming this is an array that could contain any type of data
|
||||
}
|
||||
|
||||
@ -26,13 +26,10 @@ const Users: React.FC<UsersProps> = ({ users }) => {
|
||||
}}
|
||||
/>
|
||||
{users?.length > 0 &&
|
||||
users.map((user: IUser) => (
|
||||
users?.map((user: IUser) => (
|
||||
<Card key={user.id} sx={{ mb: 2 }}>
|
||||
<CardContent>
|
||||
<Typography variant="h6">{user.username}</Typography>
|
||||
<Typography variant="body2">
|
||||
Merchant ID: {user.merchantId}
|
||||
</Typography>
|
||||
|
||||
<Stack direction="row" spacing={1} mt={1}>
|
||||
<UserRoleCard
|
||||
|
||||
@ -17,9 +17,8 @@ const NewUser: React.FC = () => {
|
||||
|
||||
const handleClose = () => {
|
||||
dispatch(clearAddedUser());
|
||||
dispatch(setShowNewUserModal(false));
|
||||
// Refresh data after closing to update lists without racing rehydrate
|
||||
router.refresh();
|
||||
dispatch(setShowNewUserModal(false));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -25,19 +25,11 @@ import { IUser } from "../Pages/Admin/Users/interfaces";
|
||||
|
||||
interface Props {
|
||||
user: IUser;
|
||||
isAdmin: boolean;
|
||||
lastLogin: string;
|
||||
merchants: string[];
|
||||
extraRolesCount?: number;
|
||||
}
|
||||
|
||||
export default function UserRoleCard({
|
||||
user,
|
||||
isAdmin,
|
||||
extraRolesCount,
|
||||
}: Props) {
|
||||
export default function UserRoleCard({ user }: Props) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const { username, name, email, authorities: roles } = user;
|
||||
const { username, first_name, last_name, email, groups } = user;
|
||||
const handleEditClick = () => {
|
||||
setIsEditing(!isEditing);
|
||||
};
|
||||
@ -50,10 +42,12 @@ export default function UserRoleCard({
|
||||
<Avatar>{username?.slice(0, 2).toUpperCase()}</Avatar>
|
||||
<Box flexGrow={1}>
|
||||
<Typography fontWeight="bold">{username}</Typography>
|
||||
<Typography variant="body2">{name}</Typography>
|
||||
<Typography variant="body2">
|
||||
{first_name} {last_name}
|
||||
</Typography>
|
||||
<Typography variant="caption">{email}</Typography>
|
||||
</Box>
|
||||
{isAdmin && (
|
||||
{true && (
|
||||
<Chip icon={<AdminPanelSettings />} label="Admin" size="small" />
|
||||
)}
|
||||
<IconButton>
|
||||
@ -100,11 +94,11 @@ export default function UserRoleCard({
|
||||
</Typography>
|
||||
<Stack direction="row" spacing={1} mt={1} flexWrap="wrap">
|
||||
<Stack direction="row" spacing={1}>
|
||||
{roles.map(role => (
|
||||
{groups.map(role => (
|
||||
<Chip key={role} label={role} size="small" />
|
||||
))}
|
||||
</Stack>
|
||||
{extraRolesCount && <Chip label={`+${extraRolesCount}`} />}
|
||||
{/* {extraRolesCount && <Chip label={`+${extraRolesCount}`} />} */}
|
||||
</Stack>
|
||||
</Box>
|
||||
<div
|
||||
|
||||
@ -158,12 +158,14 @@ export const validateAuth = createAsyncThunk<
|
||||
{ tokenInfo: TokenInfo | null },
|
||||
void,
|
||||
{ rejectValue: ThunkError }
|
||||
>("auth/validate", async (_, { rejectWithValue }) => {
|
||||
>("auth/validate", async (_, { rejectWithValue, dispatch }) => {
|
||||
try {
|
||||
const res = await fetch("/api/auth/validate", { method: "POST" });
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok || !data.valid) {
|
||||
if (!res.ok || !data.success) {
|
||||
toast.error(data.message || "Session invalid or expired");
|
||||
dispatch(autoLogout("Session invalid or expired"));
|
||||
return rejectWithValue("Session invalid or expired");
|
||||
}
|
||||
|
||||
|
||||
@ -52,6 +52,7 @@
|
||||
"@types/react-dom": "^19",
|
||||
"@types/react-redux": "^7.1.34",
|
||||
"@types/redux-persist": "^4.3.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.3",
|
||||
"husky": "^9.1.7",
|
||||
|
||||
@ -1,20 +1,6 @@
|
||||
import { IEditUserForm } from "@/app/features/UserRoles/User.interfaces";
|
||||
|
||||
// export async function addUser(data: IEditUserForm) {
|
||||
// const res = await fetch("/api/auth/register", {
|
||||
// method: "POST",
|
||||
// headers: { "Content-Type": "application/json" },
|
||||
// body: JSON.stringify(data),
|
||||
// });
|
||||
|
||||
// if (!res.ok) {
|
||||
// throw new Error("Failed to create role");
|
||||
// }
|
||||
|
||||
// return res.json(); // or return type depending on your backend
|
||||
// }
|
||||
export async function editUser(id: string, data: IEditUserForm) {
|
||||
console.log("[editUser] - id", id, data);
|
||||
const res = await fetch(`/api/dashboard/admin/users/${id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
||||
13
yarn.lock
13
yarn.lock
@ -233,6 +233,11 @@
|
||||
resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz"
|
||||
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
|
||||
|
||||
"@epic-web/invariant@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@epic-web/invariant/-/invariant-1.0.0.tgz#1073e5dee6dd540410784990eb73e4acd25c9813"
|
||||
integrity sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.7.0":
|
||||
version "4.7.0"
|
||||
resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz"
|
||||
@ -1619,6 +1624,14 @@ crc-32@~1.2.0, crc-32@~1.2.1:
|
||||
resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz"
|
||||
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
|
||||
|
||||
cross-env@^10.1.0:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-10.1.0.tgz#cfd2a6200df9ed75bfb9cb3d7ce609c13ea21783"
|
||||
integrity sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==
|
||||
dependencies:
|
||||
"@epic-web/invariant" "^1.0.0"
|
||||
cross-spawn "^7.0.6"
|
||||
|
||||
cross-spawn@^7.0.6:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user