This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a commit to branch cybershuttle-staging
in repository https://gitbox.apache.org/repos/asf/airavata.git


The following commit(s) were added to refs/heads/cybershuttle-staging by this 
push:
     new cd8d67670f Authentication for Research Portal (#481)
cd8d67670f is described below

commit cd8d67670f926cdc0f16bca0adb32b79f25c409f
Author: Ganning Xu <[email protected]>
AuthorDate: Fri Apr 4 00:17:08 2025 -0400

    Authentication for Research Portal (#481)
    
    * Basic auth
    
    * Broken authzfilter authorization
    
    * sections
    
    * pushing...
    
    * application.yml change
    
    * prep for deployment
    
    * change app url
    
    * Full support for frontend + backend
    
    * Change dev user and app url
---
 modules/research-framework/portal/src/App.tsx      |  20 +--
 .../portal/src/components/PageHeader.tsx           |   4 +-
 .../portal/src/components/auth/login.tsx           |  70 ++++-----
 .../portal/src/components/datasets/index.tsx       |  12 +-
 .../portal/src/components/home/ProjectCard.tsx     |  51 +------
 .../portal/src/components/home/ProjectsSection.tsx |  23 +--
 .../portal/src/components/home/ResourceCard.tsx    |  12 +-
 .../portal/src/components/home/SessionCard.tsx     |  57 +++----
 .../src/components/home/SessionCardControls.tsx    |  88 +++++++++++
 .../portal/src/components/home/SessionsSection.tsx |  56 +++++--
 .../home/StartSessionFromProjectButton.tsx         | 126 ++++++++++++++++
 .../portal/src/components/home/index.tsx           |   2 +
 .../portal/src/components/models/index.tsx         |  11 +-
 .../src/components/notebooks/ProjectDetails.tsx    | 163 ---------------------
 .../portal/src/components/notebooks/index.tsx      |  12 +-
 .../src/components/repositories/RepositoryCard.tsx |  39 -----
 .../repositories/RepositorySpecificDetails.tsx     |   4 -
 .../portal/src/components/repositories/index.tsx   |  12 +-
 .../src/components/resources/ResourceDetails.tsx   |  26 ++--
 .../portal/src/components/ui/toaster.tsx           |  43 ++++++
 .../portal/src/interfaces/ResourceType.ts          |   4 +-
 modules/research-framework/portal/src/lib/api.ts   |   8 +-
 .../research-framework/portal/src/lib/constants.ts |  10 +-
 .../portal/src/lib/controller.ts                   |   6 +
 .../service/ResponseTypes/RedirectResponse.java    |  17 +++
 .../service/controller/ProjectController.java      |  28 ++++
 .../service/controller/ResearchHubController.java  |  51 ++-----
 .../service/controller/ResourceController.java     |   9 +-
 .../service/controller/SessionController.java      |  50 +++++++
 .../research/service/handlers/ProjectHandler.java  |   6 +
 .../service/handlers/ResearchHubHandler.java       |   4 -
 .../research/service/handlers/SessionHandler.java  |   7 +-
 .../service/model/repo/SessionRepository.java      |   7 +
 .../src/main/resources/application.yml             |   3 +-
 34 files changed, 582 insertions(+), 459 deletions(-)

diff --git a/modules/research-framework/portal/src/App.tsx 
b/modules/research-framework/portal/src/App.tsx
index d8927464ca..42206ac062 100644
--- a/modules/research-framework/portal/src/App.tsx
+++ b/modules/research-framework/portal/src/App.tsx
@@ -6,7 +6,7 @@ import { Datasets } from "./components/datasets";
 import ResourceDetails from "./components/resources/ResourceDetails";
 import Notebooks from "./components/notebooks";
 import Repositories from "./components/repositories";
-import { Login } from "./components/auth/login";
+import { Login } from "./components/auth/Login";
 import ProtectedComponent from "./components/auth/ProtectedComponent";
 import { useAuth } from "react-oidc-context";
 import { useEffect } from "react";
@@ -18,9 +18,10 @@ function App() {
   }
 
   const user = useAuth();
+
   useEffect(() => {
     if (user.isAuthenticated) {
-      setUserProvider(() => user.user);
+      setUserProvider(() => Promise.resolve(user.user ?? null));
     }
   }, [user]);
 
@@ -58,21 +59,6 @@ function App() {
               element={<ProtectedComponent Component={ResourceDetails} />}
             />
           </Route>
-
-          {/* <Route path="/notebook">
-            <Route path=":slug" element={<ProjectDetails />} />
-          </Route>
-          <Route path="/repository">
-            <Route path=":slug" element={<ProjectDetails />} />
-          </Route>
-          <Route path="/models">
-            <Route index element={<Models />} />
-            <Route path=":id" element={<ModelDetails />} />
-          </Route>
-          <Route path="/datasets">
-            <Route index element={<Datasets />} />
-            <Route path=":slug" element={<DatasetDetails />} />
-          </Route> */}
         </Routes>
       </BrowserRouter>
     </>
diff --git a/modules/research-framework/portal/src/components/PageHeader.tsx 
b/modules/research-framework/portal/src/components/PageHeader.tsx
index f4e1e8ee85..f145d0994d 100644
--- a/modules/research-framework/portal/src/components/PageHeader.tsx
+++ b/modules/research-framework/portal/src/components/PageHeader.tsx
@@ -1,5 +1,5 @@
 import { Box, HStack, Text, Heading, Icon } from "@chakra-ui/react";
-import { ReactNode } from "react";
+import { ElementType } from "react";
 
 export const PageHeader = ({
   title,
@@ -7,7 +7,7 @@ export const PageHeader = ({
   description,
 }: {
   title: string;
-  icon?: ReactNode;
+  icon?: ElementType;
   description: string;
 }) => {
   const fontSize = "4xl";
diff --git a/modules/research-framework/portal/src/components/auth/login.tsx 
b/modules/research-framework/portal/src/components/auth/login.tsx
index 9dc3a1c88b..545fb3eeff 100644
--- a/modules/research-framework/portal/src/components/auth/login.tsx
+++ b/modules/research-framework/portal/src/components/auth/login.tsx
@@ -1,46 +1,40 @@
-import { Box, Center, SimpleGrid, Flex, Text, Button } from "@chakra-ui/react";
+import { Center, Text, Button, Image, VStack, Stack } from "@chakra-ui/react";
 import { useAuth } from "react-oidc-context";
+import AirvataLogo from "../../assets/airavata-logo.png";
 
 export const Login = () => {
   const auth = useAuth();
   return (
-    <>
-      <Center height="100vh">
-        <SimpleGrid
-          columns={{
-            base: 1,
-            lg: 2,
-          }}
-          gap={32}
-          alignItems="center"
-        >
-          <Box>
-            <Flex gap={4} alignItems="center">
-              <Box>
-                <Text fontWeight="bold">Airavata Data Catalog</Text>
-                <Text color="gray.500" fontSize="sm">
-                  Login to access the data catalog
-                </Text>
-              </Box>
-            </Flex>
+    <Center height="100vh">
+      <Stack
+        gap={8}
+        alignItems="center"
+        padding={4}
+        direction={{ base: "column", lg: "row" }}
+      >
+        <Image src={AirvataLogo} alt="Airavata Logo" maxWidth="100px" />
+        <VStack gap={1} alignItems="flex-start">
+          <Text fontWeight="bold" fontSize="2xl">
+            Cybershuttle Data Catalog
+          </Text>
+          <Text color="gray.500" fontSize="sm" maxWidth="300px">
+            Jupyter Notebooks, repositories, datasets, and models, for
+            scientists, by scientists.
+          </Text>
 
-            <Button
-              bg="black"
-              color="white"
-              _hover={{ bg: "gray.800" }}
-              _active={{ bg: "gray.900" }}
-              rounded="full"
-              w="300px"
-              onClick={() => {
-                console.log("Sign in clicked");
-                auth.signinRedirect();
-              }}
-            >
-              Institution Login
-            </Button>
-          </Box>
-        </SimpleGrid>
-      </Center>
-    </>
+          <Button
+            colorPalette="black"
+            _active={{ bg: "gray.900" }}
+            w="300px"
+            onClick={() => {
+              console.log("Sign in clicked");
+              auth.signinRedirect();
+            }}
+          >
+            Institution Login
+          </Button>
+        </VStack>
+      </Stack>
+    </Center>
   );
 };
diff --git 
a/modules/research-framework/portal/src/components/datasets/index.tsx 
b/modules/research-framework/portal/src/components/datasets/index.tsx
index 6d89984f6d..690dd61152 100644
--- a/modules/research-framework/portal/src/components/datasets/index.tsx
+++ b/modules/research-framework/portal/src/components/datasets/index.tsx
@@ -10,12 +10,18 @@ import { DatasetResource } from "@/interfaces/ResourceType";
 import { useEffect, useState } from "react";
 import { ResourceCard } from "../home/ResourceCard";
 import { resourceTypeToColor } from "@/lib/util";
+import { ResourceTypeEnum } from "@/interfaces/ResourceTypeEnum";
+import { CONTROLLER } from "@/lib/controller";
 
 const getDatasets = async () => {
   try {
-    const response = await api.get(
-      "/project-management/resources?type=DATASET"
-    );
+    const response = await api.get(`${CONTROLLER.resources}/`, {
+      params: {
+        type: ResourceTypeEnum.DATASET,
+        pageNumber: 0,
+        pageSize: 100,
+      },
+    });
     const data = response.data;
     return data;
   } catch (error) {
diff --git 
a/modules/research-framework/portal/src/components/home/ProjectCard.tsx 
b/modules/research-framework/portal/src/components/home/ProjectCard.tsx
index 8096c93a4a..d11ce5c968 100644
--- a/modules/research-framework/portal/src/components/home/ProjectCard.tsx
+++ b/modules/research-framework/portal/src/components/home/ProjectCard.tsx
@@ -1,38 +1,10 @@
 import { ProjectType } from "@/interfaces/ProjectType";
-import api from "@/lib/api";
-import { API_VERSION, BACKEND_URL } from "@/lib/constants";
-import { Button, Card, HStack, Text } from "@chakra-ui/react";
-import { useEffect, useState } from "react";
-
-async function checkIfSessionWithProjectExists(project: ProjectType) {
-  try {
-    await api.get(`/hub/project/${project.id}/exists`);
-
-    return true;
-  } catch (error: any) {
-    return false;
-  }
-}
+import { Card, HStack, Text } from "@chakra-ui/react";
+import { StartSessionFromProjectButton } from 
"./StartSessionFromProjectButton";
 
 export const ProjectCard = ({ project }: { project: ProjectType }) => {
-  const [exists, setExists] = useState(false);
-  useEffect(() => {
-    let intervalId: NodeJS.Timeout;
-
-    async function fetchData() {
-      const exists = await checkIfSessionWithProjectExists(project);
-      setExists(exists);
-    }
-
-    fetchData(); // Initial fetch
-
-    intervalId = setInterval(fetchData, 2000); // Fetch every 5 seconds
-
-    return () => clearInterval(intervalId); // Cleanup on unmount
-  }, [project]); // Depend on project so it updates correctly
-
   return (
-    <Card.Root overflow="hidden" size="md">
+    <Card.Root overflow="hidden" size="md" height="fit-content">
       <Card.Body gap="2">
         {/* Card Content */}
         <HStack alignItems="center" justifyContent="space-between">
@@ -55,22 +27,7 @@ export const ProjectCard = ({ project }: { project: 
ProjectType }) => {
           </Text>
         </Text>
 
-        {exists ? (
-          <Text color="green.600">Session already started</Text>
-        ) : (
-          <Button
-            colorPalette="black"
-            size="sm"
-            onClick={() => {
-              window.open(
-                
`${BACKEND_URL}/api/${API_VERSION}/rf/hub/project/${project.id}?sessionName=${project.name}`,
-                "_blank"
-              );
-            }}
-          >
-            Open Project
-          </Button>
-        )}
+        <StartSessionFromProjectButton project={project} />
       </Card.Body>
     </Card.Root>
   );
diff --git 
a/modules/research-framework/portal/src/components/home/ProjectsSection.tsx 
b/modules/research-framework/portal/src/components/home/ProjectsSection.tsx
index ad8f930465..062cc3a171 100644
--- a/modules/research-framework/portal/src/components/home/ProjectsSection.tsx
+++ b/modules/research-framework/portal/src/components/home/ProjectsSection.tsx
@@ -3,19 +3,22 @@ import api from "@/lib/api";
 import { SimpleGrid } from "@chakra-ui/react";
 import { useEffect, useState } from "react";
 import { ProjectCard } from "./ProjectCard";
+import { CONTROLLER } from "@/lib/controller";
 
-async function getAllProjects() {
-  try {
-    const response = await api.get("/hub/projects");
-    const data = response.data;
-    return data;
-  } catch (error) {
-    console.error("Error fetching:", error);
-  }
-}
 export const ProjectsSection = () => {
   const [projects, setProjects] = useState<ProjectType[]>([]);
 
+  async function getAllProjects() {
+    try {
+      const response = await api.get(`${CONTROLLER.projects}/`);
+      const data = response.data;
+      console.log("projects", data);
+      return data;
+    } catch (error) {
+      console.error("Error fetching:", error);
+    }
+  }
+
   useEffect(() => {
     async function init() {
       const projects = await getAllProjects();
@@ -26,7 +29,7 @@ export const ProjectsSection = () => {
     init();
   }, []);
   return (
-    <SimpleGrid mt={2} columns={{ base: 1, md: 2, lg: 2 }}>
+    <SimpleGrid mt={2} columns={{ base: 1, md: 2, lg: 2 }} gap={4}>
       {projects.map((project: ProjectType) => {
         return <ProjectCard project={project} key={project.id} />;
       })}
diff --git 
a/modules/research-framework/portal/src/components/home/ResourceCard.tsx 
b/modules/research-framework/portal/src/components/home/ResourceCard.tsx
index b5bc8c26e8..bceb4bdd92 100644
--- a/modules/research-framework/portal/src/components/home/ResourceCard.tsx
+++ b/modules/research-framework/portal/src/components/home/ResourceCard.tsx
@@ -16,8 +16,8 @@ import { ResourceTypeEnum } from 
"@/interfaces/ResourceTypeEnum";
 import { ModelCardButton } from "../models/ModelCardButton";
 
 export const ResourceCard = ({ resource }: { resource: Resource }) => {
-  console.log("Resource:", resource);
   const author = resource.authors[0];
+  console.log("author" + author);
 
   const isValidImage = isValidImaage(resource.headerImage);
 
@@ -82,16 +82,12 @@ export const ResourceCard = ({ resource }: { resource: 
Resource }) => {
           {author && (
             <HStack>
               <Avatar.Root shape="full" size="sm">
-                <Avatar.Fallback
-                  name={author.firstName + " " + author.lastName}
-                />
-                <Avatar.Image src={author.avatar} />
+                <Avatar.Fallback name={author} />
+                <Avatar.Image src={author} />
               </Avatar.Root>
 
               <Box>
-                <Text fontWeight="bold">
-                  {author.firstName + " " + author.lastName}
-                </Text>
+                <Text fontWeight="bold">{author}</Text>
               </Box>
             </HStack>
           )}
diff --git 
a/modules/research-framework/portal/src/components/home/SessionCard.tsx 
b/modules/research-framework/portal/src/components/home/SessionCard.tsx
index a529a0cde8..6c7637ce47 100644
--- a/modules/research-framework/portal/src/components/home/SessionCard.tsx
+++ b/modules/research-framework/portal/src/components/home/SessionCard.tsx
@@ -1,30 +1,32 @@
 import { SessionType } from "@/interfaces/SessionType";
-import { API_VERSION, BACKEND_URL } from "@/lib/constants";
-import {
-  Box,
-  Text,
-  Card,
-  Heading,
-  HStack,
-  Badge,
-  Button,
-} from "@chakra-ui/react";
+import { Box, Text, Card, Heading, HStack, Badge } from "@chakra-ui/react";
+import { SessionCardControls } from "./SessionCardControls";
+import { SessionStatusEnum } from "@/interfaces/SessionStatusEnum";
 
 const getColorPalette = (status: string) => {
   switch (status) {
-    case "CREATED":
+    case SessionStatusEnum.CREATED:
       return "green";
-    case "stopped":
+    case SessionStatusEnum.RUNNING:
+      return "blue";
+    case SessionStatusEnum.FINISHED:
+      return "purple";
+    case SessionStatusEnum.TERMINATED:
+      return "gray";
+    case SessionStatusEnum.ERROR:
       return "red";
-    case "pending":
-      return "yellow";
     default:
       return "gray";
   }
 };
+
+const PREVENT_CONTROL_STATUS = [
+  SessionStatusEnum.TERMINATED,
+  SessionStatusEnum.ERROR,
+];
 export const SessionCard = ({ session }: { session: SessionType }) => {
   return (
-    <Card.Root size="sm">
+    <Card.Root size="sm" height="fit-content">
       <Card.Header>
         <HStack justify="space-between" alignItems="flex-start">
           <Box>
@@ -43,28 +45,9 @@ export const SessionCard = ({ session }: { session: 
SessionType }) => {
           : {new Date(session.createdAt).toLocaleString()}
         </Text>
 
-        <HStack alignItems="center" mt={2}>
-          <Button
-            size="sm"
-            colorPalette="red"
-            variant="subtle"
-            onClick={() => {}}
-          >
-            Terminate
-          </Button>
-          <Button
-            size="sm"
-            colorPalette="green"
-            onClick={() => {
-              window.open(
-                
`${BACKEND_URL}/api/${API_VERSION}/rf/hub/sessions/${session.id}/resolve`,
-                "_blank"
-              );
-            }}
-          >
-            Open Session
-          </Button>
-        </HStack>
+        {!PREVENT_CONTROL_STATUS.includes(session.status) && (
+          <SessionCardControls session={session} />
+        )}
       </Card.Body>
     </Card.Root>
   );
diff --git 
a/modules/research-framework/portal/src/components/home/SessionCardControls.tsx 
b/modules/research-framework/portal/src/components/home/SessionCardControls.tsx
new file mode 100644
index 0000000000..d7dc22be89
--- /dev/null
+++ 
b/modules/research-framework/portal/src/components/home/SessionCardControls.tsx
@@ -0,0 +1,88 @@
+import { SessionType } from "@/interfaces/SessionType";
+import { HStack, Button } from "@chakra-ui/react";
+import api from "@/lib/api";
+import { CONTROLLER } from "@/lib/controller";
+import { toaster } from "../ui/toaster";
+import { useState } from "react";
+import { SessionStatusEnum } from "@/interfaces/SessionStatusEnum";
+
+export const SessionCardControls = ({ session }: { session: SessionType }) => {
+  const [openLoading, setOpenLoading] = useState(false);
+  const [terminateLoading, setTerminateLoading] = useState(false);
+  const [hideControls, setHideControls] = useState(false);
+
+  const handleOpenSession = async () => {
+    setOpenLoading(true);
+    try {
+      const resp = await api.get(
+        `${CONTROLLER.hub}/resume/session/${session.id}`
+      );
+      const data = resp.data;
+      window.open(data.redirectUrl, "_blank");
+      toaster.create({
+        title: "Session started",
+        description: session.sessionName,
+        type: "success",
+      });
+    } catch {
+      toaster.create({
+        title: "Error resuming session",
+        type: "error",
+      });
+    }
+    setOpenLoading(false);
+  };
+
+  const handleTerminateSession = async () => {
+    setTerminateLoading(true);
+    try {
+      // empty request body
+      await api.patch(
+        `${CONTROLLER.sessions}/${session.id}`,
+        {},
+        {
+          params: {
+            status: SessionStatusEnum.TERMINATED,
+          },
+        }
+      );
+
+      toaster.create({
+        title: "Session terminated",
+        description: session.sessionName,
+        type: "success",
+      });
+      setHideControls(true);
+    } catch {
+      toaster.create({
+        title: "Error terminating session",
+        type: "error",
+      });
+    }
+    setTerminateLoading(false);
+  };
+
+  return (
+    <HStack alignItems="center" mt={2} hidden={hideControls}>
+      <Button
+        size="sm"
+        colorPalette="red"
+        variant="subtle"
+        onClick={handleTerminateSession}
+        loading={terminateLoading}
+        disabled={terminateLoading}
+      >
+        Terminate
+      </Button>
+      <Button
+        size="sm"
+        colorPalette="green"
+        onClick={handleOpenSession}
+        loading={openLoading}
+        disabled={openLoading}
+      >
+        Open Session
+      </Button>
+    </HStack>
+  );
+};
diff --git 
a/modules/research-framework/portal/src/components/home/SessionsSection.tsx 
b/modules/research-framework/portal/src/components/home/SessionsSection.tsx
index 20e8e63f44..41f230c3d5 100644
--- a/modules/research-framework/portal/src/components/home/SessionsSection.tsx
+++ b/modules/research-framework/portal/src/components/home/SessionsSection.tsx
@@ -1,12 +1,21 @@
 import { SessionType } from "@/interfaces/SessionType";
 import { SessionCard } from "./SessionCard";
-import { GridContainer } from "../GridContainer";
 import api from "@/lib/api";
 import { useEffect, useState } from "react";
+import { CONTROLLER } from "@/lib/controller";
+import { Button, HStack, SimpleGrid } from "@chakra-ui/react";
+import { SessionStatusEnum } from "@/interfaces/SessionStatusEnum";
 
-async function getSessions() {
+async function getSessions(status: SessionStatusEnum | null = null) {
   try {
-    const response = await api.get("/hub/sessions");
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const params = {} as any;
+    if (status) {
+      params.status = status;
+    }
+    const response = await api.get(`${CONTROLLER.sessions}/`, {
+      params,
+    });
     const data = response.data;
     return data;
   } catch (error) {
@@ -15,31 +24,56 @@ async function getSessions() {
 }
 
 export const SessionsSection = () => {
+  const [sessionStatusFilter, setSessionStatusFilter] =
+    useState<SessionStatusEnum | null>(null);
   const [sessions, setSessions] = useState<SessionType[]>([]);
 
   useEffect(() => {
     let intervalId: NodeJS.Timeout;
 
     async function fetchData() {
-      const sessions = await getSessions();
-      setSessions(sessions);
+      const allSessions = await getSessions(sessionStatusFilter);
+
+      setSessions(allSessions);
     }
 
     fetchData(); // Initial fetch
-
     // eslint-disable-next-line prefer-const
-    intervalId = setInterval(fetchData, 2000); // Fetch every 2 seconds
+    intervalId = setInterval(fetchData, 2000); // Refetch every 2s
 
-    return () => clearInterval(intervalId); // Cleanup on unmount
-  }, []);
+    return () => clearInterval(intervalId); // Cleanup
+  }, [sessionStatusFilter]); // <== Important dependency
 
   return (
     <>
-      <GridContainer>
+      <HStack flexWrap={"wrap"} gap={2} mt={4}>
+        <Button
+          variant={sessionStatusFilter === null ? "solid" : "outline"}
+          size="sm"
+          onClick={() => {
+            setSessionStatusFilter(null);
+          }}
+        >
+          All
+        </Button>
+        {Object.values(SessionStatusEnum).map((status) => (
+          <Button
+            key={status}
+            variant={sessionStatusFilter === status ? "solid" : "outline"}
+            size="sm"
+            onClick={() => {
+              setSessionStatusFilter(status);
+            }}
+          >
+            {status}
+          </Button>
+        ))}
+      </HStack>
+      <SimpleGrid mt={4} columns={{ base: 1, md: 2, lg: 3 }} gap={4}>
         {sessions.map((session: SessionType) => {
           return <SessionCard key={session.id} session={session} />;
         })}
-      </GridContainer>
+      </SimpleGrid>
     </>
   );
 };
diff --git 
a/modules/research-framework/portal/src/components/home/StartSessionFromProjectButton.tsx
 
b/modules/research-framework/portal/src/components/home/StartSessionFromProjectButton.tsx
new file mode 100644
index 0000000000..db0bdb7723
--- /dev/null
+++ 
b/modules/research-framework/portal/src/components/home/StartSessionFromProjectButton.tsx
@@ -0,0 +1,126 @@
+import { ProjectType } from "@/interfaces/ProjectType";
+import api from "@/lib/api";
+import { CONTROLLER } from "@/lib/controller";
+import {
+  useDialog,
+  Dialog,
+  Button,
+  Portal,
+  Fieldset,
+  Input,
+  CloseButton,
+} from "@chakra-ui/react";
+import { useState } from "react";
+import { Toaster, toaster } from "../ui/toaster";
+
+export const StartSessionFromProjectButton = ({
+  project,
+}: {
+  project: ProjectType;
+}) => {
+  const dialog = useDialog();
+  const defaultName =
+    new Date().toLocaleDateString() + " " + project.name + " Session";
+  const [sessionName, setSessionName] = useState<string>(defaultName);
+  const [loadingOpenProject, setLoadingOpenProject] = useState(false);
+
+  const handleOpenProject = async () => {
+    if (!sessionName) {
+      toaster.create({
+        title: "Session name is required",
+        type: "error",
+      });
+      return;
+    }
+    setLoadingOpenProject(true);
+    try {
+      const resp = await api.get(
+        `${CONTROLLER.hub}/start/project/${project.id}`,
+        {
+          params: {
+            sessionName: sessionName,
+          },
+        }
+      );
+      const data = resp.data;
+      window.open(data.redirectUrl, "_blank");
+      dialog.setOpen(false);
+      toaster.create({
+        title: "Session started",
+        type: "success",
+      });
+    } catch (error) {
+      console.error("Error fetching project:", error);
+      toaster.create({
+        title: "Error starting session",
+        type: "error",
+      });
+    }
+    setLoadingOpenProject(false);
+  };
+
+  return (
+    <>
+      <Toaster />
+
+      <Dialog.RootProvider
+        value={dialog}
+        size="sm"
+        lazyMount={true}
+        unmountOnExit={true}
+        onExitComplete={() => {
+          setSessionName(defaultName); // Reset session name when modal closes
+        }}
+      >
+        <Dialog.Trigger asChild>
+          <Button colorPalette="black" size="sm">
+            Start Project Session
+          </Button>
+        </Dialog.Trigger>
+
+        <Portal>
+          <Dialog.Backdrop />
+          <Dialog.Positioner>
+            <Dialog.Content>
+              <Dialog.Body p="4">
+                <Dialog.Title>New Session</Dialog.Title>
+                <Dialog.Description mt="2">
+                  Project: <b>{project.name}</b>
+                </Dialog.Description>
+
+                <Fieldset.Root size="lg" mt={2}>
+                  <Fieldset.Legend>Session Name</Fieldset.Legend>
+                  <Input
+                    placeholder="My new session"
+                    mt={2}
+                    value={sessionName}
+                    onChange={(e) => setSessionName(e.target.value)}
+                  />
+                  <Fieldset.HelperText mt="2">
+                    All session and session names are private and cannot be 
seen
+                    by anyone else.
+                  </Fieldset.HelperText>
+                </Fieldset.Root>
+
+                <Button
+                  colorPalette="black"
+                  width="100%"
+                  type="submit"
+                  mt={4}
+                  onClick={handleOpenProject}
+                  disabled={!sessionName || loadingOpenProject}
+                  loading={loadingOpenProject}
+                >
+                  Create session
+                </Button>
+              </Dialog.Body>
+              <Dialog.CloseTrigger asChild>
+                <CloseButton _hover={{ bg: "gray.100" }} size="sm" />
+              </Dialog.CloseTrigger>
+            </Dialog.Content>
+          </Dialog.Positioner>
+        </Portal>
+      </Dialog.RootProvider>
+    </>
+  );
+};
diff --git a/modules/research-framework/portal/src/components/home/index.tsx 
b/modules/research-framework/portal/src/components/home/index.tsx
index 1007007e67..3a932951f7 100644
--- a/modules/research-framework/portal/src/components/home/index.tsx
+++ b/modules/research-framework/portal/src/components/home/index.tsx
@@ -53,6 +53,8 @@ const Home = () => {
           </HStack>
         </HStack>
         <SessionsSection />
+
+        <Box my={8} />
       </Container>
     </Box>
   );
diff --git a/modules/research-framework/portal/src/components/models/index.tsx 
b/modules/research-framework/portal/src/components/models/index.tsx
index 0fa41c76b5..2ae3cd27b2 100644
--- a/modules/research-framework/portal/src/components/models/index.tsx
+++ b/modules/research-framework/portal/src/components/models/index.tsx
@@ -9,10 +9,19 @@ import api from "@/lib/api";
 import { ModelResource } from "@/interfaces/ResourceType";
 import { useEffect, useState } from "react";
 import { ResourceCard } from "../home/ResourceCard";
+import { CONTROLLER } from "@/lib/controller";
+import { ResourceTypeEnum } from "@/interfaces/ResourceTypeEnum";
 
 const getModels = async () => {
   try {
-    const response = await api.get("/project-management/resources?type=MODEL");
+    const response = await api.get(`${CONTROLLER.resources}/`, {
+      params: {
+        type: ResourceTypeEnum.MODEL,
+        pageNumber: 0,
+        pageSize: 100,
+      },
+    });
+
     const data = response.data;
     return data;
   } catch (error) {
diff --git 
a/modules/research-framework/portal/src/components/notebooks/ProjectDetails.tsx 
b/modules/research-framework/portal/src/components/notebooks/ProjectDetails.tsx
deleted file mode 100644
index 9b9141d712..0000000000
--- 
a/modules/research-framework/portal/src/components/notebooks/ProjectDetails.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-import { Link, useParams } from "react-router";
-import NavBar from "../NavBar";
-import {
-  Container,
-  Spinner,
-  HStack,
-  Box,
-  Separator,
-  Button,
-  Icon,
-  Link as ChakraLink, Heading, ListItem, Center, ListRoot,
-} from "@chakra-ui/react";
-// @ts-expect-error This is fine
-import { MOCK_PROJECTS } from "../../data/MOCK_DATA";
-import { useEffect, useState } from "react";
-import { BiArrowBack } from "react-icons/bi";
-import { ProjectType } from "@/interfaces/ProjectType";
-import { Metadata } from "../Metadata";
-import {FiFile, FiFolder} from "react-icons/fi";
-
-interface FileTreeItem {
-  name: string;
-  type: string;
-  sha: string;
-}
-
-async function getProject(slug: string | undefined) {
-  return MOCK_PROJECTS.find(
-      (project: ProjectType) => project.metadata.slug === slug
-  );
-}
-
-const ProjectDetails = () => {
-  const { slug } = useParams();
-  const [project, setProject] = useState<ProjectType | null>(null);
-  const [fileTree, setFileTree] = useState<FileTreeItem[]>([]);
-  const [fileTreeLoading, setFileTreeLoading] = useState(false);
-
-  useEffect(() => {
-    if (!slug) return;
-
-    async function getData() {
-      const n = await getProject(slug);
-      setProject(n);
-    }
-    getData();
-  }, [slug]);
-
-  const isNotebook = project?.notebookViewer !== undefined;
-
-  useEffect(() => {
-    if (project && !isNotebook) {
-      const repoUrl = project.repositoryUrl;
-      // @ts-expect-error This is fine
-      const match = repoUrl.match(/github\.com\/([^/]+)\/([^/]+)/);
-      if (match) {
-        const owner = match[1];
-        const repo = match[2].replace(/\.git$/, "");
-        setFileTreeLoading(true);
-        fetch(`https://api.github.com/repos/${owner}/${repo}/contents`)
-            .then((res) => res.json())
-            .then((data) => {
-              setFileTree(data);
-              setFileTreeLoading(false);
-            })
-            .catch((error) => {
-              console.error("Error fetching file tree:", error);
-              setFileTreeLoading(false);
-            });
-      }
-    }
-  }, [project, isNotebook]);
-
-  if (!project) return <Spinner />;
-
-  return (
-      <>
-        <NavBar />
-
-        <Container maxW="breakpoint-lg" mx="auto" p={4} mt={16}>
-          <Box>
-            <HStack alignItems="center" mb={4}>
-              <Icon>
-                <BiArrowBack />
-              </Icon>
-              <Link to="/">Back to Projects</Link>
-            </HStack>
-          </Box>
-          <Metadata metadata={project.metadata} />
-
-          <Separator my={8} />
-
-          <Button colorPalette="teal" mb={4} w="full">
-            <ChakraLink
-                target="_blank"
-                href={project?.notebookViewer || project?.repositoryUrl}
-                color="white"
-                fontWeight="bold"
-                display="block"
-                width="100%"
-                textAlign="center"
-            >
-              Open
-            </ChakraLink>
-          </Button>
-
-          <Box border="1px" borderColor="gray.200" borderRadius="md" 
overflow="hidden">
-            <Box height="600px" bg="gray.50" p={4} overflow="auto">
-              {isNotebook ? (
-                  <iframe
-                      title="notebook"
-                      src={project?.notebookViewer}
-                      width="100%"
-                      height="100%"
-                      style={{ border: "none" }}
-                  />
-              ) : fileTreeLoading ? (
-                  <Center height="100%">
-                    <Spinner size="xl" />
-                  </Center>
-              ) : (
-                  <Box
-                      bg="white"
-                      p={4}
-                      borderRadius="md"
-                      shadow="md"
-                      overflow="auto"
-                      height="full"
-                  >
-                    <Heading as="h2" size="lg" mb={4}>
-                      {project.metadata.title}
-                    </Heading>
-                    <ListRoot>
-                      {fileTree &&
-                          fileTree.map((file) => (
-                              <ListItem
-                                  key={file.sha}
-                                  display="flex"
-                                  alignItems="center"
-                                  p={2}
-                                  borderRadius="md"
-                                  _hover={{ bg: "gray.100" }}
-                              >
-                                <Icon
-                                    as={file.type === "dir" ? FiFolder : 
FiFile}
-                                    color={file.type === "dir" ? "blue.500" : 
"gray.500"}
-                                    mr={2}
-                                />
-
-                                <p>{file.name}</p>
-                              </ListItem>
-                          ))}
-                    </ListRoot>
-                  </Box>
-              )}
-            </Box>
-          </Box>
-        </Container>
-      </>
-  );
-};
-
-export default ProjectDetails;
diff --git 
a/modules/research-framework/portal/src/components/notebooks/index.tsx 
b/modules/research-framework/portal/src/components/notebooks/index.tsx
index 5730581c7f..7baa79aee7 100644
--- a/modules/research-framework/portal/src/components/notebooks/index.tsx
+++ b/modules/research-framework/portal/src/components/notebooks/index.tsx
@@ -7,12 +7,18 @@ import { useEffect, useState } from "react";
 import api from "@/lib/api";
 import { NotebookResource } from "@/interfaces/ResourceType";
 import { ResourceCard } from "../home/ResourceCard";
+import { CONTROLLER } from "@/lib/controller";
+import { ResourceTypeEnum } from "@/interfaces/ResourceTypeEnum";
 
 const getNotebooks = async () => {
   try {
-    const response = await api.get(
-      "/project-management/resources?type=NOTEBOOK"
-    );
+    const response = await api.get(`${CONTROLLER.resources}/`, {
+      params: {
+        type: ResourceTypeEnum.NOTEBOOK,
+        pageNumber: 0,
+        pageSize: 100,
+      },
+    });
     const data = response.data;
     return data;
   } catch (error) {
diff --git 
a/modules/research-framework/portal/src/components/repositories/RepositoryCard.tsx
 
b/modules/research-framework/portal/src/components/repositories/RepositoryCard.tsx
deleted file mode 100644
index 5d5f2ba8e4..0000000000
--- 
a/modules/research-framework/portal/src/components/repositories/RepositoryCard.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import {
-  Card,
-  Heading,
-  Box,
-  Text,
-  Link as ChakraLink,
-  HStack,
-} from "@chakra-ui/react";
-export const RepositoryCard = ({ repository }) => {
-  return (
-    <Card.Root width="100%">
-      <Card.Header>
-        <HStack justifyContent="space-between" alignItems="flex-start">
-          <Box>
-            <Heading size="md">{repository.title}</Heading>
-            <Text color="gray.500" mt={2}>
-              {repository.date}
-            </Text>
-          </Box>
-
-          <Text>
-            <ChakraLink
-              href={repository.githubUrl}
-              target="_blank"
-              bg="black"
-              color="white"
-              p={2}
-              rounded="md"
-              fontWeight="bold"
-            >
-              View on GitHub
-            </ChakraLink>
-          </Text>
-        </HStack>
-      </Card.Header>
-      <Card.Body color="fg.muted">{repository.description}</Card.Body>
-    </Card.Root>
-  );
-};
diff --git 
a/modules/research-framework/portal/src/components/repositories/RepositorySpecificDetails.tsx
 
b/modules/research-framework/portal/src/components/repositories/RepositorySpecificDetails.tsx
index 2dd16cb69e..ba1f3f08a3 100644
--- 
a/modules/research-framework/portal/src/components/repositories/RepositorySpecificDetails.tsx
+++ 
b/modules/research-framework/portal/src/components/repositories/RepositorySpecificDetails.tsx
@@ -32,8 +32,6 @@ export const RepositorySpecificDetails = ({
   const [currentPath, setCurrentPath] = useState<string>("");
   const [history, setHistory] = useState<string[]>([]);
   const [fileContent, setFileContent] = useState<string | null>(null);
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  const [fileName, setFileName] = useState<string | null>(null);
   const [error, setError] = useState<string | null>(null);
 
   useEffect(() => {
@@ -105,7 +103,6 @@ export const RepositorySpecificDetails = ({
       const owner = ownerAndRepo.owner;
       const repo = ownerAndRepo.repo;
       fetchFileContent(owner, repo, path);
-      setFileName(path);
     }
 
     console.log("File clicked:", path);
@@ -117,7 +114,6 @@ export const RepositorySpecificDetails = ({
       setHistory((prev) => prev.slice(0, -1));
       setCurrentPath(previousPath);
       setFileContent(null);
-      setFileName(null);
     }
   };
 
diff --git 
a/modules/research-framework/portal/src/components/repositories/index.tsx 
b/modules/research-framework/portal/src/components/repositories/index.tsx
index 1100620a9b..665c6ab724 100644
--- a/modules/research-framework/portal/src/components/repositories/index.tsx
+++ b/modules/research-framework/portal/src/components/repositories/index.tsx
@@ -7,12 +7,18 @@ import { RepositoryResource } from 
"@/interfaces/ResourceType";
 import { ResourceCard } from "../home/ResourceCard";
 import { LuSearch } from "react-icons/lu";
 import { InputGroup } from "../ui/input-group";
+import { CONTROLLER } from "@/lib/controller";
+import { ResourceTypeEnum } from "@/interfaces/ResourceTypeEnum";
 
 const getRepositories = async () => {
   try {
-    const response = await api.get(
-      "/project-management/resources?type=REPOSITORY"
-    );
+    const response = await api.get(`${CONTROLLER.resources}/`, {
+      params: {
+        type: ResourceTypeEnum.REPOSITORY,
+        pageNumber: 0,
+        pageSize: 100,
+      },
+    });
     const data = response.data;
     return data;
   } catch (error) {
diff --git 
a/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx
 
b/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx
index 0841b6dd3b..a475cf302d 100644
--- 
a/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx
+++ 
b/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx
@@ -1,4 +1,4 @@
-import { Link, useParams } from "react-router";
+import { useNavigate, useParams } from "react-router";
 import NavBar from "../NavBar";
 import {
   Container,
@@ -12,6 +12,7 @@ import {
   Badge,
   Heading,
   Avatar,
+  Button,
 } from "@chakra-ui/react";
 import { useEffect, useState } from "react";
 import { BiArrowBack } from "react-icons/bi";
@@ -23,22 +24,23 @@ import {
   Resource,
 } from "@/interfaces/ResourceType";
 import { Tag } from "@/interfaces/TagType";
-import { User } from "@/interfaces/UserType";
 import { isValidImaage, resourceTypeToColor } from "@/lib/util";
 import { ResourceTypeBadge } from "./ResourceTypeBadge";
 import { ResourceTypeEnum } from "@/interfaces/ResourceTypeEnum";
 import { ModelSpecificBox } from "../models/ModelSpecificBox";
 import { NotebookSpecificDetails } from "../notebooks/NotebookSpecificDetails";
 import { RepositorySpecificDetails } from 
"../repositories/RepositorySpecificDetails";
+import { CONTROLLER } from "@/lib/controller";
 
 async function getResource(id: string) {
-  const response = await api.get(`/project-management/resources/${id}`);
+  const response = await api.get(`${CONTROLLER.resources}/${id}`);
   return response.data;
 }
 
 const ResourceDetails = () => {
   const { id } = useParams();
   const [resource, setResource] = useState<Resource | null>(null);
+  const navigate = useNavigate();
 
   useEffect(() => {
     if (!id) return;
@@ -61,7 +63,7 @@ const ResourceDetails = () => {
       <NavBar />
       <Container maxW="breakpoint-lg" mx="auto" p={4} mt={16}>
         <Box>
-          <Link to="/">
+          <Button variant="plain" p={0} onClick={() => navigate(-1)}>
             <HStack
               alignItems="center"
               mb={4}
@@ -77,7 +79,7 @@ const ResourceDetails = () => {
               </Icon>
               Back
             </HStack>
-          </Link>
+          </Button>
         </Box>
 
         <HStack
@@ -106,20 +108,16 @@ const ResourceDetails = () => {
             </HStack>
 
             <HStack mt={8}>
-              {resource.authors.map((author: User) => {
+              {resource.authors.map((author: string) => {
                 return (
-                  <HStack key={author.id}>
+                  <HStack key={author}>
                     <Avatar.Root shape="full" size="xl">
-                      <Avatar.Fallback
-                        name={author.firstName + " " + author.lastName}
-                      />
-                      <Avatar.Image src={author.avatar} />
+                      <Avatar.Fallback name={author} />
+                      <Avatar.Image src={author} />
                     </Avatar.Root>
 
                     <Box>
-                      <Text fontWeight="bold">
-                        {author.firstName + " " + author.lastName}
-                      </Text>
+                      <Text fontWeight="bold">{author}</Text>
                     </Box>
                   </HStack>
                 );
diff --git a/modules/research-framework/portal/src/components/ui/toaster.tsx 
b/modules/research-framework/portal/src/components/ui/toaster.tsx
new file mode 100644
index 0000000000..df6c2c38ee
--- /dev/null
+++ b/modules/research-framework/portal/src/components/ui/toaster.tsx
@@ -0,0 +1,43 @@
+"use client"
+
+import {
+  Toaster as ChakraToaster,
+  Portal,
+  Spinner,
+  Stack,
+  Toast,
+  createToaster,
+} from "@chakra-ui/react"
+
+export const toaster = createToaster({
+  placement: "bottom-end",
+  pauseOnPageIdle: true,
+})
+
+export const Toaster = () => {
+  return (
+    <Portal>
+      <ChakraToaster toaster={toaster} insetInline={{ mdDown: "4" }}>
+        {(toast) => (
+          <Toast.Root width={{ md: "sm" }}>
+            {toast.type === "loading" ? (
+              <Spinner size="sm" color="blue.solid" />
+            ) : (
+              <Toast.Indicator />
+            )}
+            <Stack gap="1" flex="1" maxWidth="100%">
+              {toast.title && <Toast.Title>{toast.title}</Toast.Title>}
+              {toast.description && (
+                <Toast.Description>{toast.description}</Toast.Description>
+              )}
+            </Stack>
+            {toast.action && (
+              <Toast.ActionTrigger>{toast.action.label}</Toast.ActionTrigger>
+            )}
+            {toast.meta?.closable && <Toast.CloseTrigger />}
+          </Toast.Root>
+        )}
+      </ChakraToaster>
+    </Portal>
+  )
+}
diff --git a/modules/research-framework/portal/src/interfaces/ResourceType.ts 
b/modules/research-framework/portal/src/interfaces/ResourceType.ts
index 273e435ba4..32d4f697a8 100644
--- a/modules/research-framework/portal/src/interfaces/ResourceType.ts
+++ b/modules/research-framework/portal/src/interfaces/ResourceType.ts
@@ -2,14 +2,14 @@ import { PrivacyEnum } from "./PrivacyEnum";
 import { ResourceTypeEnum } from "./ResourceTypeEnum";
 import { StatusEnum } from "./StatusEnum";
 import { Tag } from "./TagType";
-import { User } from "./UserType";
+// import { User } from "./UserType";
 
 export interface Resource {
   id: string; // UUID, immutable
   name: string;
   description: string;
   headerImage: string;
-  authors: User[];
+  authors: string[];
   tags: Tag[];
   status: StatusEnum;
   privacy: PrivacyEnum;
diff --git a/modules/research-framework/portal/src/lib/api.ts 
b/modules/research-framework/portal/src/lib/api.ts
index 62ee3f6ab0..054439ad85 100644
--- a/modules/research-framework/portal/src/lib/api.ts
+++ b/modules/research-framework/portal/src/lib/api.ts
@@ -1,5 +1,6 @@
 import axios, { AxiosInstance, AxiosResponse } from 'axios';
 import { API_VERSION, BACKEND_URL } from './constants';
+import { User } from 'oidc-client-ts';
 
 const api: AxiosInstance = axios.create({
   baseURL: `${BACKEND_URL}/api/${API_VERSION}/rf`,
@@ -10,9 +11,9 @@ const api: AxiosInstance = axios.create({
 });
 
 // Function to inject token dynamically
-let getUser: (() => Promise<any | null>) | null = null;
+let getUser: (() => Promise<User | null>) | null = null;
 
-export const setUserProvider = (provider: () => Promise<string | null>) => {
+export const setUserProvider = (provider: () => Promise<User | null>) => {
   getUser = provider;
 };
 
@@ -30,9 +31,6 @@ api.interceptors.request.use(
       }
     }
 
-    console.log("configuration", config);
-
-
     return config;
   },
   (error) => Promise.reject(error)
diff --git a/modules/research-framework/portal/src/lib/constants.ts 
b/modules/research-framework/portal/src/lib/constants.ts
index 27d336f3e3..34bbea7b8f 100644
--- a/modules/research-framework/portal/src/lib/constants.ts
+++ b/modules/research-framework/portal/src/lib/constants.ts
@@ -1,15 +1,17 @@
 let BACKEND_URL: string;
-const API_VERSION = "v1";
+let APP_URL: string;
 
 if (process.env.NODE_ENV === 'production') {
-  BACKEND_URL = "";
+  BACKEND_URL = "https://api.cybershuttle.org:18889";;
+  APP_URL = "https://catalog.dev.cybershuttle.org";;
 } else {
   BACKEND_URL = "http://localhost:18889";;
+  APP_URL = 'http://localhost:5173';
 }
 
+export const API_VERSION = "v1";
 export const CLIENT_ID = 'data-catalog-portal';
-export const APP_URL = 'http://localhost:5173';
 export const APP_REDIRECT_URI = `${APP_URL}/oauth_callback`;
 export const OPENID_CONFIG_URL = 
`https://auth.dev.cybershuttle.org/realms/default/.well-known/openid-configuration`;
 
-export { BACKEND_URL, API_VERSION };
\ No newline at end of file
+export { BACKEND_URL, APP_URL };
diff --git a/modules/research-framework/portal/src/lib/controller.ts 
b/modules/research-framework/portal/src/lib/controller.ts
new file mode 100644
index 0000000000..866e82e630
--- /dev/null
+++ b/modules/research-framework/portal/src/lib/controller.ts
@@ -0,0 +1,6 @@
+export const CONTROLLER = {
+  projects: "/projects",
+  hub: "/hub",
+  resources: "/resources",
+  sessions: "/sessions",
+}
\ No newline at end of file
diff --git 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/ResponseTypes/RedirectResponse.java
 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/ResponseTypes/RedirectResponse.java
new file mode 100644
index 0000000000..abc4bfaf9b
--- /dev/null
+++ 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/ResponseTypes/RedirectResponse.java
@@ -0,0 +1,17 @@
+package org.apache.airavata.research.service.ResponseTypes;
+
+public class RedirectResponse {
+    private String redirectUrl;
+
+    public RedirectResponse(String redirectUrl) {
+        this.redirectUrl = redirectUrl;
+    }
+
+    public String getRedirectUrl() {
+        return redirectUrl;
+    }
+
+    public void setRedirectUrl(String redirectUrl) {
+        this.redirectUrl = redirectUrl;
+    }
+}
diff --git 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ProjectController.java
 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ProjectController.java
new file mode 100644
index 0000000000..d3f5c780ec
--- /dev/null
+++ 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ProjectController.java
@@ -0,0 +1,28 @@
+package org.apache.airavata.research.service.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.airavata.research.service.handlers.ProjectHandler;
+import org.apache.airavata.research.service.model.entity.Project;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/v1/rf/projects")
+@Tag(name = "Projects", description = "Projects are comprised of dataset and 
repository resources")
+public class ProjectController {
+
+    @Autowired
+    private ProjectHandler projectHandler;
+
+    @GetMapping("/")
+    @Operation(summary = "Get all projects")
+    public ResponseEntity<List<Project>> getAllProjects() {
+        return ResponseEntity.ok(projectHandler.getAllProjects());
+    }
+}
diff --git 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResearchHubController.java
 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResearchHubController.java
index 9a5f46881c..9a2e6bed28 100644
--- 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResearchHubController.java
+++ 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResearchHubController.java
@@ -20,6 +20,7 @@ package org.apache.airavata.research.service.controller;
 
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.airavata.research.service.ResponseTypes.RedirectResponse;
 import org.apache.airavata.research.service.enums.SessionStatusEnum;
 import org.apache.airavata.research.service.handlers.ResearchHubHandler;
 import org.apache.airavata.research.service.handlers.SessionHandler;
@@ -46,55 +47,33 @@ public class ResearchHubController {
 
     private final ResearchHubHandler rHubHandler;
 
-    @Autowired
-    private SessionHandler sessionHandler;
-
     public ResearchHubController(ResearchHubHandler rHubHandler) {
         this.rHubHandler = rHubHandler;
     }
 
-    @GetMapping("/projects")
-    @Operation(summary = "Get all projects")
-    public ResponseEntity<List<Project>> getAllProjects() {
-        return ResponseEntity.ok(rHubHandler.getAllProjects());
-    }
-
-    @GetMapping("/project/{projectId}")
+    @GetMapping("/start/project/{projectId}")
     @Operation(summary = "Spawn new project session")
-    public ResponseEntity<?> resolveRHubUrl(@PathVariable("projectId") String 
projectId, @RequestParam("sessionName") String sessionName) {
-        String spawnUrl = rHubHandler.spinRHubSession(projectId, sessionName);
-
-        LOGGER.info("Redirecting user to spawn URL: {}", spawnUrl);
-        return 
ResponseEntity.status(HttpStatus.FOUND).location(URI.create(spawnUrl)).build();
-    }
+    public ResponseEntity<RedirectResponse> 
resolveRHubUrl(@PathVariable("projectId") String projectId, 
@RequestParam("sessionName") String sessionName) {
+        LOGGER.info("Starting new RHub session ({}) for project: {}", 
sessionName, projectId);
 
-    @GetMapping("/project/{projectId}/exists")
-    @Operation(summary = "Check if session with project already exists")
-    public ResponseEntity<Boolean> 
getProjectSessionExists(@PathVariable("projectId") String projectId) {
+        String spawnUrl = rHubHandler.spinRHubSession(projectId, sessionName);
 
-        return 
ResponseEntity.ok(sessionHandler.checkIfSessionExists(projectId, 
UserContext.userId()));
-    }
+        LOGGER.info("Session spawned: {}", spawnUrl);
 
-    @GetMapping("/sessions")
-    @Operation(summary = "Get all sessions for a given user")
-    public ResponseEntity<List<Session>> getSessions() {
-        String userId = UserContext.userId();
-        List<Session> sessions = sessionHandler.findAllByUserId(userId);
-        return ResponseEntity.ok(sessions);
-    }
+        RedirectResponse response = new RedirectResponse(spawnUrl);
 
-    @PatchMapping("/sessions/{sessionId}")
-    @Operation(summary = "Update a session's status")
-    public ResponseEntity<Session> 
updateSessionStatus(@PathVariable(value="sessionId") String sessionId, 
@Param(value="status") SessionStatusEnum status) {
-        return ResponseEntity.ok(sessionHandler.updateSessionStatus(sessionId, 
status));
+        return ResponseEntity.ok(response);
     }
 
-    @GetMapping("/sessions/{sessionId}/resolve")
-    public ResponseEntity<?> 
resolveRHubExistingSession(@PathVariable("sessionId") String sessionId) {
+    @GetMapping("/resume/session/{sessionId}")
+    public ResponseEntity<RedirectResponse> 
resolveRHubExistingSession(@PathVariable("sessionId") String sessionId) {
+        LOGGER.info("Resuming session: {}", sessionId);
         String spawnUrl = rHubHandler.resolveRHubExistingSession(sessionId);
+        LOGGER.info("Resume success: {}", spawnUrl);
+
+        RedirectResponse response = new RedirectResponse(spawnUrl);
 
-        LOGGER.info("Redirecting to existing session spawn URL: {}", spawnUrl);
-        return 
ResponseEntity.status(HttpStatus.FOUND).location(URI.create(spawnUrl)).build();
+        return ResponseEntity.ok(response);
     }
 }
 
diff --git 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
index e3fd758782..d61c4e198e 100644
--- 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
+++ 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
@@ -39,12 +39,11 @@ import java.util.ArrayList;
 import java.util.List;
 
 @RestController
-@RequestMapping("/api/v1/rf/project-management")
-@Tag(name = "Project", description = "The Project API")
+@RequestMapping("/api/v1/rf/resources")
+@Tag(name = "Resources", description = "Datasets, notebooks, repositories, 
models")
 public class ResourceController {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(ResourceController.class);
 
-
     @org.springframework.beans.factory.annotation.Autowired
     private ResourceHandler resourceHandler;
 
@@ -75,7 +74,7 @@ public class ResourceController {
     @Operation(
             summary = "Get dataset, notebook, or repository"
     )
-    @GetMapping(value = "/resources/{id}")
+    @GetMapping(value = "/{id}")
     public ResponseEntity<Resource> getResource(@PathVariable(value="id") 
String id) {
         return ResponseEntity.ok(resourceHandler.getResourceById(id));
     }
@@ -83,7 +82,7 @@ public class ResourceController {
     @Operation(
             summary = "Get all resources"
     )
-    @GetMapping("/resources")
+    @GetMapping("/")
     public ResponseEntity<Page<Resource>> getAllResources(
             @RequestParam(value="pageNumber", defaultValue = "0") int 
pageNumber,
             @RequestParam(value="pageSize", defaultValue = "10") int pageSize,
diff --git 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/SessionController.java
 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/SessionController.java
new file mode 100644
index 0000000000..a0261f9276
--- /dev/null
+++ 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/SessionController.java
@@ -0,0 +1,50 @@
+package org.apache.airavata.research.service.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.airavata.research.service.enums.SessionStatusEnum;
+import org.apache.airavata.research.service.handlers.ResearchHubHandler;
+import org.apache.airavata.research.service.handlers.SessionHandler;
+import org.apache.airavata.research.service.model.UserContext;
+import org.apache.airavata.research.service.model.entity.Session;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.repository.query.Param;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.net.URI;
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/v1/rf/sessions")
+@Tag(name = "Sessions", description = "All operations related to sessions 
(created from projects")
+public class SessionController {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(SessionController.class);
+
+    @Autowired
+    private SessionHandler sessionHandler;
+
+    @GetMapping("/")
+    @Operation(summary = "Get all sessions by session status and userId")
+    public ResponseEntity<List<Session>> 
getSessions(@RequestParam(value="status", required = false) SessionStatusEnum 
status) {
+        LOGGER.info("Getting all sessions for user: {}, status filter: {}", 
UserContext.userId(), status);
+        String userId = UserContext.userId();
+        List<Session> sessions;
+        if (status == null) {
+            sessions = sessionHandler.findAllByUserId(userId);
+        } else {
+            sessions = sessionHandler.findAllByUserIdAndStatus(userId, status);
+        }
+        return ResponseEntity.ok(sessions);
+    }
+
+    @PatchMapping("/{sessionId}")
+    @Operation(summary = "Update a session's status")
+    public ResponseEntity<Session> 
updateSessionStatus(@PathVariable(value="sessionId") String sessionId, 
@RequestParam(value="status") SessionStatusEnum status) {
+        LOGGER.info("Updating session status for session: {} to {}", 
sessionId, status);
+        return ResponseEntity.ok(sessionHandler.updateSessionStatus(sessionId, 
status));
+    }
+}
diff --git 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ProjectHandler.java
 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ProjectHandler.java
index eb76fcf939..8b1425015c 100644
--- 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ProjectHandler.java
+++ 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ProjectHandler.java
@@ -25,6 +25,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
+import java.util.List;
+
 @Service
 public class ProjectHandler {
 
@@ -42,4 +44,8 @@ public class ProjectHandler {
             return new EntityNotFoundException("Unable to find a Project with 
id: " + projectId);
         });
     }
+
+    public List<Project> getAllProjects() {
+        return projectRepository.findAll();
+    }
 }
diff --git 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResearchHubHandler.java
 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResearchHubHandler.java
index 3f5f4cf923..d5fbc60220 100644
--- 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResearchHubHandler.java
+++ 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResearchHubHandler.java
@@ -70,8 +70,4 @@ public class ResearchHubHandler {
         LOGGER.debug("Generated the session url: {} for the user: {}", 
sessionUrl, UserContext.userId());
         return sessionUrl;
     }
-
-    public List<Project> getAllProjects() {
-        return projectRepository.findAll();
-    }
 }
diff --git 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/SessionHandler.java
 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/SessionHandler.java
index e3c25c75e2..486c7d69db 100644
--- 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/SessionHandler.java
+++ 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/SessionHandler.java
@@ -60,8 +60,11 @@ public class SessionHandler {
     }
 
     public List<Session> findAllByUserId(String userId) {
-        List<Session> sessions = sessionRepository.findByUserId(userId);
-        return sessions;
+        return sessionRepository.findByUserIdOrderByCreatedAtDesc(userId);
+    }
+
+    public List<Session> findAllByUserIdAndStatus(String userId, 
SessionStatusEnum status) {
+        return 
sessionRepository.findByUserIdAndStatusOrderByCreatedAtDesc(userId, status);
     }
 
     public Session updateSessionStatus(String sessionId, SessionStatusEnum 
status) {
diff --git 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/SessionRepository.java
 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/SessionRepository.java
index 42e9ad95e5..15fe1ea08b 100644
--- 
a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/SessionRepository.java
+++ 
b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/SessionRepository.java
@@ -18,6 +18,7 @@
  */
 package org.apache.airavata.research.service.model.repo;
 
+import org.apache.airavata.research.service.enums.SessionStatusEnum;
 import org.apache.airavata.research.service.model.entity.Session;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.Query;
@@ -31,4 +32,10 @@ public interface SessionRepository extends 
JpaRepository<Session, String> {
 
     @Query(value = "SELECT * FROM session WHERE project_id = :projectId AND 
user_id = :userId", nativeQuery = true)
     Optional<Session> findSessionByProjectIdAndUserId(@Param("projectId") 
String projectId, @Param("userId") String userId);
+
+    List<Session> findByUserIdAndStatus(String userId, SessionStatusEnum 
status);
+
+    List<Session> findByUserIdOrderByCreatedAtDesc(String userId);
+
+    List<Session> findByUserIdAndStatusOrderByCreatedAtDesc(String userId, 
SessionStatusEnum status);
 }
diff --git 
a/modules/research-framework/research-service/src/main/resources/application.yml
 
b/modules/research-framework/research-service/src/main/resources/application.yml
index 2c80d31bfd..42b99ebf12 100644
--- 
a/modules/research-framework/research-service/src/main/resources/application.yml
+++ 
b/modules/research-framework/research-service/src/main/resources/application.yml
@@ -9,9 +9,10 @@ server:
 airavata:
   research-hub:
     url: https://hub.dev.cybershuttle.org
-    dev-user: "[email protected]"
+    dev-user: "[email protected]"
   research-portal:
     url: http://localhost:5173
+
   user-profile:
     server:
       url: api.dev.cybershuttle.org

Reply via email to