Hooking get users with golang

This commit is contained in:
Mitchell Magro 2025-10-29 19:34:10 +01:00
parent be1df88e75
commit 0c148b328d
12 changed files with 138 additions and 129 deletions

View File

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

View File

@ -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: [],
},
];

View 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 }
);
}
}

View File

@ -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={[]} />
<Users users={data?.users} />
</div>
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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" },

View File

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