Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: NextJS user api #246

Merged
merged 11 commits into from
Sep 6, 2023
59 changes: 18 additions & 41 deletions services/web-app/src/app/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,24 @@
"use client";
import Image from "next/image";
import { User } from "next-auth";
import { useSession } from "next-auth/react";
import { useEffect, useState } from "react";

import { ReturnToHome } from "../../components/cards/returnToHome";
import UpdateAPIKey from "../../components/dialog/updateApiKey";
import Loading from "../../components/loading/loading";
import { RepoTable } from "../../components/tables/repoTable";
import useAxios from "../../lib/hooks/useAxios";
import { UserBody } from "../../lib/types";
import { useUserApi } from "../../pages/api/user/useUser";

const containsUserDataFields = (input: object): boolean =>
"email" in input &&
typeof input.email === "string" &&
"userId" in input &&
typeof input.userId === "string" &&
"apiKey" in input &&
typeof input.apiKey === "string" &&
"name" in input &&
typeof input.name === "string";

const isValidUserData = (input: unknown): input is User =>
typeof input === "object" && input !== null && containsUserDataFields(input);

export default async function Profile(): Promise<JSX.Element> {
let user: User;
export default function Profile(): JSX.Element {
const { data: session, status } = useSession();
const { axiosInstance } = await useAxios();
const [data, setData] = useState<string | null>(null);
const [user, setUser] = useState<UserBody | null>(null);
const [loading, setLoading] = useState(false);

useEffect(() => {
const fetchData = async () => {
setLoading(true);
const { getUser } = useUserApi();
try {
if (
session === null ||
Expand All @@ -41,23 +27,20 @@ export default async function Profile(): Promise<JSX.Element> {
) {
throw new Error("Session data not fetched correctly.");
}
const response = await axiosInstance.get(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`/getUser?userId=${session.user.userId}`
);
//We need response.data to be of type string so that it can be parsed into user data
if (typeof response.data !== "string") {
throw new Error("Session data not fetched correctly.");
}
setData(response.data);
const userId = session.user.userId;
const parsedUser = await getUser({ userId });
setUser(parsedUser);
} catch (err) {
console.error("Failed to getUser, due to the following error ", err);
} finally {
setLoading(false);
}
};
void fetchData();
}, [session?.user]);

if (!user) {
void fetchData();
}
}, [session?.user?.userId, user]);

if (status === "loading" || loading) {
return <Loading />;
Expand All @@ -67,25 +50,19 @@ export default async function Profile(): Promise<JSX.Element> {
return <ReturnToHome message="You are not logged in" />;
}

if (!data) {
// Check this error
if (!user) {
return <ReturnToHome message="Could not retrieve User data." />;
} else {
const parsedData: unknown = JSON.parse(data);
if (!isValidUserData(parsedData)) {
return <ReturnToHome message="Could not retrieve valid User data." />;
} else {
user = parsedData;
}
}

const handleUpdateApiKey = async (newApiKey: string) => {
try {
const response = await axiosInstance.post(`/updateUser`, {
const { updateUser } = useUserApi();
await updateUser({
apiKey: newApiKey,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
userId: user.userId,
});
console.log("API key updated successfully:", response.data);
console.log("API key updated successfully");
} catch (error) {
console.error("Failed to update API key:", error);
}
Expand Down
32 changes: 17 additions & 15 deletions services/web-app/src/components/buttons/basicButton.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import React from "react";

import React, { forwardRef } from "react";
export interface ButtonProps {
text: string;
onClick?: () => void;
styling?: string;
}

const BasicButton: React.FC<ButtonProps> = ({ text, onClick, styling }) => {
return (
<button
className={`bg-black text-white sm:font-light font-extralight py-2 px-4 rounded ${styling ?? ""}`}
onClick={onClick}
>
{text}
</button>
);
};

export default BasicButton;
const BasicButton = forwardRef<HTMLButtonElement, ButtonProps>(
({ text, onClick, styling }, ref) => {
return (
<button
ref={ref}
className={`bg-black text-white sm:font-light font-extralight py-2 px-4 rounded ${
styling ?? ""
}`}
onClick={onClick}
>
{text}
</button>
);
}
);
export default BasicButton;
20 changes: 1 addition & 19 deletions services/web-app/src/lib/hooks/useAxios.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,12 @@
import axios, { type AxiosInstance } from "axios";
import { getSession } from "next-auth/react";

import { BASE_URL } from "../constants";

const axiosInstance = axios.create({
baseURL: `${BASE_URL}`,
});

const useAxios = async (): Promise<{ axiosInstance: AxiosInstance }> => {
const session = await getSession();

if (session === null || !('token' in session)) {
throw new Error(
"Error: logged in user's session data not fetched correctly."
);
}

axiosInstance.interceptors.request.clear();
axiosInstance.interceptors.request.use(
(config) => {
config.headers["Authorization"] = `Bearer ${session.token ?? ""}`;

return config;
}
);

export const useAxios = (): { axiosInstance: AxiosInstance } => {
return { axiosInstance };
};

Expand Down
18 changes: 16 additions & 2 deletions services/web-app/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
export interface User {
export interface UserBody {
email: string ,
userId: string,
apiKey: string,
name: string,
pictureUrl?: string,
repos?: [],
}
}

export type GetUserProps = {
userId: string;
};

export type UpdateUserProps = {
apiKey: string;
userId: string;
};

export type UseUserApiResponse = {
updateUser: (updateUserProps: UpdateUserProps) => Promise<void>;
getUser: (getUserProps: GetUserProps) => Promise<UserBody>;
}
1 change: 0 additions & 1 deletion services/web-app/src/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ const authOptions: NextAuthOptions = {
async session({ session, token }) {
if (session.user) {
session.user.userId = token.sub;
session.token = token.token;
}

return session;
Expand Down
12 changes: 12 additions & 0 deletions services/web-app/src/pages/api/user/getUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { type AxiosInstance } from "axios";

import { GetUserProps, UserBody } from "../../../lib/types";

export const getUser = async (
getUserProps : GetUserProps,
axiosInstance: AxiosInstance,
): Promise<UserBody> => {
const { data } = await axiosInstance.get<string>(`/getUser?userId=${getUserProps.userId}`);

return JSON.parse(data) as UserBody;
};
9 changes: 9 additions & 0 deletions services/web-app/src/pages/api/user/updateUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { type AxiosInstance } from "axios";

import { UpdateUserProps } from "../../../lib/types";

export const updateUser = async (
userUpdatableProps: UpdateUserProps,
axiosInstance: AxiosInstance
): Promise<void> =>
axiosInstance.post(`/updateUser`, userUpdatableProps);
19 changes: 19 additions & 0 deletions services/web-app/src/pages/api/user/useUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { getUser } from "./getUser";
import { updateUser } from "./updateUser";
import { useAxios } from "../../../lib/hooks/useAxios";
import {
GetUserProps,
UpdateUserProps,
UseUserApiResponse,
} from "../../../lib/types";

export const useUserApi = (): UseUserApiResponse => {
const { axiosInstance } = useAxios();

return {
updateUser: async (updateUserProps: UpdateUserProps) =>
await updateUser(updateUserProps, axiosInstance),
getUser: async (getUserProps: GetUserProps) =>
await getUser(getUserProps, axiosInstance),
};
};
2 changes: 1 addition & 1 deletion services/web-app/types/next-auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { JWT } from "next-auth/jwt";

declare module "next-auth" {
interface Session {
token?: string;
token?: JWT;
user?: User;
expires?: ISODateString;
}
Expand Down