Backend:

/* eslint-disable require-jsdoc */
/* eslint-disable max-len */

// Import necessary modules
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const express = require("express");
const cors = require("cors"); // For chatWithOpenAI function

// Access configuration variables
const databaseURL = functions.config().fbrt.database_url;
const openaiApiKey = functions.config().openai.apikey;

// Initialize Firebase Admin SDK with databaseURL
admin.initializeApp({
  credential: admin.credential.applicationDefault(),
  databaseURL: databaseURL,
});

// Create an Express app
const app = express();
app.use(cors({origin: true})); // Enable CORS for all origins
app.use(express.json()); // Parse JSON bodies

// Middleware to verify Firebase Auth token and check for 'psychologist' role
async function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    res.status(401).send("Unauthorized: Missing or invalid token");
    return;
  }

  const idToken = authHeader.split("Bearer ")[1];

  try {
    const decodedToken = await admin.auth().verifyIdToken(idToken);

    // Check if the user has the 'psychologist' role
    if (decodedToken.role !== "psychologist") {
      res.status(403).send("Forbidden: Insufficient permissions");
      return;
    }

    req.user = decodedToken;
    next();
  } catch (error) {
    console.error("Error verifying Firebase ID token:", error);
    res.status(401).send("Unauthorized");
  }
}

// Route to fetch users data
app.get("/users", authenticate, async (req, res) => {
  try {
    // Fetch users data from Realtime Database
    const usersRef = admin.database().ref("users");
    const snapshot = await usersRef.once("value");
    const usersData = snapshot.val();

    res.status(200).json(usersData || {});
  } catch (error) {
    console.error("Error fetching users data:", error);
    res.status(500).send("Internal Server Error");
  }
});

// Route to fetch user activity logs
app.get("/activityLogs", authenticate, async (req, res) => {
  try {
    // Fetch activity logs from Realtime Database
    const logsRef = admin.database().ref("activityLogs");
    const snapshot = await logsRef.once("value");
    const logsData = snapshot.val();

    res.status(200).json(logsData || {});
  } catch (error) {
    console.error("Error fetching activity logs:", error);
    res.status(500).send("Internal Server Error");
  }
});

// Cloud Function for chatWithOpenAI
exports.chatWithOpenAI = functions.https.onRequest(async (req, res) => {
  // Set CORS headers to allow cross-origin requests
  res.set("Access-Control-Allow-Origin", "*");
  res.set("Access-Control-Allow-Headers", "Content-Type");

  // Handle preflight request (for CORS)
  if (req.method === "OPTIONS") {
    res.status(204).send("");
    return;
  }

  // Ensure only POST requests are accepted
  if (req.method !== "POST") {
    res.status(405).send({error: "Only POST requests are accepted"});
    return;
  }

  try {
    const {conversationHistory} = req.body;
    console.log("Received conversationHistory:", conversationHistory);

    // Validate conversationHistory
    if (!Array.isArray(conversationHistory)) {
      res.status(400).send({error: "\"conversationHistory\" must be an array"});
      return;
    }

    // Use OpenAI API key from environment variables
    const apiKey = openaiApiKey;

    if (!apiKey) {
      console.error("OpenAI API key is not configured.");
      res.status(500).send({error: "OpenAI API key is not configured"});
      return;
    }

    // System prompt to guide the assistant
    const systemPrompt = `You are a compassionate and skilled mental health assistant trained in Cognitive Behavioral Therapy (CBT) and mindfulness practices. Your mission is to support users in identifying, restructuring, and challenging negative thoughts through a natural, private conversation, similar to talking with a trusted friend.

**Guidelines:**

1. **Warm and Empathetic Tone:** Maintain a friendly, understanding, and non-judgmental manner. Ensure your responses are brief, clear, and resemble everyday human conversation.

2. **CBT Techniques:**
   - **Identify Negative Thoughts:** Help users recognize and articulate their negative thoughts.
   - **Explore Underlying Beliefs:** Encourage users to delve deeper into the beliefs driving their negative thoughts.
   - **Challenge and Restructure:** Assist users in examining the validity of their thoughts by considering real-life evidence and alternative perspectives.

3. **Mindfulness Integration:**
   - **Present Moment Awareness:** Guide users to focus on the here and now when appropriate.
   - **Breathing Exercises:** Suggest simple mindfulness practices, such as deep breathing, to help users center themselves.

4. **Step-by-Step Support:**
   - **Progressive Solutions:** Introduce coping strategies and solutions gradually to avoid overwhelming the user.
   - **Actionable Steps:** Break down information into manageable, practical actions that users can implement.

5. **Encourage Self-Reflection:**
   - **Open-Ended Questions:** Pose questions that prompt users to reflect on their feelings and thoughts more deeply.
   - **Active Listening:** Acknowledge user feelings and validate their experiences to foster a sense of understanding.

6. **Concise Responses:**
   - **Avoid Lengthy Paragraphs:** Keep replies short and to the point to facilitate a smooth and engaging conversation.
   - **Clear and Direct Language:** Use straightforward language to ensure clarity and ease of understanding.

7. **Safety and Boundaries:**
   - **Recognize Distress Signals:** Be attentive to signs of severe distress or mentions of self-harm.
   - **Encourage Professional Help:** Prompt users to seek professional assistance when necessary, ensuring their safety is prioritized.

**Example Interaction:**

*User:* I've been feeling really overwhelmed and can't stop thinking about my failures.

*Assistant:* I'm sorry you're feeling this way. Can you share more about these thoughts of failure?

*User:* I keep thinking I messed up that important presentation at work.

*Assistant:* That sounds stressful. What evidence do you have that supports or contradicts this thought?

*User:* Well, my manager actually praised my effort afterwards.

*Assistant:* That's a positive sign! How can we view the presentation more balanced, considering both the feedback and your feelings?

*User:* Maybe I focused too much on one part and overlooked others, but overall it was well-received.

*Assistant:* It sounds like you did better than you initially thought. How can you remind yourself of this next time you start feeling overwhelmed by negative thoughts?

*User:* I could write down the positive feedback and review it when I feel down.

*Assistant:* That’s a great idea. Keeping a list of positive feedback can help you stay balanced. Would you like to discuss ways to implement this?

---

\n\n`;

    // Function to summarize conversation if it exceeds a certain number of turns
    const summarizeConversation = async (messages) => {
      const summaryPrompt = `Summarize the following conversation into a concise summary (3-5 sentences) that captures the main points and context:

${messages
      .map(
          (turn) =>
            `${turn.role === "user" ? "User" : "Assistant"}: ${turn.content}`,
      )
      .join("\n")}

Summary:`;

      const summaryPayload = {
        model: "gpt-4",
        messages: [
          {
            role: "system",
            content: "You are an assistant that summarizes conversations concisely.",
          },
          {role: "user", content: summaryPrompt},
        ],
        max_tokens: 150,
        temperature: 0.5,
      };

      const summaryResponse = await fetch("https://api.openai.com/v1/chat/completions", {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${apiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(summaryPayload),
      });

      if (!summaryResponse.ok) {
        const errorData = await summaryResponse.json();
        console.error("Error summarizing conversation:", JSON.stringify(errorData, null, 2));
        throw new Error("Failed to summarize conversation");
      }

      const summaryData = await summaryResponse.json();
      return summaryData.choices[0].message.content.trim();
    };

    // Transform conversationHistory into OpenAI's message format
    let messages = [{role: "system", content: systemPrompt}];

    if (conversationHistory.length > 0) {
      conversationHistory.forEach((turn) => {
        if (turn.sender === "user") {
          messages.push({role: "user", content: turn.text});
        } else if (turn.sender === "bot") {
          messages.push({role: "assistant", content: turn.text});
        }
      });
    }

    // Optionally summarize if conversation is too long
    const MAX_TURNS = 40; // Adjust based on token limits and requirements
    if (messages.length > MAX_TURNS) {
      // Extract the last MAX_TURNS messages
      const recentMessages = messages.slice(-MAX_TURNS);
      const summary = await summarizeConversation(recentMessages);
      // Reset messages with system prompt and summary
      messages = [
        {role: "system", content: systemPrompt},
        {role: "user", content: `Summary of previous conversation: ${summary}`},
      ];
    }

    console.log("Constructed messages array:", messages);

    // Create the payload for OpenAI API
    const payload = {
      model: "gpt-4",
      messages: messages,
      max_tokens: 150, // Limit the length of the response
      temperature: 0.7, // Control creativity
      top_p: 0.9, // Sampling method for diverse responses
      n: 1, // Number of responses to generate
      stop: null, // Let the model decide when to stop
    };

    // OpenAI API endpoint
    const OPENAI_ENDPOINT = "https://api.openai.com/v1/chat/completions";

    // Send the request to OpenAI
    const response = await fetch(OPENAI_ENDPOINT, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
    });

    if (!response.ok) {
      const errorData = await response.json();
      console.error("Error from OpenAI API:", JSON.stringify(errorData, null, 2));
      res.status(response.status).send({error: "Error from OpenAI API", details: errorData});
      return;
    }

    // Parse the response from OpenAI
    const data = await response.json();
    console.log("Data from OpenAI API:", JSON.stringify(data, null, 2));

    // Extract assistant's reply
    const assistantReply = data.choices[0].message.content.trim();

    // Send the assistant's reply back to the client
    res.status(200).send({reply: assistantReply});
  } catch (error) {
    console.error("Error in Cloud Function:", error);
    res.status(500).send({error: "Internal server error"});
  }
});

// Export the Express app as a Firebase Cloud Function
exports.api = functions.https.onRequest(app);

Frontend:

import React, { useState, useEffect, useContext } from 'react';
import { AuthContext } from '../AuthContext';
import { auth } from '../firebase';
import { signOut } from 'firebase/auth';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import {
  Typography,
  Button,
} from '@mui/material';
import { styled } from '@mui/system';
import UserActivityLogs from './UserActivityLogs'; // Import UserActivityLogs component
import UserCard from './UserCard'; // Import UserCard component

// Define your custom styled components
const Root = styled('div')(({ theme }) => ({
  padding: theme.spacing(3),
}));

const Title = styled(Typography)(({ theme }) => ({
  marginBottom: theme.spacing(2),
}));

const LogoutButton = styled(Button)(({ theme }) => ({
  marginBottom: theme.spacing(2),
  float: 'right',
}));

function DashboardPage() {
  const [usersData, setUsersData] = useState([]);
  const [activityLogs, setActivityLogs] = useState([]); // State for activity logs
  const { currentUser } = useContext(AuthContext);
  const navigate = useNavigate();

  useEffect(() => {
    if (!currentUser) {
      navigate('/');
      return;
    }

    

    // Fetch users data and activity logs from backend
    const fetchData = async () => {
      try {
        const token = await currentUser.getIdToken(true);
    
        const [usersResponse, logsResponse] = await Promise.all([
          axios.get('https://us-central1-mind-gardener.cloudfunctions.net/api/users', {
            headers: { Authorization: `Bearer ${token}` },
          }),
          axios.get('https://us-central1-mind-gardener.cloudfunctions.net/api/activityLogs', {
            headers: { Authorization: `Bearer ${token}` },
          }),
        ]);
    
        setUsersData(usersResponse.data);
        setActivityLogs(logsResponse.data);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    };
    

    fetchData();
  }, [currentUser, navigate]);

  const handleLogout = () => {
    signOut(auth)
      .then(() => {
        navigate('/');
      })
      .catch((error) => {
        console.error('Error during sign out:', error);
      });
  };

  return (
    <Root>
      <Title variant="h4">Psychologist Dashboard</Title>
      <LogoutButton variant="outlined" color="secondary" onClick={handleLogout}>
        Logout
      </LogoutButton>

      {/* User Activity Logs Section */}
      <UserActivityLogs logs={activityLogs} />

      {/* Users List */}
      {usersData.map((user) => (
        <UserCard key={user.id} user={user} />
      ))}
    </Root>
  );
}

export default DashboardPage;

Problem:
error 403, i have assigned some users in my realtime database the psychologist role. but i realised that i need to also assign it to the decodedtoken, add some functionality for this.