diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 87cf2c2..4d3c2b7 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -4,7 +4,7 @@ services: context: .. dockerfile: .devcontainer/Dockerfile hostname: dev-team - container_name: dev-team-sp25 + container_name: classroom-lm volumes: - ..:/app command: sleep infinity diff --git a/.env.example b/.env.example index 935732f..28a5a4a 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,8 @@ NEXT_PUBLIC_SUPABASE_URL= NEXT_PUBLIC_SUPABASE_ANON_KEY= NEXT_PUBLIC_SITE_URL= // http://localhost:8080 -NEXT_PUBLIC_ORGANIZATION_NAME="NYU" -NEXT_PUBLIC_HOSTED_DOMAIN="nyu.edu" +NEXT_PUBLIC_ALLOWED_EMAIL_DOMAINS= +NEXT_PUBLIC_ORGANIZATION_NAME= SUPABASE_SERVICE_ROLE_KEY= RAGFLOW_API_KEY= diff --git a/.github/assets/classrooms.png b/.github/assets/classrooms.png new file mode 100644 index 0000000..47ce182 Binary files /dev/null and b/.github/assets/classrooms.png differ diff --git a/.github/assets/collab-full.png b/.github/assets/collab-full.png new file mode 100644 index 0000000..8a00927 Binary files /dev/null and b/.github/assets/collab-full.png differ diff --git a/.github/assets/example2-full.png b/.github/assets/example2-full.png new file mode 100644 index 0000000..b8993e8 Binary files /dev/null and b/.github/assets/example2-full.png differ diff --git a/.github/assets/logo-text.png b/.github/assets/logo-text.png new file mode 100644 index 0000000..7290908 Binary files /dev/null and b/.github/assets/logo-text.png differ diff --git a/.github/assets/main-banner.png b/.github/assets/main-banner.png new file mode 100644 index 0000000..246f038 Binary files /dev/null and b/.github/assets/main-banner.png differ diff --git a/.github/assets/personal-example.png b/.github/assets/personal-example.png new file mode 100644 index 0000000..2c3d48d Binary files /dev/null and b/.github/assets/personal-example.png differ diff --git a/.github/assets/tech-overview.png b/.github/assets/tech-overview.png new file mode 100644 index 0000000..7815a24 Binary files /dev/null and b/.github/assets/tech-overview.png differ diff --git a/.prettierignore b/.prettierignore index 1cae0b3..6742734 100644 --- a/.prettierignore +++ b/.prettierignore @@ -43,3 +43,9 @@ next-env.d.ts dist pnpm-lock.yaml + + +# ignore k8 files that are templated for keys/configs +k8s/secret.yaml +k8s/config.yaml +k8s/deployment.yaml \ No newline at end of file diff --git a/.tekton/events/trigger.template.yaml b/.tekton/events/trigger.template.yaml index f1d2afe..dc63ce4 100644 --- a/.tekton/events/trigger.template.yaml +++ b/.tekton/events/trigger.template.yaml @@ -22,7 +22,7 @@ spec: name: cd-pipeline params: - name: APP_NAME - value: dev-team-sp25 + value: classroom-lm - name: GIT_REPO value: $(tt.params.git-repo-url) - name: IMAGE_NAME diff --git a/.tekton/pipeline.yaml b/.tekton/pipeline.yaml index 5520848..9cbd519 100644 --- a/.tekton/pipeline.yaml +++ b/.tekton/pipeline.yaml @@ -17,7 +17,7 @@ spec: - name: APP_NAME description: Name of the application type: string - default: dev-team-sp25 + default: classroom-lm - name: IMAGE_NAME description: The name of the image to build type: string diff --git a/README.md b/README.md index 18a5960..56a4806 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,242 @@ # ClassroomLM -An open-source platform that enables students and teachers to interact with classroom materials through Retrieval-Augmented Generation (RAG). Upload documents, chat with course content, and streamline academic Q&A using powerful language models—all in one collaborative interface. +[![ClassroomLM Banner](.github/assets/main-banner.png)](https://github.com/TechAtNYU/ClassroomLM) -## Tech Stack +**ClassroomLM provides each classroom within an educational organization a specialized LLM assistant that is specific and accurate to the subject matter and resources of its particular classroom.** -- **Framework**: Next.js 15 with App Router -- **Language**: TypeScript -- **Styling**: Tailwind CSS with shadcn/ui components -- **Testing**: Vitest with React Testing Library -- **Database**: Supabase -- **Deployment**: Docker and Kubernetes support +--- + +ClassroomLM's main value comes from its core application framework. Retrieval Augmented Generation (RAG) makes AI assistants more accurate, grounded, and specific by basing their answers on a knowledge base. ClassroomLM is unique in its mechanism of having a siloed knowledge base per classroom so that RAG can be conducted separately on specific classroom contexts. The additional features (like the collaborative chat, auto-generating review materials, etc.) are layered on top of this core mechanism. + +## Application Walkthrough + + + +https://github.com/user-attachments/assets/b7ad1a0c-9046-400d-9c53-89554de16738 + +> ClassroomLM can enhance learning, access to information, ease of use of AI systems across all kinds and levels of educational institutions. While geared towards classrooms, once ClassroomLM is in place at an institution it can also help subgroups conducting research, administrators with internal documentation, and even for other adjacent organizations, like clubs and student associations that want to easily give access to an assistant specific to their documents and resources. ## Features -- **Classroom-Style Structure** - Teachers can create classrooms, upload documents (PDFs, slides, handwritten notes), and invite students. +### **Classroom-Style Structure** -- **Classroom-Specific AI Assistants** - Each classroom has its own RAG-based LLM assistant trained on uploaded materials. +Teachers can create classrooms, upload documents (PDFs, slides, handwritten notes), and invite students. -- **Collaborative AI Chats** - Group chat support where the AI can participate with full chat context. +![Classroom page screenshot and diagram of knowledge base per classroom](.github/assets/classrooms.png) +### **Classroom-Specific AI Assistants** -## Prerequisites +**_Each classroom has access to an LLM assistant that is RAG-enabled, allowing it to be more specific and accurate, while also being more grounded and capable of retrieving information from the class' resources, unlocking greater potential for engaging learning, peer interaction, and more._** -- Node.js (LTS version) -- pnpm (recommended package manager) -- Docker (for containerized development) -- Kubernetes (for deployment) +![Personal Assistant Example](.github/assets/personal-example.png) -## Getting Started +#### **Advantages over current user-facing AI assistant systems** -1. Clone the repository: - ```bash - git clone https://github.com/TechAtNYU/dev-team-spring-25.git - cd dev-team-spring-25 - ``` +**More accurate, specific, and grounded**: ClassroomLM's LLM assistant +provides responses with full awareness and knowledge of the classroom's specific or niche context, rather than operating in the default context of LLMs: the entire world/internet's knowledge. + +> **Use case example**: An NYU Professor has a variation of assembly created specifically for the classroom, called E20. Putting the E20 manual into the shared classroom dataset gave all students within this classroom access to **an assistant that is now specialized, knowledgeable, and with full context of this niche, not-seen-before language personally created by a professor.** \ +> Compared to ClassroomLM, other user-facing assistant systems gave vague, nonspecific, and non-accurate answers relevant to other assembly variants. + +--- + +**Logistical and practical benefits**: Created knowledge bases are shared across the entire classroom eliminating a need to individually upload resources. + +Rather than an entire classroom's worth of students having to upload their documents individually, keep it up to date with new resources, and separate it from other classes (and repeat all that across all classrooms in an org), **having a shared knowledge base for an entire classroom (but siloed from other classes) reduces the overhead, friction, and effort of accessing an LLM assistant, on top of allowing for superior use of those resources.** + +> Implementing this core mechanism now provides a foundation for all the features that could benefit from having shared knowledge bases for subgroups. + +--- + +**Powerful and highly flexible abilities**: +**ClassroomLM brings out the full potential of LLM assistants** for educators and students, meaning the **existing,** highly flexible **powers and capabilities of LLMs** that people expect are not only **retained, but enhanced.** + +- The assistant can be used to generate exam questions, review material, interrogate the classroom documents, have a discussion about the content, judge and correct your own understanding, and many other tested abilities, and with even more possible. +- And again, **in comparison to existing user-facing systems, all of these will be more accurate and specific because of the grounding that comes from the classroom's resource dataset.** + +--- + +**Tested in diverse contexts**: +In terms of contexts, ClassroomLM was tested to be useful for subjects ranging from physics, different math topics, computer science, different topics within the humanities, etc. As an example, for something like philosophy, a class with many texts, ClassroomLM shines because it's able to synthesize across the many readings, and without each student having to reupload all documents. + +### **Collaborative Chats with ClassroomLM** + +**_Group chats with other class members and an AI assistant that's a full conversation participant, not just a bot responding to one-off Q&As_** + +- Students can create multiple chatrooms per classroom and choose who to invite within each chatroom. +- Within the chatroom, students can pull the LLM into the conversation in the manner of a group chat with the **`/ask [message]`** command. +- The assistant in this case retains all the benefits described above for the personal chat, as it is also RAG enabled. + +#### Unique to ClassroomLM: Collaborative chat with full conversation context _and_ grounded with RAG on a classroom's resources + +- With ClassroomLM, when triggered with the `/ask` command the LLM will have knowledge of the previous conversation and respond accordingly. + - Will make corrections to messages even if other discussion occurred in the meantime and otherwise **act like a full participant in the conversation, rather than just a bot that you Q&A one-off messages.** +- This is **unlike the common implementations of a "group chat with an AI assistant" idea very often found in company Slacks, etc.** where the LLM is only aware of the message that triggered it and responds just to that. + - The only benefit of those implementations, compared to just personally asking an LLM, is that everyone in the chat witnesses the Q&A. **ClassroomLM is much more powerful than this simplistic approach**. + +#### Collaborative chat example + +![Collaborative Chat Example](.github/assets/collab-full.png) + + + +#### Collaborative chat, advanced interactivity example + + + +- Here, we see the ClassroomLM assistant behaving as an actual conversation participant—in this example, **it successfully understands that it needs to keep giving new questions one-by-one within a group review session and waiting till the end to evaluate**. +- We also see that the **questions are rooted in the knowledge base**, and that the **evaluation correctly and faithfully sticks to the resources** to provide additional relevant context and give feedback. + ![Collaborative chat with interactivity](.github/assets/example2-full.png) + +## Technical Overview + +![Technical overview](.github/assets/tech-overview.png) + +**ClassroomLM builds on top of the [RAGFlow](https://github.com/infiniflow/ragflow) engine.** This is an open-source RAG (Retrieval-Augmented Generation) engine with the benefits of being polished, actively-maintained, and mature. \ +This is especially true in terms of handling bugs and having a comprehensive and [well-documented HTTPS API](https://ragflow.io/docs/v0.19.0/http_api_reference) for the ClassroomLM application to utilize. + +### RAGFlow vs. ClassroomLM's responsibilities + +As seen above in the diagram, the **RAGFlow** instance (note that it's self-hosted) is responsible for storing the documents within knowledge bases and handling RAG functionality during LLM chats. **ClassroomLM is responsible for the layer above this in terms of managing classrooms, collaborative chats, etc**. For example, the ClassroomLM application is what links the siloed datasets within RAGFlow to the corresponding classroom for all LLM assistant functionality. + +## Usage + +For both development and deployment, the **instructions below need to be followed** to ensure you have a RagFlow and Supabase instance running. The only difference is that development could mean you can just have local versions of those two things. + +### 1. Set up [RagFlow](https://github.com/infiniflow/ragflow) + +Follow [the instructions on the Ragflow docs](https://ragflow.io/docs/dev/) to **deploy and configure** it. This includes choosing the LLM to use, with many supported options to choose from.\ +Note the deployment method they detail in the docs are with Docker Compose. Alternatively, they also have a [helm chart](https://github.com/infiniflow/ragflow/tree/main/helm) to deploy RagFlow onto a Kubernetes cluster. + +> Note: since we're deploying our web app onto port 8080 as per our [Dockerfile](https://github.com/TechAtNYU/dev-team-spring-25/blob/main/Dockerfile), depending on whether or not your RagFlow engine is deployed on the same machine/network as the ClassroomLM application, you might need to change the port for RagFlow's web interface. +> Follow the instructions [here to update the HTTP and HTTPS port]() away from 80 and 443 if you would not like RagFlow's web interface to occupy them. + +#### Create a RagFlow API Key + +Follow the [instructions on the RagFlow docs](https://ragflow.io/docs/dev/acquire_ragflow_api_key) to create an API key. + +### 2. Set up Supabase + +[Supabase](https://supabase.com/) can be self-hosted. Most likely, this is the better option since you'll need to host RagFlow somewhere anyway. Follow the [instructions here](https://supabase.com/docs/guides/self-hosting) to see how to self host a Supabase instance using Docker Compose, Kubernetes, etc. + +Otherwise, you can choose to use Supabase's hosted service, which also [has a free tier](https://supabase.com/pricing). + +If you're only developing locally, you can take a look at [this section on the Supabase docs.](https://supabase.com/docs/guides/local-development/cli/getting-started?queryGroups=platform&platform=npm#running-supabase-locally) + +#### Clone the repository + +```bash +git clone https://github.com/TechAtNYU/dev-team-spring-25.git +cd dev-team-spring-25 +``` + +#### Provision Supabase instance + +First, [install the Supabase CLI](https://supabase.com/docs/guides/local-development/cli/getting-started). If you already have the `npm` dependencies installed from the development setup, then you should already have it. + +Next, get the _`CONNECTION_STRING`_. You can either use the dashboard and press **Connect** on the top left. Or see the `Accessing Postgres` [section of the self-hosted Supabase docs.](https://supabase.com/docs/guides/self-hosting/docker#accessing-postgres) + +If you don't already have it, [get the Postgres CLI.](https://www.postgresql.org/download/) +And finally, run the following command to automatically set up the tables, functions, trigger, realtime functionality, etc. Replace `[CONNECTION_STRING]` with what you determined above. + +```bash +psql --single-transaction -variable ON_ERROR_STOP=1 --file supabase-setup.sql --dbname [CONNECTION_STRING] +``` + +#### Set up Supabase Auth with Google OAuth + +Follow the [instructions on the Supabase docs to set up Google OAuth](https://supabase.com/docs/guides/auth/social-login/auth-google?queryGroups=platform&platform=web#configuration) since it's required to add users.\ +Note that for the parts in the instruction where you need the Supabase dashboard, you still have this even if you self-hosted it. (The dashboard is exposed at port 8000 by default). + +#### Add allowed domains to database + +Either by connecting to the database through the `psql` CLI or through the Supabase dashboard (recommended), add any allowable domains to the `Allowed_Domains` table.\ +When a user signs in with Google, it will only work if their Google account's address is found in table. E.g. for `user@domain.com`, `domain.com` must be an entry in `Allowed_Domains`. + +Add a domain (or multiple) in the following manner to `Allowed_Domains`: + +| id | domain | +| --- | ------- | +| 1 | nyu.edu | + +**Note**: In the section below, you'll see that you need to add the allowed domains to the `.env` file as well. + +### 3. Add config info + +Create a `.env` file in the root of the repository based on [`.env.example`](https://github.com/TechAtNYU/dev-team-spring-25/blob/main/.env.example). + +Explanation of each variable: + +| Env Variable | Description | +| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| NEXT_PUBLIC_SUPABASE_URL | Use either the given Supabase URL from the hosted version or a custom URL from your self-hosted solution | +| NEXT_PUBLIC_SUPABASE_ANON_KEY | Available in Supabase's dashboard or CLI | +| NEXT_PUBLIC_SITE_URL | The root URL for the site, to be used for callbacks after authentication | +| NEXT_PUBLIC_ALLOWED_EMAIL_DOMAINS | When users login with Google, these are the only email domains allowed to sign up. **Note that this is also needs to be [configured in the `Allowed_Domains` table within Supabase](#add-allowed-domains-to-database)** | +| NEXT_PUBLIC_ORGANIZATION_NAME | The name of the organization for display purposes | +| SUPABASE_SERVICE_ROLE_KEY | Available in Supabase's dashboard or CLI | +| RAGFLOW_API_KEY | [See above](#create-a-ragflow-api-key) on how to make this key | +| RAGFLOW_API_URL | Publicly available hostname to access RagFlow's API | + +### Deployment + +This section is for deploying ClassroomLM, scroll down below to see the setup for [Development](#development). Currently, only Kubernetes is supported as a deployment option. + +#### Add configuration info to kubernetes files + +Put the same information from `.env` into the `k8s/config.yaml` and `k8s/secret.yaml` (the info is split among those two files.)\ +Note: the same info is duplicated because NextJS requires the environment variables at build time too. + +#### Build Docker image + +Next, we build the image with the following command, with your registry information filled in (if necessary). What's important is that the tag matches the tag used in the deployment file later. + +```bash + docker build -f Dockerfile -t [registry host]:[registry port]/classroom-lm/classroom-lm-app:latest . +``` + +#### Change `k8s/deployment.yaml` and deploy + +Change the **container image** within `k8s/deployment.yaml` to match the image tag in the previous step. + +Then deploy: + +```bash +kubectl apply -f k8s +``` + +### Development + +1. Install dependencies:\ + Assuming NPM is installed, we [recommend installing `pnpm`](https://pnpm.io/installation).\ + Then, run the following in the root directory: -2. Install dependencies: ```bash pnpm install ``` -3. Set up environment variables: - Create a `.env.local` file in the root directory with the necessary environment variables. - ```bash - cp .env.example .env - ``` - and update the appropriate variables -4. Start the development server: +2. Start the development server: + ```bash pnpm dev ``` + The application will be available at [http://localhost:8080](http://localhost:8080) -## Available Scripts +### Repository contents + +- `app`: Under the NextJS app router paradigm, everything within this directory matches the structure of the actual routes for the application. +- `shared/components`: Components used multiple times within the app directory, including those from [ShadCN](https://ui.shadcn.com/). +- `shared/lib`: Shared code for Supabase, an abstraction layer for RagFlow, and a React ContextProvider to give user and classroom information to pages. +- `shared/utils/supabase`: Used for the creation of different types of clients, typing Supabase calls, and handling middleware. Most code here is sourced from [Supabase's Server-Side Auth for NextJS reference](https://supabase.com/docs/guides/auth/server-side/nextjs?queryGroups=router&router=app). + +#### Available Scripts -- `pnpm dev` - Start development server with Turbopack +- `pnpm dev` - Start development server - `pnpm build` - Build the application for production - `pnpm start` - Start the production server - `pnpm test` - Run tests in watch mode @@ -69,28 +248,12 @@ An open-source platform that enables students and teachers to interact with clas - `pnpm format` - Format code with Prettier - `pnpm format:check` - Check code formatting -## Development - -- The project uses the Next.js App Router for routing -- Components are styled using Tailwind CSS with shadcn/ui -- TypeScript ensures type safety throughout the application -- Git hooks (via Husky) ensure code quality before commits -- Prettier and ESLint maintain consistent code style - -## Testing - -The project uses Vitest for testing with React Testing Library. Tests can be run in watch mode or as a single run. Coverage reports can be generated to ensure comprehensive testing. - -## Deployment - -The application can be deployed using Docker and Kubernetes. The project includes: -- Dockerfile for containerization -- Kubernetes manifests in the `k8s` directory -- Tekton pipelines for CI/CD +## Credits -## Contributing +tech@nyu logo\ +ClassroomLM was initially created by the first cohort of [Tech@NYU's](https://techatnyu.org) Dev Team Program in the Spring 2025 semester. -1. Create a new branch for your feature -2. Make your changes -3. Run tests and ensure they pass -4. Submit a pull request +**Tech Lead**: Safi Patel\ +**Program Manager**: Sanjay Chunduru\ +**Developers**: Yixiang Chen, Joseph Cheng, Pranit Singh Gandhi, Xiaomin Liu, Shubham Parab, Emily Silkina, Kavya Srikumar, Austin Tan, Benjamin Tian, Chenxin Yan\ +**Product design**: Jennifer Huang, Haley Ngai diff --git a/k8s/config.yaml b/k8s/config.yaml index 459e5bb..33df23a 100644 --- a/k8s/config.yaml +++ b/k8s/config.yaml @@ -3,7 +3,7 @@ kind: ConfigMap metadata: name: next-config data: - .env: #| + .env: !!!#| # NEXT_PUBLIC_SUPABASE_URL= SUPABASE_URL> # NEXT_PUBLIC_SUPABASE_ANON_KEY=SUPABASE_ANON_KEY> # NEXT_PUBLIC_SITE_URL= SITE_URL> diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 6687131..2c1beef 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -1,9 +1,9 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: dev-team-sp25 + name: classroom-lm labels: - app: dev-team-sp25 + app: classroom-lm spec: replicas: 1 revisionHistoryLimit: 2 @@ -13,16 +13,16 @@ spec: maxUnavailable: 50% selector: matchLabels: - app: dev-team-sp25 + app: classroom-lm template: metadata: labels: - app: dev-team-sp25 + app: classroom-lm spec: restartPolicy: Always containers: - - name: dev-team-sp25 - image: dev.techatnyu.org:5000/dev-team-sp25:1.0 + - name: classroom-lm + image: !!!:/classroom-lm/classroom-lm-app:latest imagePullPolicy: Always ports: - containerPort: 8080 @@ -30,11 +30,6 @@ spec: envFrom: - secretRef: name: next-secret - # - name: DATABASE_URI - # valueFrom: - # secretKeyRef: - # name: postgres-creds - # key: database_uri readinessProbe: httpGet: path: /api/health diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml index c7a78de..8ee1a9b 100644 --- a/k8s/ingress.yaml +++ b/k8s/ingress.yaml @@ -2,7 +2,7 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: dev-team-sp25 + name: classroom-lm annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: @@ -13,7 +13,7 @@ spec: pathType: Prefix backend: service: - name: dev-team-sp25 + name: classroom-lm port: number: 8080 - path: /webhook diff --git a/k8s/secret.yaml b/k8s/secret.yaml index 4149cb6..e6a087d 100644 --- a/k8s/secret.yaml +++ b/k8s/secret.yaml @@ -3,5 +3,5 @@ kind: Secret metadata: name: next-secret stringData: - SUPABASE_SERVICE_ROLE_KEY: SUPABASE ROLE KEY> + SUPABASE_SERVICE_ROLE_KEY: !!! SUPABASE ROLE KEY> immutable: true diff --git a/k8s/service.yaml b/k8s/service.yaml index dde3974..20b71ac 100644 --- a/k8s/service.yaml +++ b/k8s/service.yaml @@ -1,12 +1,12 @@ apiVersion: v1 kind: Service metadata: - name: dev-team-sp25 + name: classroom-lm labels: - app: dev-team-sp25 + app: classroom-lm spec: selector: - app: dev-team-sp25 + app: classroom-lm type: ClusterIP internalTrafficPolicy: Cluster ports: diff --git a/public/cloud.svg b/public/cloud.svg deleted file mode 100644 index dc7d1c1..0000000 --- a/public/cloud.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/public/cloud2-08.svg b/public/cloud2-08.svg deleted file mode 100644 index 61d14b9..0000000 --- a/public/cloud2-08.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/supabase-setup.sql b/supabase-setup.sql new file mode 100644 index 0000000..eadbb93 --- /dev/null +++ b/supabase-setup.sql @@ -0,0 +1,815 @@ + +SET default_transaction_read_only = off; + +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; + +ALTER ROLE "anon" SET "statement_timeout" TO '3s'; + +ALTER ROLE "authenticated" SET "statement_timeout" TO '8s'; + +ALTER ROLE "authenticator" SET "statement_timeout" TO '8s'; + +RESET ALL; + + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + + +CREATE EXTENSION IF NOT EXISTS "pgsodium"; + + + + + + +COMMENT ON SCHEMA "public" IS 'standard public schema'; + + + +CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql"; + + + + + + +CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" WITH SCHEMA "extensions"; + + + + + + +CREATE EXTENSION IF NOT EXISTS "pgcrypto" WITH SCHEMA "extensions"; + + + + + + +CREATE EXTENSION IF NOT EXISTS "pgjwt" WITH SCHEMA "extensions"; + + + + + + +CREATE EXTENSION IF NOT EXISTS "supabase_vault" WITH SCHEMA "vault"; + + + + + + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA "extensions"; + + + + + + +CREATE OR REPLACE FUNCTION "public"."check_user_domain"() RETURNS "trigger" + LANGUAGE "plpgsql" SECURITY DEFINER + AS $$BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM public."Allowed_Domains" ad + WHERE NEW.email LIKE ('%@' || ad.domain) + ) THEN + RAISE EXCEPTION 'INCORRECT_DOMAIN'; + END IF; + + RETURN NEW; +END;$$; + + +ALTER FUNCTION "public"."check_user_domain"() OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."handle_new_user"() RETURNS "trigger" + LANGUAGE "plpgsql" SECURITY DEFINER + SET "search_path" TO '' + AS $$begin + insert into public."Users" (id, email, full_name, avatar_url) + values (new.id, + new.email , + new.raw_user_meta_data->>'full_name', + new.raw_user_meta_data->>'avatar_url'); + return new; +end;$$; + + +ALTER FUNCTION "public"."handle_new_user"() OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."user_in_classroom"("_classroom_id" bigint) RETURNS boolean + LANGUAGE "sql" SECURITY DEFINER + SET "search_path" TO 'public', 'pg_temp' + AS $$ + SELECT EXISTS( + SELECT 1 + FROM public."Classroom_Members" + WHERE "Classroom_Members".classroom_id = _classroom_id + AND "Classroom_Members".user_id = auth.uid() + ); +$$; + + +ALTER FUNCTION "public"."user_in_classroom"("_classroom_id" bigint) OWNER TO "postgres"; + +SET default_tablespace = ''; + +SET default_table_access_method = "heap"; + + +CREATE TABLE IF NOT EXISTS "public"."Allowed_Domains" ( + "id" integer NOT NULL, + "domain" "text" NOT NULL +); + + +ALTER TABLE "public"."Allowed_Domains" OWNER TO "postgres"; + + +ALTER TABLE "public"."Allowed_Domains" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."Allowed_Domains_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + + +CREATE TABLE IF NOT EXISTS "public"."Chatroom_Members" ( + "id" bigint NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "chatroom_id" "uuid" NOT NULL, + "member_id" bigint NOT NULL, + "is_active" boolean DEFAULT true NOT NULL +); + + +ALTER TABLE "public"."Chatroom_Members" OWNER TO "postgres"; + + +ALTER TABLE "public"."Chatroom_Members" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."Chatroom_Members_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + + +CREATE TABLE IF NOT EXISTS "public"."Chatrooms" ( + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "classroom_id" bigint NOT NULL, + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "name" "text" NOT NULL, + "creater_user_id" "uuid" NOT NULL, + "ragflow_session_id" "text" +); + + +ALTER TABLE "public"."Chatrooms" OWNER TO "postgres"; + + +COMMENT ON COLUMN "public"."Chatrooms"."creater_user_id" IS 'Creater of the chatroom'; + + + +CREATE TABLE IF NOT EXISTS "public"."Classroom_Members" ( + "id" bigint NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "classroom_id" bigint NOT NULL, + "user_id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "ragflow_session_id" "text" +); + + +ALTER TABLE "public"."Classroom_Members" OWNER TO "postgres"; + + +ALTER TABLE "public"."Classroom_Members" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."Classroom_Members_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + + +CREATE TABLE IF NOT EXISTS "public"."Classrooms" ( + "id" bigint NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "name" "text", + "metadata" "jsonb", + "admin_user_id" "uuid" DEFAULT "gen_random_uuid"(), + "ragflow_dataset_id" "text", + "chat_assistant_id" "text", + "archived" boolean, + "join_code" "text" GENERATED ALWAYS AS (SUBSTRING("md5"(("id")::"text") FROM 1 FOR 8)) STORED, + "chatroom_assistant_id" "text" +); + + +ALTER TABLE "public"."Classrooms" OWNER TO "postgres"; + + +ALTER TABLE "public"."Classrooms" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME "public"."Classroom_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + + +CREATE TABLE IF NOT EXISTS "public"."Messages" ( + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "content" "text" NOT NULL, + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "is_new" boolean DEFAULT true NOT NULL, + "member_id" bigint, + "chatroom_id" "uuid" NOT NULL, + "is_ask" boolean DEFAULT false NOT NULL +); + + +ALTER TABLE "public"."Messages" OWNER TO "postgres"; + + +COMMENT ON COLUMN "public"."Messages"."is_new" IS 'Whether is message is sent to LLM'; + + + +CREATE TABLE IF NOT EXISTS "public"."Users" ( + "id" "uuid" DEFAULT "auth"."uid"() NOT NULL, + "email" "text" NOT NULL, + "full_name" "text", + "avatar_url" "text" +); + + +ALTER TABLE "public"."Users" OWNER TO "postgres"; + + +ALTER TABLE ONLY "public"."Allowed_Domains" + ADD CONSTRAINT "Allowed_Domains_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."Chatroom_Members" + ADD CONSTRAINT "Chatroom_Members_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."Chatrooms" + ADD CONSTRAINT "Chatrooms_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."Chatrooms" + ADD CONSTRAINT "Chatrooms_uuid_key" UNIQUE ("id"); + + + +ALTER TABLE ONLY "public"."Classroom_Members" + ADD CONSTRAINT "Classroom_Members_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."Classrooms" + ADD CONSTRAINT "Classroom_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."Classrooms" + ADD CONSTRAINT "Classrooms_id_key" UNIQUE ("id"); + + + +ALTER TABLE ONLY "public"."Messages" + ADD CONSTRAINT "Messages_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."Users" + ADD CONSTRAINT "Users_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."Chatroom_Members" + ADD CONSTRAINT "Chatroom_Members_chatroom_id_fkey" FOREIGN KEY ("chatroom_id") REFERENCES "public"."Chatrooms"("id") ON UPDATE CASCADE ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."Chatroom_Members" + ADD CONSTRAINT "Chatroom_Members_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "public"."Classroom_Members"("id") ON UPDATE CASCADE ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."Chatrooms" + ADD CONSTRAINT "Chatrooms_classroom_id_fkey" FOREIGN KEY ("classroom_id") REFERENCES "public"."Classrooms"("id") ON UPDATE CASCADE ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."Chatrooms" + ADD CONSTRAINT "Chatrooms_creater_user_id_fkey" FOREIGN KEY ("creater_user_id") REFERENCES "public"."Users"("id"); + + + +ALTER TABLE ONLY "public"."Classroom_Members" + ADD CONSTRAINT "Classroom_Members_classroom_id_fkey" FOREIGN KEY ("classroom_id") REFERENCES "public"."Classrooms"("id") ON UPDATE CASCADE ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."Classroom_Members" + ADD CONSTRAINT "Classroom_Members_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."Users"("id") ON UPDATE CASCADE ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."Classrooms" + ADD CONSTRAINT "Classroom_admin_user_id_fkey" FOREIGN KEY ("admin_user_id") REFERENCES "public"."Users"("id") ON UPDATE CASCADE ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."Messages" + ADD CONSTRAINT "Messages_chatroom_id_fkey" FOREIGN KEY ("chatroom_id") REFERENCES "public"."Chatrooms"("id") ON UPDATE CASCADE ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."Messages" + ADD CONSTRAINT "Messages_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "public"."Chatroom_Members"("id"); + + + +CREATE POLICY "Allow authenticated users to insert classroom" ON "public"."Classrooms" FOR INSERT WITH CHECK (("auth"."uid"() = "admin_user_id")); + + + +CREATE POLICY "Allow authenticated users to select from Users" ON "public"."Users" FOR SELECT USING (("auth"."role"() = 'authenticated'::"text")); + + + +CREATE POLICY "Allow select if user is a member of the classroom" ON "public"."Classroom_Members" FOR SELECT TO "authenticated" USING ("public"."user_in_classroom"("classroom_id")); + + + +ALTER TABLE "public"."Allowed_Domains" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."Chatroom_Members" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."Chatrooms" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."Classroom_Members" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."Classrooms" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "Enable delete access for all users" ON "public"."Chatroom_Members" FOR DELETE USING (true); + + + +CREATE POLICY "Enable delete access for all users" ON "public"."Messages" FOR DELETE TO "authenticated" USING (true); + + + +CREATE POLICY "Enable delete for users based on user_id" ON "public"."Chatrooms" FOR DELETE USING ((( SELECT "auth"."uid"() AS "uid") = "creater_user_id")); + + + +CREATE POLICY "Enable insert for authenticated users only" ON "public"."Chatroom_Members" FOR INSERT TO "authenticated" WITH CHECK (true); + + + +CREATE POLICY "Enable insert for authenticated users only" ON "public"."Chatrooms" FOR INSERT TO "authenticated" WITH CHECK (true); + + + +CREATE POLICY "Enable insert for authenticated users only" ON "public"."Messages" FOR INSERT TO "authenticated" WITH CHECK (true); + + + +CREATE POLICY "Enable insert for authenticated users only" ON "public"."Users" FOR INSERT TO "authenticated" WITH CHECK (true); + + + +CREATE POLICY "Enable read access for all users" ON "public"."Allowed_Domains" FOR SELECT USING (true); + + + +CREATE POLICY "Enable read access for all users" ON "public"."Chatroom_Members" FOR SELECT USING (true); + + + +CREATE POLICY "Enable read access for all users" ON "public"."Chatrooms" FOR SELECT USING (true); + + + +CREATE POLICY "Enable read access for all users" ON "public"."Messages" FOR SELECT USING (true); + + + +CREATE POLICY "Enable update for all users" ON "public"."Messages" FOR UPDATE USING (true) WITH CHECK (true); + + + +ALTER TABLE "public"."Messages" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."Users" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "classroom_members_view_own" ON "public"."Classroom_Members" TO "authenticated" USING ((( SELECT "auth"."uid"() AS "uid") = "user_id")); + + + +CREATE POLICY "classroom_view_for_members" ON "public"."Classrooms" FOR SELECT USING ((( SELECT "auth"."uid"() AS "uid") IN ( SELECT "cm"."user_id" + FROM "public"."Classroom_Members" "cm" + WHERE ("cm"."classroom_id" = "Classrooms"."id")))); + + + +CREATE POLICY "delete-classroom" ON "public"."Classrooms" FOR DELETE USING ((( SELECT "auth"."uid"() AS "uid") IN ( SELECT "cm"."user_id" + FROM "public"."Classroom_Members" "cm" + WHERE ("cm"."classroom_id" = "Classrooms"."id")))); + + + + + +ALTER PUBLICATION "supabase_realtime" OWNER TO "postgres"; + + + + + + +ALTER PUBLICATION "supabase_realtime" ADD TABLE ONLY "public"."Messages"; + + + +GRANT USAGE ON SCHEMA "public" TO "postgres"; +GRANT USAGE ON SCHEMA "public" TO "anon"; +GRANT USAGE ON SCHEMA "public" TO "authenticated"; +GRANT USAGE ON SCHEMA "public" TO "service_role"; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +GRANT ALL ON FUNCTION "public"."check_user_domain"() TO "anon"; +GRANT ALL ON FUNCTION "public"."check_user_domain"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."check_user_domain"() TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "anon"; +GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."user_in_classroom"("_classroom_id" bigint) TO "anon"; +GRANT ALL ON FUNCTION "public"."user_in_classroom"("_classroom_id" bigint) TO "authenticated"; +GRANT ALL ON FUNCTION "public"."user_in_classroom"("_classroom_id" bigint) TO "service_role"; + + + + + + + + + + + + + + + + + + +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Allowed_Domains" TO "anon"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Allowed_Domains" TO "authenticated"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Allowed_Domains" TO "service_role"; + + + +GRANT ALL ON SEQUENCE "public"."Allowed_Domains_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."Allowed_Domains_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."Allowed_Domains_id_seq" TO "service_role"; + + + +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Chatroom_Members" TO "anon"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Chatroom_Members" TO "authenticated"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Chatroom_Members" TO "service_role"; + + + +GRANT ALL ON SEQUENCE "public"."Chatroom_Members_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."Chatroom_Members_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."Chatroom_Members_id_seq" TO "service_role"; + + + +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Chatrooms" TO "anon"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Chatrooms" TO "authenticated"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Chatrooms" TO "service_role"; + + + +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Classroom_Members" TO "anon"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Classroom_Members" TO "authenticated"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Classroom_Members" TO "service_role"; + + + +GRANT ALL ON SEQUENCE "public"."Classroom_Members_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."Classroom_Members_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."Classroom_Members_id_seq" TO "service_role"; + + + +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Classrooms" TO "anon"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Classrooms" TO "authenticated"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Classrooms" TO "service_role"; + + + +GRANT ALL ON SEQUENCE "public"."Classroom_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."Classroom_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."Classroom_id_seq" TO "service_role"; + + + +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Messages" TO "anon"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Messages" TO "authenticated"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Messages" TO "service_role"; + + + +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Users" TO "anon"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Users" TO "authenticated"; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE "public"."Users" TO "service_role"; + + + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "postgres"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "anon"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "authenticated"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "service_role"; + + + + + + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "postgres"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "anon"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "authenticated"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "service_role"; + + + + + + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLES TO "postgres"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLES TO "anon"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLES TO "authenticated"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLES TO "service_role"; + + + +CREATE OR REPLACE TRIGGER "check_user_domain_trigger" BEFORE INSERT ON "auth"."users" FOR EACH ROW EXECUTE FUNCTION "public"."check_user_domain"(); + +CREATE OR REPLACE TRIGGER "on_auth_user_created" AFTER INSERT ON "auth"."users" FOR EACH ROW EXECUTE FUNCTION "public"."handle_new_user"(); + + + + + + + + + + + + + + + + + + + + + + + + + + +RESET ALL; +