# Project export: sage

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2024
- Tagline: connecting generations, one meeting at a time
- Devpost: https://devpost.com/software/treehacks-8jdp2m
- GitHub: https://github.com/jasmine-dragons/sage
- Demo: https://sage-treehacks.vercel.app/
- Team: 4 GitHub contributor(s) — Nishant Balaji (34 commits), Ellie Yang (19 commits), ArvinDing (16 commits), Tina (9 commits)

## Devpost submission (written by the team)

### Overview

As individuals coming from families where our elderly family members are living alone, we wanted to build a project to help reduce their social isolation and loneliness. According to the National Academies of Sciences, Engineering, and Medicine (NASEM), “more than one-third of adults aged 45 and older feel lonely, and nearly one-fourth of adults aged 65 and older are considered to be socially isolated.” The New York Times reports that "By the year 2050, the global population of people aged 60 and over will … be 2.1 billion" and that "nearly 27 percent of American seniors live alone." Wanting to address the issue of senior isolation, we created Sage, as a way for older adults to connect with younger folk. Sage allows anyone (but especially older adults) to connect with either random volunteers or close friends/caregivers. With a push of a button, a user can reach out to their/our network and find someone to talk to about anything they want through video call. The pairings for random volunteers would be prioritized based on common interests and user profiles, hopefully providing a starting point for conversation. UI / UX User Story We first started with a user story to better understand our target user. Lo-fi Protoypes We started our process with some concept sketching and lo-fi prototyping. Hi-fi Prototypes We started out with a mobile app concept (above), but we decided to switch to a web app to allow for larger text/accessibility. We created a hi-fi prototype for our web app using Figma: https://www.figma.com/file/bkod0UOX2CC61OJcHg1562/sage?type=design&node-id=0%3A1&mode=design&t=6oTvIf2k9D5zww1V-1 Engineeering For frontend, we used a combination of Next.js, React, Material UI, and CSS. There are a variety of different screens including the login/sign-up page, the request call, and the call page. As this is a Next.js application, we used the serverless function as a primary backend. We used Twilio to implement the video call feature, and additionally ran a Flask server to support the sockets. Twilio's Programmable Video API was used to facilitate calls between users and Websockets were used in conjunction with Flask to match users online at the same time. Screens Here are a few of our screens: Register Sign In Request Call Video Call

### Challenges we ran into

Our first challenge was deciding on a project direction, but once we aligned on a problem space, it was also a challenge to imagine what a viable solution would look like. We also faced challenges in terms of finding appropriate ways to implement features we wanted, as well as combining the different aspects of our project while multiple people worked on different parts at the same time (e.g. frontend screens, video calling, notifications). For example one challenge was getting our videos to render, as well as working on the functionality of notifications.

### Accomplishments we're proud of

We are proud that we were able to successfully integrate live video calling into our project. Additionally, we are proud that we were able to merge the different parts of our project into one flow. Along the way, we are also proud that we were able to debug a variety of issues to make our current demo.

### What we learned

Throughout the course of Treehacks, we learned a tremendous amount -- from improving our planning and ideation to improving our technical skills. For our beginner hackers, we were able to get a lot of knowledge working on a project and building an MVP in a short amount of time. From prototyping with Figma to building the final product, we learned how to prioritize our tasks and effectively work towards our goal. Another major feature we all learned was working with Twilio to make a functional video calling feature. This was a lot of our first times working with this API, and we learned a great amount about how to get a two way video and audio stream working! Another huge thing to note was that for many of us, this was among one of our first in person hackathon experiences! Our more advanced hackers had done hackathons previously, but most of them had been online. An in-person hackathon came with its own slew of challenges, one of which being finding space and working in an environment with so many talented other people! It was a unique experience seeing the awesome things others were building which being so motivated to create our own application.

### What's next

for Sage? We have a few upgrades planned to Sage! The most immediate next steps for Sage would be building out more of our intended features like a friends list, user profiles, and even favorites section. Volunteers might add notes on the preferences of seniors, like topics they like to talk about or things they prefer to avoid. These criteria may benefit matching algorithms to better pair seniors and volunteers. Older people are especially vulnerable. We want to ensure we provide a safe space for anyone on the platform to interact through rigorous screening of users and a reporting/feedback system.

## README (from the GitHub repository)

# Sage - Treehacks 2024

# Background

As individuals coming from families where our elderly family members are living alone, we wanted to build a project to help reduce their social isolation and loneliness. According to the National Academies of Sciences, Engineering, and Medicine (NASEM), “more than one-third of adults aged 45 and older feel lonely, and nearly one-fourth of adults aged 65 and older are considered to be socially isolated.” The New York Times reports that "By the year 2050, the global population of people aged 60 and over will … be 2.1 billion" and that "nearly 27 percent of American seniors live alone." Wanting to address the issue of senior isolation, we created Sage, as a way for older adults to connect with younger folk. 

# What is Sage? 

Sage allows anyone (but especially older adults) to connect with either random volunteers or close friends/caregivers. With a push of a button, a user can reach out to their/our network and find someone to talk to about anything they want through video call. The pairings for random volunteers would be prioritized based on common interests and user profiles, hopefully providing a starting point for conversation. 

# How is Sage built?

## UI / UX

### User Story
![Sage - TREEHACKS 2024](https://github.com/jasmine-dragons/sage/assets/44332326/108653ae-fb3b-4671-a408-46a0fc8f5d84)
We first started with a user story to better understand our target user.

### Lo-fi Protoypes
<img width="400" alt="image" src="https://github.com/jasmine-dragons/sage/assets/40480195/d8c22d5d-2e3a-46a3-b282-11ecdc2c47ef">
<img width="387" alt="image" src="https://github.com/jasmine-dragons/sage/assets/40480195/57ca75b6-3641-4e03-9649-13af446a10f6">

We started our process with some concept sketching and lo-fi prototyping.

### Hi-fi Prototypes

<img width="967" alt="image" src="https://github.com/jasmine-dragons/sage/assets/40480195/b5947e29-87ef-4576-8bbb-b9dfed6f4b9e">

We started out with a mobile app concept (above), but we decided to switch to a web app to allow for larger text/accessibility.

<img width="985" alt="image" src="https://github.com/jasmine-dragons/sage/assets/40480195/7a862992-b431-4b1a-aa1d-ab91baa882a2">

We created a hi-fi prototype for our web app using [Figma](https://www.figma.com/file/bkod0UOX2CC61OJcHg1562/sage?type=design&node-id=0%3A1&mode=design&t=6oTvIf2k9D5zww1V-1)


## Engineeering

For frontend, we used a combination of Next.js, React, Material UI, and CSS. There are a variety of different screens including the login/sign-up page, the request call, and the call page. As this is a Next.js application, we used the serverless function as a primary backend. We used Twilio to implement the video call feature, and additionally ran a Flask server to support the sockets. Twilio's Programmable Video API was used to facilitate calls between users and Websockets were used in conjunction with Flask to match users online at the same time. 

<img width="890" alt="image" src="https://github.com/jasmine-dragons/sage/assets/40480195/30e6013b-e1dc-4f0d-9062-1fe91a7fe94f">


## Screens
Here are a few of our screens:

### Landing
<img width="1408" alt="image" src="https://github.com/jasmine-dragons/sage/assets/40480195/e04dc84d-1628-4372-bd9f-ae28d68c59e8">

### Register
<img width="1298" alt="image" src="https://github.com/jasmine-dragons/sage/assets/40480195/52ec5bec-62bc-435c-b198-ee2d0cbb30dc">

### Sign In
<img width="1297" alt="image" src="https://github.com/jasmine-dragons/sage/assets/40480195/791b5ccb-ede1-4206-85d6-30a9487ce72f">

### Video Call Waiting


### Request Call
<img width="1201" alt="image" src="https://github.com/jasmine-dragons/sage/assets/40480195/d5f2e59a-96be-43fe-9638-0af0176603c1">

### Video Call
<img width="1355" alt="image" src="https://github.com/jasmine-dragons/sage/assets/40480195/9972287d-8aa3-4b24-b3cd-1435a14ac168">


## Challenges we ran into

Our first challenge was deciding on a project direction, but once we aligned on a problem space, it was also a challenge to imagine what a viable solution would look like. We also faced challenges in terms of finding appropriate ways to implement features we wanted, as well as combining the different aspects of our project while multiple people worked on different parts at the same time (e.g. frontend screens, video calling, notifications). For example one challenge was getting our videos to render, as well as working on the functionality of notifications.

# Takeaways

## Accomplishments that we're proud of

We are proud that we were able to successfully integrate live video calling into our project. Additionally, we are proud that we were able to merge the different parts of our project into one flow. Along the way, we are also proud that we were able to debug a variety of issues to make our current demo.

## What we learned

Throughout the course of Treehacks, we learned a tremendous amount -- from improving our planning and ideation to improving our technical skills. For our beginner hackers, we were able to get a lot of knowledge working on a project and building an MVP in a short amount of time. From prototyping with Figma to building the final product, we learned how to prioritize our tasks and effectively work towards our goal. Another major feature we all learned was working with Twilio to make a functional video calling feature. This was a lot of our first times working with this API, and we learned a great amount about how to get a two way video and audio stream working! 

Another huge thing to note was that for many of us, this was among one of our first in person hackathon experiences! Our more advanced hackers had done hackathons previously, but most of them had been online. An in person hackathon came with its own slew of challenges, one of which being finding space and working in an environment with so many talented other people! It was a unique experience seeing the awesome things others were building which being so motivated to create our own application. 

## What's next for Sage?

We have a few upgrades planned to Sage! The most immediate next steps for Sage would be building out more of our intended features like a friends list, user profiles, and even favorites section. We also want to work on securing our platform, providing a safe space for anyone on the platform to interact. 



## Detected evidence (automated analysis)

Indexed codebase: 33 recognized source files, 54 KB.
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code

## Codebase structure (from repository index)

### Files (39 of 39)

```
.gitignore
flask-demo/app.py
flask-demo/requirements.txt
flask-demo/static/app.js
flask-demo/static/style.css
flask-demo/templates/index.html
flask-demo/vercel.json
my-app/.gitignore
my-app/app/call.js
my-app/app/globals.css
my-app/app/layout.js
my-app/app/page.js
my-app/app/page.module.css
my-app/components/Lobby.jsx
my-app/components/Notification.css
my-app/components/Notification.jsx
my-app/components/Participant.jsx
my-app/components/socket.js
my-app/components/VideoChat.jsx
my-app/jsconfig.json
my-app/next.config.js
my-app/package.json
my-app/pages/api/chat/index.js
my-app/pages/api/config.js
my-app/pages/api/token.js
my-app/pages/api/video/index.js
my-app/pages/api/voice/index.js
my-app/pages/call/[room].js
my-app/pages/home/index.css
my-app/pages/home/index.js
my-app/pages/register/index.css
my-app/pages/register/index.js
my-app/pages/Room.css
my-app/pages/Room.js
my-app/pages/sign-in/index.css
my-app/pages/sign-in/index.js
my-app/README.md
my-app/theme.js
README.md
```

### Dependencies

- flask-demo/requirements.txt: Flask@==3.0.2, Flask_SocketIO@==5.3.6, python-dotenv@==1.0.1, twilio@==8.13.0
- my-app/package.json: @emotion/react@^11.11.3, @emotion/styled@^11.11.0, @mui/material@^5.15.10, eslint-config-next@^13.5.6, next@^13.5.6, react@^18.2.0, react-dom@^18.2.0, react-webcam@^7.2.0, socket.io-client@^4.7.4, twilio@^4.22.0, twilio-video@^2.2.0, yarn@^1.22.21

### Recent commits (newest first)

- Update README.md
- Update README.md
- aioergheoi
- aoierdgj
- logout
- im gonna be sad if this is the reason
- asiudha;
- sfbhdijhvc
- transports
- aeoirghaoihg please workkkkkk
- aeriubfhdior
- erodivfh
- faeiwogrh
- server broekn
- adfsa
- Merge branch 'main' of https://github.com/jasmine-dragons/treehacks
- helppp
- Update README.md
- Update README.md
- plswork

## Key source files (fetched from GitHub, selected and truncated for size)

### flask-demo/requirements.txt

```
Flask==3.0.2
Flask_SocketIO==5.3.6
python-dotenv==1.0.1
twilio==8.13.0

```

### my-app/package.json

```
{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@emotion/react": "^11.11.3",
    "@emotion/styled": "^11.11.0",
    "@mui/material": "^5.15.10",
    "eslint-config-next": "^13.5.6",
    "next": "^13.5.6",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-webcam": "^7.2.0",
    "socket.io-client": "^4.7.4",
    "twilio": "^4.22.0",
    "twilio-video": "^2.2.0",
    "yarn": "^1.22.21"
  }
}

```

### flask-demo/app.py

```python
import os
from dotenv import load_dotenv
from flask import Flask, render_template, request, abort, session
from twilio.jwt.access_token import AccessToken
from twilio.jwt.access_token.grants import VideoGrant
from flask_socketio import SocketIO
import string
import random
from flask_cors import CORS

roomN = 10
caller = None
ids = []
load_dotenv()
twilio_account_sid = os.environ.get('TWILIO_ACCOUNT_SID')
twilio_api_key_sid = os.environ.get('TWILIO_API_KEY_SID')
twilio_api_key_secret = os.environ.get('TWILIO_API_KEY_SECRET')
app = Flask(__name__)
CORS(app)
# cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

socketio = SocketIO(app, cors_allowed_origins="*")

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/login', methods=['POST'])
def login():
    username = request.get_json(force=True).get('username')
    if not username:
        abort(401)

    token = AccessToken(twilio_account_sid, twilio_api_key_sid,
                        twilio_api_key_secret, identity=username)
    token.add_grant(VideoGrant(room='My Room'))
    return {'token': token.to_jwt()}

@app.route('/hello', methods=['GET'])
def hello():
    return {'message': 'hello'}


@socketio.on('connected')
def new_connection(json):
    print("new connect" + request.sid)
    if request.sid not in ids:
        ids.append(request.sid)
        
@socketio.on('call')
def request_call(json):
    global caller
    print("call request from "+ request.sid)
    caller = request.sid
    print(ids)
    for id in ids:
        if id != request.sid:
            print("call incoming f{id}", id)
            socketio.emit("call incoming", caller ,to = id)
            
@socketio.on('answer call')
def answer_call(json):
    print("starting call")
    roomID ="".join(random.choices(string.ascii_uppercase + string.digits, k = roomN))
    socketio.emit("start call", roomID, to=caller)
    socketio.emit("start call", roomID, to=request.sid)

if __name__ == '__main__':
    socketio.run(app, port=5000, cors_allowed_origins="*", allowEIO3= True,transports= ["polling"])
```

### my-app/app/page.js

```javascript
import Image from "next/image";
import styles from "./page.module.css";
import Call from "./call";

export default function Home() {
  return (
    <main className={styles.main}>
      <Call />
    </main>
  );
}

```

### my-app/app/layout.js

```javascript
import { Poppins } from "next/font/google";
import "./globals.css";

const poppins = Poppins({ subsets: ["latin"], weight: "100" });

export const metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={poppins.className}>{children}</body>
    </html>
  );
}

```

### flask-demo/static/app.js

```javascript
const usernameInput = document.getElementById('username');
const button = document.getElementById('join_leave');
const container = document.getElementById('container');
const count = document.getElementById('count');
let connected = false;
let room;

const addLocalVideo = async () => {
  const track = await Twilio.Video.createLocalVideoTrack();
  const video = document.getElementById('local').firstElementChild;
  video.appendChild(track.attach());
};

const connectButtonHandler = async (event) => {
  event.preventDefault();
  if (!connected) {
    const username = usernameInput.value;
    if (!username) {
      alert('Enter your name before connecting');
      return;
    }
    button.disabled = true;
    button.innerHTML = 'Connecting...';
    try {
      await connect(username);
      button.innerHTML = 'Leave call';
      button.disabled = false;
    }
    catch {
      alert('Connection failed. Is the backend running?');
      button.innerHTML = 'Join call';
      button.disabled = false;    
    }
  }
  else {
    disconnect();
    button.innerHTML = 'Join call';
    connected = false;
  }
};

const connect = async (username) => {
  const response = await fetch('/login', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({'username': username}),
  });
  const data = await response.json();
  room = await Twilio.Video.connect(data.token);
  room.participants.forEach(participantConnected);
  room.on('participantConnected', participantConnected);
  room.on('participantDisconnected', participantDisconnected);
  connected = true;
  updateParticipantCount();
};

const updateParticipantCount = () => {
  if (!connected) {
    count.innerHTML = 'Disconnected.';
  }
  else {
    count.innerHTML = (room.participants.size + 1) + ' participants online.';
  }
};


const participantConnected = (participant) => {
  const participantDiv = document.createElement('div');
  participantDiv.setAttribute('id', participant.sid);
  participantDiv.setAttribute('class', 'participant');

  const tracksDiv = document.createElement('div');
  participantDiv.appendChild(tracksDiv);

  const labelDiv = document.createElement('div');
  labelDiv.innerHTML = participant.identity;
  participantDiv.appendChild(labelDiv);

  container.appendChild(participantDiv);

  participant.tracks.forEach(publication => {
    if (publication.isSubscribed) {
      trackSubscribed(tracksDiv, publication.track);
    }
  });
  participant.on('trackSubscribed', track => trackSubscribed(tracksDiv, track));
  participant.on('trackUnsubscribed', trackUnsubscribed);
  updateParticipantCount();
};

const participantDisconnected = (participant) => {
  document.getElementById(participant.sid).remove();
  updateParticipantCount();
};

const trackSubscribed = (div, track) => {
  div.appendChild(track.attach());
};

const trackUnsubscribed = (track) => {
  track.detach().forEach(element => element.remove());
};

const disconnect = () => {
  room.disconnect();
  while (container.lastChild.id != 'local') {
      container.removeChild(container.lastChild);
  }
  button.innerHTML = 'Join call';
  connected = false;
  updateParticipantCount();
};

addLocalVideo();
button.addEventListener('click', connectButtonHandler);
```

### my-app/pages/sign-in/index.js

```javascript
import React from "react";
import Button from "@mui/material/Button";
import { Typography } from "@mui/material";
import "./index.css";
import { ThemeProvider } from "@mui/material/styles";
import theme from "../../theme.js";

const SignIn = ({
  username,
  handleUsernameChange,
  roomName,
  handleRoomNameChange,
  handleSubmit,
}) => {
  return (
    <div className="app">
      <main>
        <form className="login" onSubmit={handleSubmit}>
          <ThemeProvider theme={theme}>
            <Typography style={{ textAlign: "center", color: "white" }}>
              Sign In
            </Typography>
            <div>
              <label htmlFor="username">Username</label>
              <input
                type="text"
                id="username"
                value={username}
                onChange={handleUsernameChange}
                required
              />
            </div>

            <div>
              <label htmlFor="password">Password</label>
              <input
                type="text"
                id="password"
                // value={password}
                // onChange={handleRoomNameChange}
                required
              />
            </div>

            <div
              style={{
                width: "100%",
                display: "flex",
                justifyContent: "center",
                marginTop: "2rem",
              }}
            >
              <Button
                variant="contained"
                style={{ width: "12rem" }}
                href="/home"
              >
                Log In
              </Button>
            </div>
          </ThemeProvider>
        </form>
      </main>
    </div>
  );
};

export default SignIn;

```

### my-app/pages/register/index.js

```javascript
import React from "react";
import Button from '@mui/material/Button';
import { Typography } from "@mui/material";
import './index.css'
import { ThemeProvider } from "@mui/material/styles";
import theme from '../../theme.js';

const Register = ({
  username,
  handleUsernameChange,
  roomName,
  handleRoomNameChange,
  handleSubmit,
}) => {
  return (
    <div className="app">
      <main>
        <form className="signin" onSubmit={handleSubmit}>
          <Typography style={{textAlign: "center", color: "white"}}>Register</Typography>
          <div>
            <label htmlFor="username">Username</label>
            <input
              type="text"
              id="username"
              value={username}
              onChange={handleUsernameChange}
              required
            />
          </div>

          <div>
            <label htmlFor="password">Password</label>
            <input
              type="text"
              id="password"
              // value={password}
              // onChange={handleRoomNameChange}
              required
            />
          </div>

          <div>
            <label htmlFor="password">Confirm Password</label>
            <input
              type="text"
              id="password"
              // value={password}
              // onChange={handleRoomNameChange}
              required
            />
          </div>
          <ThemeProvider theme={theme}>
            <div style={{width: "100%", display: "flex", justifyContent: "center", marginTop: "2rem"}}>
              <Button color="secondary" variant="contained" style={{width: "12rem"}} href="/home">
                Create Account
              </Button>
            </div>
          </ThemeProvider>
        </form>
      </main>
    </div>
  );
};

export default Register;

```

### my-app/pages/home/index.js

```javascript
"use client";
import { useState, useEffect } from "react";
import Notification from "@/components/Notification";
import socket from "@/components/socket";
import { useRouter } from "next/router";
import React from "react";
import { Typography } from "@mui/material";
import "./index.css";
import Webcam from "react-webcam";
import { ThemeProvider } from "@mui/material/styles";
import theme from "../../theme.js";

const Home = () => {
  const router = useRouter();
  const [call, setCall] = useState({ callRequest: false, caller: "bob" });
  socket.on("connect", () => {
    socket.emit("connected", {});
  });
  socket.on("disconnect", () => {
    socket.emit("disconnected", {});
  });
  socket.on("call incoming", (callerID) => {
    setCall({ callRequest: true, caller: callerID });
  });
  socket.on("start call", (roomID) => {
    onStartCall(roomID);
  });

  const requestCall = () => {
    console.log("requesting call");
    console.log(socket);
    socket.emit("call", {});
  };

  const answer = () => {
    socket.emit("answer call", {});
  };

  const onStartCall = (roomID) => {
    router.push(`/call/${roomID}`);
  };

  const decline = () => {
    setCall({ callRequest: false });
  };

  let body = null;
  if (!call.callRequest) {
    body = (
      <div className="app">
        <main>
          <ThemeProvider theme={theme}>
            <div className="left">
              <Typography variant="h4" color="white" style={{ flex: 1 }}>
                Here's how you look:
              </Typography>
              <div className="iconImages">
                <img
                  src="https://cdn1.iconfinder.com/data/icons/social-messaging-ui-color-round-2/254000/98-512.png"
                  width="100rem"
                  height="100rem"
                />
                <img
                  src="https://cdn.iconscout.com/icon/free/png-256/free-zoom-4062801-3357687.png?f=webp"
                  width="100rem"
                  height="100rem"
                />
              </div>
              <Webcam
                style={{ flex: 4, height: "700%", borderRadius: "1.5rem" }}
              />
            </div>
            <div className="right">
              <div
                className="callButtons"
                onClick={requestCall}
                style={{ backgroundColor: "#00B84A" }}
              >
                <Typography variant="h4" className="noselect">
                  Request a Call
                </Typography>
              </div>
              <div
                className="callButtons"
                style={{ backgroundColor: "#FFF065" }}
              >
                <Typography variant="h4">Request Favorites Only</Typography>
              </div>
            </div>
          </ThemeProvider>
        </main>
      </div>
    );
  } else {
    body = (
      <Notification name={call.caller} answer={answer} decline={decline} />
    );
  }

  return <div className="app">{body}</div>;
};

export default Home;

```

### my-app/pages/api/chat/index.js

```javascript
import config from "../config";
import { chatToken } from "../token";

export default function handler(req, res) {
  if (req.method === "GET") {
    const identity = req.query.identity;
    const token = chatToken(identity, config.config);
    res.status(200).json(JSON.stringify({ token: token.toJwt() }));
  } else if (req.method == "POST") {
    const identity = req.body.identity;
    const token = chatToken(identity, config.config);
    res.status(200).json(JSON.stringify({ token: token.toJwt() }));
  } else {
    res.status(404).json({ message: "invalid endpoint" });
  }
}

```

[23 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]