diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..03dade1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,81 @@ +# Dependencies +node_modules +bun.lockb + +# Build outputs +dist +build +.next + +# Environment files +.env +.env.local +.env.*.local + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +bun-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# IDE files +.vscode +.idea +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git +.gitignore + +# Docker +Dockerfile* +docker-compose* +.dockerignore + +# Documentation and README +README.md +*.md +docs + +# Test files +tests +__tests__ +*.test.ts +*.test.js +*.spec.ts +*.spec.js + +# Development tools +.eslintrc* +.prettierrc* +jest.config* +tsconfig*.json + +# Prisma migrations +# prisma/migrations + +# Temporary files +tmp +temp \ No newline at end of file diff --git a/.env.example b/.env.example index ccfaba5..6d8cbae 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,9 @@ DIRECT_URL= # Supabase URL SUPABASE_URL= +# Session-mode pooler URL β used by setup-local.sh for pg_dump (IPv4-accessible). +SESSION_POOLER= + # Supabase service role key SUPABASE_SERVICE_ROLE_KEY= diff --git a/.gitignore b/.gitignore index 573081c..11aecae 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid -*.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7e4436f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,55 @@ +FROM oven/bun:1.3 AS base +WORKDIR /var/www/api + +# ----------------------------- +# deps stage - cache dependencies +# ----------------------------- +FROM base AS deps + +COPY package.json bun.lock* ./ +COPY prisma ./prisma +RUN bun install --frozen-lockfile +RUN bunx prisma generate + +# ----------------------------- +# build stage - compile TypeScript to JavaScript +# ----------------------------- +FROM deps AS build + +COPY . . +RUN bun build src/server.ts --target=bun --production --outdir dist + +# ----------------------------- +# development stage +# ----------------------------- +FROM deps AS development +COPY . . +EXPOSE 3000 +CMD ["bun", "src/server.ts"] + +# ----------------------------- +# production stage +# ----------------------------- + + +FROM oven/bun:1-slim AS production + +WORKDIR /var/www/api +RUN groupadd -g 1001 nodejs && useradd -u 1001 -g nodejs -m bunjs + +COPY --from=build --chown=bunjs:nodejs /var/www/api/dist ./dist +COPY --from=deps --chown=bunjs:nodejs /var/www/api/src/generated ./dist/generated + + +RUN chown -R bunjs:nodejs /var/www/api +USER bunjs + +ENV NODE_ENV=production +ENV PORT=3000 + +EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -qO- http://localhost:3000/health || exit 1 + +CMD ["bun", "./dist/server.js"] \ No newline at end of file diff --git a/LOCAL_DEVELOPMENT.md b/LOCAL_DEVELOPMENT.md new file mode 100644 index 0000000..1a7af3a --- /dev/null +++ b/LOCAL_DEVELOPMENT.md @@ -0,0 +1,54 @@ +# Local Development Environment Setup + +This document explains how to set up and manage the local development environment for the COC-API project. + +## Prerequisites + +- **Docker & Docker Compose**: Ensure you have Docker installed and running. +- **PostgreSQL Client (`pg_dump`)**: Optional. The current local setup prefers loading a local `seed/dump.sql` file. `pg_dump` is only required if you want to create a fresh dump from a remote database manually. + +## Setup Instructions + +### 1. Configure Environment Variables + +Copy the `.env.example` file to `.env` and fill in the required values: + +```bash +cp .env.example .env +``` + +Key variables for the setup script: +- `SESSION_POOLER`: The direct connection string to your Supabase project (Session Pooler / IPv4). + - **Format**: `postgresql://postgres.PROJECT_REF:PASSWORD@aws-0-us-east-1.pooler.supabase.com:5432/postgres` + +### 2. Run the Setup Script + +The `scripts/setup-local.sh` script automates the local environment bring-up. By default it will load the local `seed/dump.sql` into the local Postgres instance. + +```bash +bun run local +``` + +#### What the script does: +1. **Starts Postgres**: Launches the `db` container. +2. **Installs Extensions**: Pre-installs `pgcrypto`, `uuid-ossp`, and `pg_stat_statements` into the `public` schema. +3. **Loads Local Seed**: The script will attempt to load `seed/dump.sql` into the DB only when there are no existing user tables present in the database. If the database already contains tables (non-system tables), the seed step will be skipped to avoid accidentally overwriting existing data. This is the default and recommended local flow. +4. **Starts the API**: Launches the `api` container and waits for it to be healthy. + +Flags: + - `--skip-dump`: reuse existing `seed/dump.sql` (no remote dump step) + - `--skip-seed`: skip loading the seed and just start containers + +### 3. Useful Commands + +- **Start environment**: `bash scripts/setup-local.sh` +- **Skip dump (reuse existing `seed/dump.sql`)**: `bash scripts/setup-local.sh --skip-dump` +- **Skip seeding (just start containers)**: `bash scripts/setup-local.sh --skip-seed` +- **View logs**: `docker compose logs -f` +- **Stop containers**: `docker compose down` +- **Wipe local data (force re-seed)**: `docker compose down -v` + +## Troubleshooting + +### Tables not in `public` schema +The script automatically patches the dump to ensure tables are placed in the `public` schema. If you manually imported a dump, ensure you've installed the required extensions first. diff --git a/README.md b/README.md index 6232c9c..defde45 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # COC-API -This repository contains the common Express.js API for the backends of our Coding Club's websites , backed by PostgreSQL (via Prisma) and Supabase storage. Weβre using **Bun** as our runtime. +This repository contains the shared Express.js API for the backends of our Coding Club's websites, backed by PostgreSQL (via Prisma) and Supabase storage. We use **Bun** as our runtime. ## π Folder Structure @@ -8,70 +8,90 @@ This repository contains the common Express.js API for the backends of our Codin / βββ prisma/ # Prisma schema and migration files β βββ schema.prisma -β βββ .env # your DATABASE_URL, etc. -β βββ migrations/ # autoβgenerated by `bun prisma migrate` +β βββ migrations/ # auto-generated by `bun run migrate` +β +βββ seed/ +β βββ dump.sql # local seed file loaded by the setup script +β +βββ scripts/ +β βββ setup-local.sh # automates local environment bring-up β βββ src/ β βββ config/ # environment/configuration loaders -β β βββ index.ts # loads process.env and exports typed config β β β βββ db/ # database client initialization -β β βββ client.ts # `export const prisma = new PrismaClient()` β β β βββ routes/ # Express route definitions -β β βββ index.ts # main router that mounts subβrouters +β β βββ index.ts # main router β mounts all sub-routers under /api/v1 β β βββ members.ts β β βββ projects.ts β β βββ achievements.ts +β β βββ interviews.ts β β βββ topics.ts β β βββ questions.ts -β β βββ interviews.ts -β β βββ progress.ts +β β βββ progress.ts +β β βββ site-content.ts +β β βββ email.ts β β β βββ controllers/ # controllers: take req β call services β send res β β βββ member.controller.ts β β βββ project.controller.ts β β βββ achievement.controller.ts +β β βββ interview.controller.ts β β βββ topic.controller.ts β β βββ question.controller.ts -β β βββ interview.controller.ts -β β βββ progress.controller.ts +β β βββ progress.controller.ts +β β βββ site-content.controller.ts +β β βββ emailTemplate.controller.ts β β β βββ services/ # business logic / Prisma queries β β βββ member.service.ts β β βββ project.service.ts β β βββ achievement.service.ts +β β βββ interview.service.ts β β βββ topic.service.ts β β βββ question.service.ts -β β βββ interview.service.ts -β β βββ progress.service.ts +β β βββ progress.service.ts +β β βββ site-content.service.ts +β β βββ emailTemplate.service.ts β β -β βββ utils/ # shared helpers (e.g. error wrappers, validators) -β β βββ apiError.ts +β βββ utils/ # shared helpers +β β βββ apiError.ts # custom error class and global error handler +β β βββ imageUtils.ts # image upload / Supabase storage helpers +β β βββ logger.ts # Winston logger instance +β β βββ supabaseClient.ts β β β βββ app.ts # configure Express app, mount routes, error handler -β βββ server.ts # start HTTP server (calls `app.listen`) +β βββ server.ts # start HTTP server β -βββ tests/ # integration and unit tests (Jest or Mocha) -β βββ members.test.ts -β βββ ... +βββ tests/ # unit tests (Jest + ts-jest) +β βββ Member.test.ts +β βββ Project.test.ts +β βββ Achievement.test.ts +β βββ Interview.test.ts +β βββ Topics.test.ts +β βββ Question.test.ts +β βββ Progress.test.ts +β βββ SiteContent.test.ts +β βββ imageUtils.test.ts β βββ .env.example # template for environment variables βββ package.json -βββ tsconfig.json # TypeScript configuration +βββ tsconfig.json ``` ## π Getting Started -### Prerequisite +### Prerequisites - Install [Bun](https://bun.sh/) on your machine. +- Install [Docker & Docker Compose](https://docs.docker.com/get-docker/) for the local database. ### 1. Clone the repo ```bash -git clone https://github.com/your-org/coding-club-api.git -cd coding-club-api +git clone https://github.com/call-0f-code/COC-API.git +cd COC-API ``` ### 2. Install dependencies @@ -82,39 +102,87 @@ bun install ### 3. Configure environment -- Copy `.env.example` to `.env` -- Update `.env` with your Supabase/PostgreSQL connection URL and any other variables: +Copy `.env.example` to `.env` and fill in the required values: + +```bash +cp .env.example .env +``` + +Key variables: + +| Variable | Description | +| ------------------------- | --------------------------------------------------------------------------- | +| `DATABASE_URL` | Supabase connection-pooling URL (used by Prisma at runtime) | +| `DIRECT_URL` | Direct DB connection URL (used by Prisma Migrate) | +| `SUPABASE_URL` | Your Supabase project URL | +| `SUPABASE_SERVICE_ROLE_KEY` | Supabase service-role secret key | +| `SESSION_POOLER` | Session-mode pooler URL β used by `setup-local.sh` for `pg_dump` (IPv4) | +| `NODE_ENV` | `development` \| `production` | + +### 4. Local development (Docker) + +The setup script starts a local Postgres container and seeds it automatically: -### 4. Initialize Prisma & Database +```bash +bun run local +``` + +> See [LOCAL_DEVELOPMENT.md](LOCAL_DEVELOPMENT.md) for full details, flags, and troubleshooting. + +### 5. Initialize / run migrations + +For a brand-new database: ```bash -bun prisma migrate dev --name init -bun prisma generate +bun run migrate:first # runs: bunx prisma migrate dev --name init +bun run generate # runs: bunx prisma generate ``` -### 5. Run in development +For subsequent schema changes: + +```bash +bun run migrate # runs: bunx prisma migrate dev +``` + +### 6. Start the server + +```bash +bun run start +``` + +The server listens on `http://localhost:3000` by default. +All API routes are prefixed with `/api/v1`, e.g. `http://localhost:3000/api/v1/members`. + +### 7. API Documentation + +Generate and serve the API docs: ```bash -bun run dev +bun run apidoc # generates static docs into /doc ``` -- By default, the server listens on `http://localhost:3000` -- `app.ts` sets up your Express instance; `server.ts` starts the HTTP listener +Then visit `http://localhost:3000/docs` while the server is running. -### 6. Run tests +### 8. Run tests ```bash -bun test +bun run test # runs: jest ``` ## π¦ Scripts -| Command | Description | -| ------------------------------- | --------------------------------------- | -| `bun run dev` | Start the dev server with hot reloading | -| `bun prisma migrate dev --name` | Apply migrations in development | -| `bun prisma generate` | Generate Prisma client | -| `bun test` | Run tests (Jest or Mocha) | +| Command | Description | +| -------------------- | -------------------------------------------------- | +| `bun run start` | Start the server (`bun src/server.ts`) | +| `bun run local` | Spin up local Docker environment and seed the DB | +| `bun run migrate` | Run pending Prisma migrations in development | +| `bun run migrate:first` | Apply initial migration (`--name init`) | +| `bun run generate` | Regenerate Prisma client | +| `bun run test` | Run all tests with Jest | +| `bun run apidoc` | Generate API documentation into `/doc` | +| `bun run lint` | Lint `src/` with ESLint | +| `bun run lint:fix` | Lint and auto-fix `src/` | +| `bun run format` | Format `src/` with Prettier | --- diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..46a1305 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,45 @@ +services: + api: + image: coc-api + build: + context: . + dockerfile: Dockerfile + target: development + ports: + - "127.0.0.1:3000:3000" + env_file: + - .env + environment: + NODE_ENV: development + PORT: 3000 + DATABASE_URL: "postgresql://postgres:example@db:5432/coc?sslmode=disable" + volumes: + - ./:/app:cached + - /app/node_modules + depends_on: + - db + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:3000/health"] + interval: 30s + timeout: 3s + retries: 3 + + db: + image: postgres:17 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: example + POSTGRES_DB: coc + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - "127.0.0.1:5432:5432" + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres", "-d", "coc", "-h", "127.0.0.1", "-p", "5432"] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + pgdata: + seed-data: diff --git a/package.json b/package.json index 86ab8eb..3781805 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "precommit": "lint-staged", "migrate:first": "bunx prisma migrate dev --name init", "migrate": "bunx prisma migrate dev", - "generate": "bunx prisma generate" + "generate": "bunx prisma generate", + "local": "bash scripts/setup-local.sh" }, "repository": { "type": "git", diff --git a/prisma/migrations/20260614203131_add_dynamic_site_feature/migration.sql b/prisma/migrations/20260614203131_add_dynamic_site_feature/migration.sql new file mode 100644 index 0000000..6070d35 --- /dev/null +++ b/prisma/migrations/20260614203131_add_dynamic_site_feature/migration.sql @@ -0,0 +1,34 @@ +-- CreateTable +CREATE TABLE "SitePageContent" ( + "id" INTEGER NOT NULL DEFAULT 1, + "heroImageUrl" TEXT, + "heroCaption" TEXT, + "heroAltText" TEXT, + "galleryPhotos" JSONB NOT NULL DEFAULT '[]', + "updatedById" TEXT, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "SitePageContent_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "SiteAction" ( + "id" SERIAL NOT NULL, + "key" TEXT NOT NULL, + "label" TEXT, + "url" TEXT, + "isVisible" BOOLEAN NOT NULL DEFAULT false, + "updatedById" TEXT, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "SiteAction_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "SiteAction_key_key" ON "SiteAction"("key"); + +-- AddForeignKey +ALTER TABLE "SitePageContent" ADD CONSTRAINT "SitePageContent_updatedById_fkey" FOREIGN KEY ("updatedById") REFERENCES "Member"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "SiteAction" ADD CONSTRAINT "SiteAction_updatedById_fkey" FOREIGN KEY ("updatedById") REFERENCES "Member"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20260616135723_email_format_support/migration.sql b/prisma/migrations/20260616135723_email_format_support/migration.sql new file mode 100644 index 0000000..7826244 --- /dev/null +++ b/prisma/migrations/20260616135723_email_format_support/migration.sql @@ -0,0 +1,23 @@ +-- CreateTable +CREATE TABLE "EmailTemplate" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "subject" TEXT NOT NULL, + "htmlBody" TEXT NOT NULL, + "textBody" TEXT, + "createdById" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedById" TEXT, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "EmailTemplate_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "EmailTemplate_name_key" ON "EmailTemplate"("name"); + +-- AddForeignKey +ALTER TABLE "EmailTemplate" ADD CONSTRAINT "EmailTemplate_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "Member"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EmailTemplate" ADD CONSTRAINT "EmailTemplate_updatedById_fkey" FOREIGN KEY ("updatedById") REFERENCES "Member"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20260616181835_new_role_added/migration.sql b/prisma/migrations/20260616181835_new_role_added/migration.sql new file mode 100644 index 0000000..1e2772a --- /dev/null +++ b/prisma/migrations/20260616181835_new_role_added/migration.sql @@ -0,0 +1,24 @@ +/* + Warnings: + + - You are about to drop the column `isManager` on the `Member` table. All the data in the column will be lost. + +*/ +-- 1. Create the Role enum +CREATE TYPE "Role" AS ENUM ('SUPER_ADMIN', 'ADMIN', 'FOUNDER', 'MEMBER'); + +-- 2. Add the new role column (nullable initially, no default yet) +ALTER TABLE "Member" ADD COLUMN "role" "Role"; + +-- 3. Backfill: convert isManager β role +UPDATE "Member" SET "role" = CASE + WHEN "isManager" = true THEN 'ADMIN'::"Role" + ELSE 'MEMBER'::"Role" +END; + +-- 4. Now make it NOT NULL with a default +ALTER TABLE "Member" ALTER COLUMN "role" SET NOT NULL; +ALTER TABLE "Member" ALTER COLUMN "role" SET DEFAULT 'MEMBER'::"Role"; + +-- 5. Drop the old column +ALTER TABLE "Member" DROP COLUMN "isManager"; diff --git a/prisma/migrations/20260616190854_add_is_ghosted/migration.sql b/prisma/migrations/20260616190854_add_is_ghosted/migration.sql new file mode 100644 index 0000000..f919c4a --- /dev/null +++ b/prisma/migrations/20260616190854_add_is_ghosted/migration.sql @@ -0,0 +1,6 @@ +-- AlterTable +ALTER TABLE "Member" ADD COLUMN "ghostedById" TEXT, +ADD COLUMN "isGhosted" BOOLEAN NOT NULL DEFAULT false; + +-- AddForeignKey +ALTER TABLE "Member" ADD CONSTRAINT "Member_ghostedById_fkey" FOREIGN KEY ("ghostedById") REFERENCES "Member"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 538e9a5..0d277c4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -27,7 +27,8 @@ model Member { codeforces String? passoutYear DateTime? isApproved Boolean @default(false) - isManager Boolean @default(false) + isGhosted Boolean @default(false) + role Role @default(MEMBER) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -36,6 +37,11 @@ model Member { approvedById String? approvedMembers Member[] @relation("MemberApprovals") + // Ghost / Dead Zone audit (self-reference) + ghostedBy Member? @relation("MemberGhosts", fields: [ghostedById], references: [id]) + ghostedById String? + ghostedMembers Member[] @relation("MemberGhosts") + // Authentication & Relations accounts Account[] achievements MemberAchievement[] @@ -52,6 +58,10 @@ model Member { updatedAchievements Achievement[] @relation("AchievementUpdatedBy") createdProjects Project[] @relation("ProjectCreatedBy") updatedProjects Project[] @relation("ProjectUpdatedBy") + updatedSitePageContent SitePageContent[] @relation("SitePageContentUpdatedBy") + updatedSiteActions SiteAction[] @relation("SiteActionUpdatedBy") + createdEmailTemplates EmailTemplate[] @relation("EmailTemplateCreatedBy") + updatedEmailTemplates EmailTemplate[] @relation("EmailTemplateUpdatedBy") } model Account { @@ -167,6 +177,13 @@ enum Difficulty { Hard } +enum Role { + SUPER_ADMIN + ADMIN + FOUNDER + MEMBER +} + model InterviewExperience { id Int @id @default(autoincrement()) company String @@ -192,3 +209,43 @@ model CompletedQuestion { @@id([memberId, questionId]) } + +model SitePageContent { + id Int @id @default(1) + heroImageUrl String? + heroCaption String? + heroAltText String? + galleryPhotos Json @default("[]") + + updatedBy Member? @relation("SitePageContentUpdatedBy", fields: [updatedById], references: [id], onDelete: SetNull) + updatedById String? + updatedAt DateTime @updatedAt +} + +model SiteAction { + id Int @id @default(autoincrement()) + key String @unique + label String? + url String? + isVisible Boolean @default(false) + + updatedBy Member? @relation("SiteActionUpdatedBy", fields: [updatedById], references: [id], onDelete: SetNull) + updatedById String? + updatedAt DateTime @updatedAt +} + + +model EmailTemplate { + id Int @id @default(autoincrement()) + name String @unique + subject String + htmlBody String + textBody String? + + createdBy Member? @relation("EmailTemplateCreatedBy", fields: [createdById], references: [id], onDelete: SetNull) + createdById String? + createdAt DateTime @default(now()) + updatedBy Member? @relation("EmailTemplateUpdatedBy", fields: [updatedById], references: [id], onDelete: SetNull) + updatedById String? + updatedAt DateTime @updatedAt +} diff --git a/scripts/setup-local.sh b/scripts/setup-local.sh new file mode 100755 index 0000000..3bf9e5d --- /dev/null +++ b/scripts/setup-local.sh @@ -0,0 +1,166 @@ +!/usr/bin/env bash +# ============================================================================= +# setup-local.sh +# +# Sets up the local development environment: +# 1. Starts the local postgres container +# 2. Waits for postgres to be healthy +# 3. Installs required extensions (pgcrypto, uuid-ossp, pg_stat_statements) +# 4. Checks whether the DB already has data β skips seeding if so +# 5. Dumps the remote database into ./seed/dump.sql using pg_dump +# 6. Strips Supabase-only extensions from the dump +# 7. Loads the dump into the running postgres container +# 8. Starts the API container +# +# --skip-dump Skip the pg_dump step (reuse an existing ./seed/dump.sql) +# --skip-seed Skip seeding entirely (just start containers) +# ============================================================================= + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +ENV_FILE="$PROJECT_ROOT/.env" +SEED_DIR="$PROJECT_ROOT/seed" +DUMP_FILE="$SEED_DIR/dump.sql" +SKIP_DUMP=false +SKIP_SEED=false + +if [[ "$OSTYPE" == "darwin"* ]]; then + SED_INPLACE=(-i '') +else + SED_INPLACE=(-i) +fi + +# ---- Parse args ---- +for arg in "$@"; do + case $arg in + --skip-dump) SKIP_DUMP=true ;; + --skip-seed) SKIP_SEED=true ;; + *) echo "Unknown argument: $arg" && exit 1 ;; + esac +done + +# ---- Colour helpers ---- +GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; RESET='\033[0m' +info() { echo -e "${GREEN}[setup]${RESET} $*"; } +warn() { echo -e "${YELLOW}[setup]${RESET} $*"; } +error() { echo -e "${RED}[setup]${RESET} $*" >&2; exit 1; } + +# ---- Sanity checks ---- +command -v docker >/dev/null 2>&1 || error "docker is not installed or not in PATH" + +# ---- Load .env ---- +if [[ ! -f "$ENV_FILE" ]]; then + error ".env file not found at $ENV_FILE. Copy .env.example and fill in your values." +fi + +info "Loading environment from $ENV_FILE" +set -o allexport +# shellcheck disable: SC1090 +source <(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$ENV_FILE" | sed 's/\r//') +set +o allexport + +# ---- Local postgres URL ---- +LOCAL_DATABASE_URL="postgresql://postgres:example@db:5432/coc?sslmode=disable" + + +# ======================================================== +# 1. Start the DB container +# ======================================================== +info "Starting postgres container..." +cd "$PROJECT_ROOT" +docker compose up db --build -d + + +# ======================================================== +# 2. Wait for postgres to be healthy +# ======================================================== +info "Waiting for postgres to be healthy..." +RETRIES=10 +until docker compose exec -T db pg_isready -U postgres -d coc -h 127.0.0.1 -p 5432 -q 2>/dev/null; do + RETRIES=$((RETRIES - 1)) + if [[ $RETRIES -le 0 ]]; then + error "Postgres did not become healthy in time. Check logs: docker compose logs db" + fi + sleep 2 +done +info "Postgres is healthy." + + +# ======================================================== +# 3. Install required extensions +# ======================================================== +info "Installing required extensions into postgres..." +docker compose exec -T db psql -v ON_ERROR_STOP=1 -U postgres -d coc <<'EXTSQL' +CREATE SCHEMA IF NOT EXISTS extensions; +CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public; +CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public; +CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA public; +EXTSQL +info "Extensions installed." + + +# ======================================================== +# 4. Check if seeding is required +# - If user passed --skip-seed, we skip +# - Otherwise, if the DB already contains user tables (non-system), skip seeding +# ======================================================== +if [[ "$SKIP_SEED" == true ]]; then + warn "--skip-seed passed. Skipping dump and load." +else + mkdir -p "$SEED_DIR" + if [[ ! -f "$DUMP_FILE" ]]; then + error "$DUMP_FILE not found. Create the seed SQL at $DUMP_FILE and re-run this script." + fi + + info "Checking whether database already has user tables..." + TABLE_COUNT=$(docker compose exec -T db psql -U postgres -d coc -t -A -c "SELECT count(*) FROM pg_catalog.pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema');" | tr -d '[:space:]' || true) + + if [[ -z "$TABLE_COUNT" ]]; then + warn "Could not determine table count; proceeding with seed." + info "Loading local seed SQL from $DUMP_FILE ..." + docker compose exec -T db psql -v ON_ERROR_STOP=1 -U postgres -d coc < "$DUMP_FILE" + info "Seed complete." + elif [[ "$TABLE_COUNT" -gt 0 ]]; then + warn "Database already has ${TABLE_COUNT} user tables; skipping seed." + else + info "No existing user tables found. Loading local seed SQL from $DUMP_FILE ..." + docker compose exec -T db psql -v ON_ERROR_STOP=1 -U postgres -d coc < "$DUMP_FILE" + info "Seed complete." + fi +fi + + +# ======================================================== +# 7. Start the API container +# ======================================================== +info "Starting API container..." +DATABASE_URL="$LOCAL_DATABASE_URL" \ + docker compose up api --build -d + +info "Waiting for api to be healthy..." +RETRIES=10 +until curl -sf http://localhost:3000/health >/dev/null 2>&1; do + RETRIES=$((RETRIES - 1)) + if [[ $RETRIES -le 0 ]]; then + warn "API health check timed out β it may still be starting. Check: docker compose logs api" + break + fi + sleep 3 +done + + +echo "" +echo -e "${GREEN}======================================================${RESET}" +echo -e "${GREEN} Local development environment is up!${RESET}" +echo -e "${GREEN}======================================================${RESET}" +echo -e " API: http://localhost:3000" +echo -e " Postgres: localhost:5432 (user=postgres, db=coc)" +echo -e "" +echo -e " Useful commands:" +echo -e " docker compose logs -f # stream all logs" +echo -e " docker compose logs -f api # api logs only" +echo -e " docker compose down # stop containers (keep data)" +echo -e " docker compose down -v # stop and wipe local DB volume" +echo -e "${GREEN}======================================================${RESET}" diff --git a/seed/dump.sql b/seed/dump.sql new file mode 100644 index 0000000..1e21c39 --- /dev/null +++ b/seed/dump.sql @@ -0,0 +1,863 @@ +-- +-- PostgreSQL database dump +-- + + +-- Dumped from database version 17.4 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_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; + +-- Supabase internal commands removed to ensure compatibility with local seeding + +-- Simplified seed SQL for local development +-- Creates core tables and inserts up to 30 members and some relations. + +CREATE SCHEMA IF NOT EXISTS realtime; +CREATE SCHEMA IF NOT EXISTS storage; +CREATE SCHEMA IF NOT EXISTS extensions; + +BEGIN; + +-- Drop existing tables and types (simple ordering) +DROP TABLE IF EXISTS public."_prisma_migrations" CASCADE; +DROP TABLE IF EXISTS public."MemberProject" CASCADE; +DROP TABLE IF EXISTS public."MemberAchievement" CASCADE; +DROP TABLE IF EXISTS public."CompletedQuestion" CASCADE; +DROP TABLE IF EXISTS public."InterviewExperience" CASCADE; +DROP TABLE IF EXISTS public."Question" CASCADE; +DROP TABLE IF EXISTS public."Topic" CASCADE; +DROP TABLE IF EXISTS public."Project" CASCADE; +DROP TABLE IF EXISTS public."Achievement" CASCADE; +DROP TABLE IF EXISTS public."Account" CASCADE; +DROP TABLE IF EXISTS public."Member" CASCADE; +DROP TABLE IF EXISTS realtime.schema_migrations CASCADE; +DROP TABLE IF EXISTS auth.users CASCADE; + +DROP TYPE IF EXISTS public."Difficulty" CASCADE; +DROP TYPE IF EXISTS public."Verdict" CASCADE; + +-- Enum types +CREATE TYPE public."Difficulty" AS ENUM ( + 'Easy', + 'Medium', + 'Hard' +); + +CREATE TYPE public."Verdict" AS ENUM ( + 'Selected', + 'Rejected', + 'Pending' +); + +CREATE TABLE IF NOT EXISTS realtime.schema_migrations ( + version TEXT, + inserted_at TIMESTAMP WITH TIME ZONE +); + + +-- Member table +CREATE TABLE public."Member" ( + id UUID, + name TEXT NOT NULL, + email TEXT UNIQUE NOT NULL, + birth_date DATE, + phone TEXT, + bio TEXT, + "profilePhoto" TEXT, + github TEXT, + linkedin TEXT, + twitter TEXT, + geeksforgeeks TEXT, + leetcode TEXT, + codechef TEXT, + codeforces TEXT, + "passoutYear" DATE, + "isApproved" BOOLEAN NOT NULL DEFAULT false, + "isManager" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT now(), + "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT now(), + "approvedById" UUID +); + +-- Account +CREATE TABLE public."Account" ( + id UUID, + provider TEXT NOT NULL, + "providerAccountId" TEXT NOT NULL, + password TEXT, + "accessToken" TEXT, + "refreshToken" TEXT, + "expiresAt" TIMESTAMP WITH TIME ZONE, + "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT now(), + "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT now(), + "memberId" UUID +); + +-- Achievement +CREATE TABLE public."Achievement" ( + id SERIAL, + title TEXT NOT NULL, + description TEXT NOT NULL, + "achievedAt" DATE NOT NULL, + "imageUrl" TEXT NOT NULL, + "createdById" UUID, + "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT now(), + "updatedById" UUID, + "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT now() +); + +-- MemberAchievement (join) +CREATE TABLE public."MemberAchievement" ( + "memberId" UUID, + "achievementId" INTEGER +); + +-- Project +CREATE TABLE public."Project" ( + id SERIAL, + name TEXT NOT NULL, + "imageUrl" TEXT NOT NULL, + "githubUrl" TEXT NOT NULL, + "deployUrl" TEXT, + "createdById" UUID, + "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT now(), + "updatedById" UUID, + "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT now() +); + +-- MemberProject (join) +CREATE TABLE public."MemberProject" ( + "memberId" UUID, + "projectId" INTEGER +); + +-- Topic +CREATE TABLE public."Topic" ( + id SERIAL, + title TEXT NOT NULL, + description TEXT NOT NULL, + "createdById" UUID, + "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT now(), + "updatedById" UUID, + "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT now() +); + +-- Question +CREATE TABLE public."Question" ( + id SERIAL, + "questionName" TEXT NOT NULL, + difficulty public."Difficulty" NOT NULL, + link TEXT NOT NULL, + "topicId" INTEGER, + "createdById" UUID, + "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT now(), + "updatedById" UUID, + "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT now() +); + +-- InterviewExperience +CREATE TABLE public."InterviewExperience" ( + id SERIAL, + company TEXT NOT NULL, + role TEXT NOT NULL, + verdict public."Verdict" NOT NULL, + content TEXT NOT NULL, + "isAnonymous" BOOLEAN NOT NULL DEFAULT false, + "memberId" UUID +); + +-- CompletedQuestion (join) +CREATE TABLE public."CompletedQuestion" ( + "memberId" UUID, + "questionId" INTEGER +); + +-- _prisma_migrations +CREATE TABLE public."_prisma_migrations" ( + id TEXT, + checksum TEXT NOT NULL, + finished_at TIMESTAMP WITH TIME ZONE, + migration_name TEXT NOT NULL, + logs TEXT, + rolled_back_at TIMESTAMP WITH TIME ZONE, + started_at TIMESTAMP WITH TIME ZONE DEFAULT now(), + applied_steps_count INTEGER DEFAULT 0 +); + +-- (Enum types moved to top of file, before CREATE TABLE statements) + +INSERT INTO public."Account" (id, provider, "providerAccountId", password, "accessToken", "refreshToken", "expiresAt", "createdAt", "updatedAt", "memberId") VALUES +('e882157d-fc39-49e0-9df9-99ca0adb8a7c', 'vedant@gmail.com', '$2b$10$eH5Xpk0e88tWULMgIMJdqeAPE3ND9qych/bfGeh7MM3tFbXX90sF.', NULL, NULL, NULL, NULL, '2025-07-27 12:36:07.398', '2025-07-27 12:36:07.398', '738b73b4-0725-4f7c-95bf-cbdb72eb4e84'), +('0b121686-c462-4fae-8324-b88316013243', 'bhaven@gmail.com', '$2b$10$irh3xCai75iDsLKfp8TJPeEMA2pfFHpIqIqkCAezNjvs25HV13vSy', NULL, NULL, NULL, NULL, '2025-07-27 12:42:51.984', '2025-07-27 12:42:51.984', '86237b63-1339-49d6-ad57-1d4b93bd5092'), +('8b8d3535-1592-4673-ab98-5c41e7899932', 'shaheen@gmail.com', '$2b$10$psuckT2vhmGq3ngd9L5PMOg/ACk/qg7wH.060RqBhs9YcP0j23w4S', NULL, NULL, NULL, NULL, '2025-07-27 12:49:37.408', '2025-07-27 12:49:37.408', '3e6666f5-8ad9-4686-8656-a6904360d4ba'), +('0d95b051-0a15-4811-84ed-e0b67a57dcbb', 'sanskar@gmail.com', '$2b$10$ajN/.3Jdvb78aGo/HKY9suTqrwozl4VFbrpz9BTFRarVnKBkb3QBO', NULL, NULL, NULL, NULL, '2025-07-27 12:53:26.146', '2025-07-27 12:53:26.146', '855fb554-02f7-4b6a-a17e-d55b7976babf'), +('36fc123d-0f51-4373-aa9c-a2db94cbaf6c', 'eshwar@gmail.com', '$2b$10$boqMJ//X86GNTKG4uG4yV.ibMMi1U43iz2MwYiwUgPwYcBCDPNjf6', NULL, NULL, NULL, NULL, '2025-07-27 13:00:38.612', '2025-07-27 13:00:38.612', '03a1a0f8-c1b9-4ac1-99d9-f10f26a82f7c'), +('a895aacf-aabe-4d0d-9d42-5a463061d45c', 'yash@gmail.com', '$2b$10$zonROajObqXYsiDX1UuYou6Pms.qvS4hxiR4Ehm5bTHT6MT.n8xmW', NULL, NULL, NULL, NULL, '2025-07-27 13:03:03.733', '2025-07-27 13:03:03.733', '0cc29a18-180c-408d-9cbe-0fc06109a5c1'), +('0ece4d7c-6a17-4d5f-8e57-2dddb8f1cc12', 'prathamesh@gmail.com', '$2b$10$KaF.GZydyvWUTb.Pk85APuVCh66sZ/uY3/3heTYUAhnQdoHThKVpG', NULL, NULL, NULL, NULL, '2025-07-27 13:04:35.928', '2025-07-27 13:04:35.928', 'bf152df5-3c23-4c1c-85aa-212e0487b420'), +('89c4d268-131f-439d-9ebd-02082af93c2f', 'pratik@gmail.com', '$2b$10$0PDGCKCKSuC5d0aJ4SyoReJjrMuRjUdNPRjSUVxJcYRACcKWu0TT6', NULL, NULL, NULL, NULL, '2025-07-27 13:05:30.243', '2025-07-27 13:05:30.243', 'ac713749-77c1-46b6-ae82-f16d616b1c7c'), +('94023539-b795-4504-85de-f4dd89362884', 'swaraj@gmail.com', '$2b$10$pH/8h6.Ic.jFy0vusM/au.1VCe.Sl37GBtrAR99eNxl7GxPS7Asxa', NULL, NULL, NULL, NULL, '2025-07-27 13:11:28.79', '2025-07-27 13:11:28.79', '1404e81c-d567-4103-941b-0abeea7fc049'), +('b95dc5ec-e7a2-4689-b0c3-2247db3b3c23', 'vansh@gmail.com', '$2b$10$eq6f05tX/ZMwWm2gkxNJnOhfr3m.zYW23eY4TTiIVDvAOE03RZS.6', NULL, NULL, NULL, NULL, '2025-07-27 13:12:10.092', '2025-07-27 13:12:10.092', '22ba7f7a-14e7-45fa-bf5f-51d5f015496f'), +('0a667c9f-e611-4d58-bd2d-1a65860fcc97', 'shivaji@gmail.com', '$2b$10$LHj6GmVwPH0.YHU3wSVLf.PK2cQ23swfr6MoI3hFxp4tInDDLnwu2', NULL, NULL, NULL, NULL, '2025-07-27 13:13:07.284', '2025-07-27 13:13:07.284', '8eeacf82-18e5-48f8-a11e-fdbbe2eb81ce'), +('edd6063c-a60f-4653-8512-f21973ab5879', 'sanica@gmail.com', '$2b$10$QlXDLQeKvVeRyA9r2.MC8umgGR.GF343BUrFtX2KSg8gUo92bFGZW', NULL, NULL, NULL, NULL, '2025-07-27 13:20:15.287', '2025-07-27 13:20:15.287', '644b5e8f-910d-450e-a855-a88f31d02b7b'), +('7e78693b-d788-4e17-aa87-94e631cee02e', 'aditya@gmail.com', '$2b$10$W4aJemPA5H/Ws1rUuC.Kge8/fhLXJE2eTTKh6x.wksQ4Z35waZmkW', NULL, NULL, NULL, NULL, '2025-07-27 13:21:05.854', '2025-07-27 13:21:05.854', '1ff4b36d-5671-4855-8476-d0a8993f9873'), +('a524c15b-3a24-484d-947d-b440aa5fa4f3', 'sarvesh@gmail.com', '$2b$10$6F4VAW1PAOwRYvXbM14Cd.XMvI61neTdp.54qXVze/r.GwF/bWseS', NULL, NULL, NULL, NULL, '2025-07-27 13:29:39.074', '2025-07-27 13:29:39.074', 'ad525dce-67dd-4878-ab95-068943923b81'), +('d754025d-f04a-489f-9cd3-a863e3a2083b', 'Mukul@gmail.com', '$2b$10$gsbswEwuP3LEY6mUg3WHUu3qfeDe7S2KLHvv.ibMTEIlwNqLJPl9u', NULL, NULL, NULL, NULL, '2025-07-27 13:36:08.586', '2025-07-27 13:36:08.586', '1b482f80-f649-45f9-a90b-7538a7a6e66e'), +('1fc11447-d4b2-44ac-9bc8-76f841f11d15', 'anushka@gmail.com', '$2b$10$lAn7dozXDFFKzmD2SovmF.MXRKChjy7EqzV/REdPokjNvbfBoJdHa', NULL, NULL, NULL, NULL, '2025-07-27 13:37:18.144', '2025-07-27 13:37:18.144', 'b6f44922-fff3-48eb-a0c9-15d41e786e38'), +('c577b0c8-ad80-46b9-bb57-03a39f740157', 'samarth@gmail.com', '$2b$10$NbmkATGYbJckgOs.rIkVIetZXCAv0jdyNbo075Kp.ybgDR2MySHIq', NULL, NULL, NULL, NULL, '2025-07-27 13:41:04.035', '2025-07-27 13:41:04.035', '1b5933a9-5d50-4246-861a-ca0d30bd581f'), +('102d22ea-192b-400f-bdce-dac29abeb49b', 'vaishnavi@gmail.com', '$2b$10$wTra4lS7IrfMWPJiMc/V9u8YDZJg5.2mIWmD8ZNyvbt07JPmako/S', NULL, NULL, NULL, NULL, '2025-07-27 13:42:05.112', '2025-07-27 13:42:05.112', 'd46a667d-5b68-4b82-9de7-fcfdf0ab0181'), +('1f24974e-5ffe-4655-94e8-282f3266bb7d', 'vaishnaviadhav@gmail.com', '$2b$10$CvBVhnqFPq3s5f6q2VYCuOVZtuZloaBduLuloZwERvS3CGJOo3nnG', NULL, NULL, NULL, NULL, '2025-07-27 13:43:26.945', '2025-07-27 13:43:26.945', '7dd07cc1-08da-48cc-a162-f546356fe291'), +('78e6f26b-dd53-440e-96c1-f4d2205fab87', 'sakshi@gmail.com', '$2b$10$TdCn5HveTLvSoIdFMR1n/eJGKLofoFXx5lQCsEEQU0GnyZrdp9qkC', NULL, NULL, NULL, NULL, '2025-07-27 13:44:46.419', '2025-07-27 13:44:46.419', '259a1e70-c093-43d4-8aa1-bba058a896b8'), +('c0c097c3-c2dc-4ded-8a82-2e718eb46eff', 'piyushaa@gmail.com', '$2b$10$GkiaYm.5cG73HgDAANN6xedU/zqzvmz1JVsoC1C6/jmCXM67shOSG', NULL, NULL, NULL, NULL, '2025-07-27 13:46:17.354', '2025-07-27 13:46:17.354', '46cfa3aa-1efe-4cc6-a624-340808ef7cb8'), +('bea77f0d-37f6-4303-b549-93846e36d774', 'siddhesh@gmail.com', '$2b$10$TcfZ9HVPsTNARAk31CwbxeAIn7gADgfQ1E2cF/8AgeCh7dRSY/xfi', NULL, NULL, NULL, NULL, '2025-07-27 13:47:45.957', '2025-07-27 13:47:45.957', 'a8443783-dd59-446a-93f5-19f5e590e88b'), +('e43283f2-13d9-4a1b-a505-deb2d4f8b967', 'aarya@gmail.com', '$2b$10$C3yPZHUkWOpgXDNFNYuRf.OSSz.RhpQI03T1IwdgiH/bqyl6rvCw2', NULL, NULL, NULL, NULL, '2025-07-27 13:56:12.936', '2025-07-27 13:56:12.936', 'db2bd9ec-25e5-4134-ae53-fea1734ca161'), +('07c9e6e0-7f65-4494-9764-c7d1c258fd75', 'shashwati@gmail.com', '$2b$10$Xb45AKKbCU8ma95Me6Yc7u5nmmX.OGnkShA4CKgInaw9On6XhFLEy', NULL, NULL, NULL, NULL, '2025-07-27 13:57:15.237', '2025-07-27 13:57:15.237', 'c5b2470d-7fb4-4c93-bfe2-fedc00415dc2'), +('2cf94aa7-3790-4758-b83d-66e762b2505d', 'suhani@gmail.com', '$2b$10$a8QFdX9ws02jnsUz4O1DdehSDJjhRvX96fuUxJIjZHoFlhfzMRkYq', NULL, NULL, NULL, NULL, '2025-07-27 13:58:46.809', '2025-07-27 13:58:46.809', '329d6d7a-9787-452e-9c0c-506481c5462a'), +('f48da39b-98fd-481a-bbd6-68c10be660d0', 'sarveshshiralkar@gmail.com', '$2b$10$7V9FuR7rpABeRvbMuU4bUeoMQKQXrehykMmvzrXarIsLuJKNa1tl.', NULL, NULL, NULL, NULL, '2025-07-27 14:02:16.734', '2025-07-27 14:02:16.734', 'd7c96d3c-d45d-4bde-8c2b-39f0451a389f'), +('2074b920-4329-4e6f-9798-07b372a6679c', 'sahillakare@gmail.com', '$2b$10$XJ.r8SaroRak1GUyVIoXj.oBCYMW1regaZVHBlP1lWLc50WyYSovW', NULL, NULL, NULL, NULL, '2025-07-27 14:07:23.722', '2025-07-27 14:07:23.722', '516af252-e8dc-48a4-80c4-5af1e0758e58'), +('7e4be315-6cd3-410f-8083-fe49f9c2305c', 'sachin@gmail.com', '$2b$10$4EiXQeSIWLAsaY.4ThRyR.Dmpt3Yo5ezPrV9re7wcES8KG7QHTmB2', NULL, NULL, NULL, NULL, '2025-07-27 14:28:15.082', '2025-07-27 14:28:15.082', '6c968bfa-ebf8-4b2b-a349-36bbc9cc2870'), +('3891d5cf-1c51-4245-9a78-81b28ce13266', 'sherin@gmail.com', '$2b$10$NvZJXjKUK2IOMzC3raJvi.9bIYUgEWH69ST9pHbbcrJyMmAJm4HIS', NULL, NULL, NULL, NULL, '2025-07-27 16:57:39.729', '2025-07-27 16:57:39.729', 'd7d54e46-8db2-449c-87f9-8e89e8537c42'), +('101d1b9b-743e-4930-830e-9a33c0429199', 'shruti@gmail.com', '$2b$10$ZuuiwH/L3Aal9jBCDO9qsuZLd6lRZz6rPCMQWIr2bAnM33oMUfsVa', NULL, NULL, NULL, NULL, '2025-07-27 17:18:51.245', '2025-07-27 17:18:51.245', 'c494d747-5123-457f-b9cf-f3359f5a0fe8'), +('f121b746-6942-4013-a11b-178571ed988e', 'shivam@gmail.com', '$2b$10$L5c9yXrvfgLjEQ4y7yLSseLhafRXbYPiOkoWt//rwj1h6f80PdnnC', NULL, NULL, NULL, NULL, '2025-07-27 17:32:16.949', '2025-07-27 17:32:16.949', '75ef229a-3770-46aa-adc8-f4d250c6ac81'), +('ab673cb2-cbb2-4129-a2af-1d911cd981d1', 'veda@gmail.com', '$2b$10$oHbUZdsrcq6tk9lUyKsFeuCLDEzpbdrPzN9/nL8BcUSFB3FbTJvPe', NULL, NULL, NULL, NULL, '2025-07-27 18:06:53.921', '2025-07-27 18:06:53.921', '64464cc4-4dfc-4522-a256-6aca2371df7f'), +('3195836d-c300-4406-bb40-7e6e665ac9a9', 'sheryash@gmail.com', '$2b$10$wdVUVGhmuykY.tdcEYNHU.8jjskz/U0JXw/XHh8OOgir4c2qux/3q', NULL, NULL, NULL, NULL, '2025-07-27 18:28:45.633', '2025-07-27 18:28:45.633', '046d352f-10d9-49b5-bbed-31d67bf4b583'), +('16423861-0e69-4a67-84f0-383dbfca9bd8', 'prajakta@gmail.com', '$2b$10$JXImJtLR3SblUWGxAMF.6.VHetvNMKcoNOMF4BAkAKDsXHK/TU0rC', NULL, NULL, NULL, NULL, '2025-07-27 18:32:27.558', '2025-07-27 18:32:27.558', '0b83d3e3-8685-4cfe-9f63-6cc22c1ceae4'), +('5bf224bd-cd63-41d7-950d-7c66725dd7a6', 'harsh@gmail.com', '$2b$10$ND15qcpzkY3t7lg5gZx4CuPs5XWP3OsT7.9x2/ODf7tGo6AvZuMDm', NULL, NULL, NULL, NULL, '2025-07-27 18:37:17.457', '2025-07-27 18:37:17.457', 'cffc47fb-1147-4dd5-9818-21d209dbe3f3'), +('e2e31f9d-1321-4c7e-98f6-fdb004cd0f27', 'Abhiram@gmail.com', '$2b$10$gOz6M.O48PwsBmvv4AsdLuHkArwIKNSspySKaAmEdkBy5by.s1Clm', NULL, NULL, NULL, NULL, '2025-07-27 18:41:32.941', '2025-07-27 18:41:32.941', 'e68ca856-978b-4bb5-a2f1-6497278624bb'), +('55badea7-0c98-4d18-a2a6-f54d15a12afc', 'aryan@gmail.com', '$2b$10$atDdFN1RzGh0ixzo8AAqjuLoIScI1DFhc1/y620fDg9.iKBL/K0AS', NULL, NULL, NULL, NULL, '2025-07-27 18:47:08.087', '2025-07-27 18:47:08.087', '48724979-f9c8-46de-b9ab-9ca0186596d0'), +('4e428487-f19d-476f-ab27-10e9577e98fd', 'shubham@gmail.com', '$2b$10$4pmoJTrSIDrfYS4t0sqql..MZXu0K5f1FA1hSJ7cf4VGsBkYDWqxi', NULL, NULL, NULL, NULL, '2025-07-27 18:49:15.105', '2025-07-27 18:49:15.105', '69394246-3e41-4eb5-812a-48801b0b5f3e'), +('5aca50b8-f251-4632-834d-3f4e92ef6c9c', 'komal@gmail.com', '$2b$10$hf21ih62PzTbX6ba4VaZAeBLrBLwwdbkXsvDfT5swq0CPd/EsIP.a', NULL, NULL, NULL, NULL, '2025-07-27 19:29:30.114', '2025-07-27 19:29:30.114', 'ef59db8b-2ad5-4e0a-b741-f58521bf61ec'), +('ab59bbd5-d03f-41fe-8267-5697d2d7774a', 'sahil@gmail.com', '$2b$10$lMayDbHFuV3p.xSULly1zOoLhWOlgPaQoVDykm3TG12RcXTSoZfua', NULL, NULL, NULL, NULL, '2025-07-27 19:42:25.794', '2025-07-27 19:42:25.794', 'f032f524-c153-496f-9eea-e2ff8622f3d1'), +('ceb3abbe-ec03-4531-9434-3265e5d1f141', 'dillip@gmail.com', '$2b$10$3o.97fG5vAcS3WJAmy9MbOMyU9yUDXkZMOHtMhQ4vbV5P757F5Z2G', NULL, NULL, NULL, NULL, '2025-07-27 19:47:56.176', '2025-07-27 19:47:56.176', '20ee0910-36b3-48d6-ad96-2112d02fd9b6'), +('229a9b54-94ec-4164-87a8-abe852079016', 'harish@gmail.com', '$2b$10$C2V0fELssTODLmt6AjO53eLTqT51C8ga.JyT4bTgFrajpB.37OvT.', '$2b$10$C2V0fELssTODLmt6AjO53eLTqT51C8ga.JyT4bTgFrajpB.37OvT.', NULL, NULL, NULL, '2025-07-27 20:43:23.073', '2025-07-27 20:43:23.073', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('50317f35-cd52-4a05-8641-52abf9736c2a', 'credentials', 'yourmom@gmail.com', '$2b$08$M63.zOUte/5o2DLUAMxgJOK/VOyVy2CQF61XucPcuTZWd80hZBLG.', NULL, NULL, NULL, '2025-10-27 20:12:59.287', '2025-10-27 20:12:59.287', 'a6bc0b3a-71bf-4e0d-8879-ddedbbc0a766'), +('bc3b8b6c-b292-46b6-96bf-48168d6a7c21', 'credentials', 'hello123@gmail.com', '$2b$08$tY.I//asON4Xxci0ANDuLeEVzysjnPDoBynffnYrsVIfUbzksWXNS', NULL, NULL, NULL, '2025-11-20 09:01:14.333', '2025-11-20 09:01:14.333', '92f0e65d-f306-4cdd-baad-059f645cf148'), +('d696a095-96fc-44af-8daa-f9afb01049ba', 'credentials', 'syswraith@gmail.com', '$2b$08$tFFZNuza5BopfhggwSR7zuedbc9O9egCZ/NGwEXLAEr.iEi/nIMAK', NULL, NULL, NULL, '2025-10-27 14:57:14.684', '2025-10-27 14:57:14.684', '207bb8bd-3e48-40c8-83ce-a825cb9fe474'); + + +-- +-- Data for Name: Achievement; Type: TABLE DATA; Schema: public; Owner: - +-- + +INSERT INTO public."Achievement" (id, title, "achievedAt", "imageUrl", "createdAt", "createdById", description, "updatedAt", "updatedById") VALUES +('14', 'Winner At BMCC''s Troika 2025 Coding Event', '2025-02-04 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/2dd9eb5c-66c6-4c8d-b978-0e671a243c76.jpeg', '2025-07-27 19:39:03.77', 'd7d54e46-8db2-449c-87f9-8e89e8537c42', 'On 4th February 2025, the team secured the winner position at BMCC''s Troika 2025 Coding Event, demonstrating outstanding innovation and technical excellence.', '2025-07-27 19:39:03.77', NULL), +('3', 'Runner-Up At Jigyasa Coding Competition', '2024-02-10 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/67235c1c-6573-40af-994c-f38138d0e175.jpeg', '2025-07-27 17:27:07.721', 'd7d54e46-8db2-449c-87f9-8e89e8537c42', 'On 10th February 2024, Vansh Waldeo secured the runner-up position in a prestigious coding competition held at IMCC, Pune.', '2025-07-27 18:10:15.82', 'd7d54e46-8db2-449c-87f9-8e89e8537c42'), +('4', 'Runner Up BITS PILANI POSTMAN API Hackathon 3.0', '2023-01-08 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/97d19654-7002-4f76-ba6c-e38bfa958d8e.png', '2025-07-27 18:36:06.816', '1b5933a9-5d50-4246-861a-ca0d30bd581f', 'Secured Runner-Up position in the BITS Pilani Postman API Hackathon 3.0, an online event centered around solving real-world problems through effective API design and collaboration. Showcased strong skills in building, testing, and integrating APIs using Postman.', '2025-07-27 18:36:06.816', NULL), +('5', 'Runner Up IXPLORER WEB DESIGN & DEVELOPMENT HACKATHON', '2023-11-27 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/1bcc40eb-3916-466a-baf4-7e4ed69ca6e8.jpeg', '2025-07-27 18:49:57.866', '1b5933a9-5d50-4246-861a-ca0d30bd581f', 'Secured Runner-Up position in the IXplorer Web Design & Development Hackathon, an online hackathon organized by IIT Patna. Built a creative and functional web application, showcasing strong skills in full-stack development and user-centered design.', '2025-07-27 18:49:57.866', NULL), +('6', 'Runner Up At Zeal Institute''s Web Development Project Competition', '2025-04-12 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/57de0ddc-322e-4c1f-a91b-b0c4a1acc8db.jpeg', '2025-07-27 18:51:38.653', 'd7d54e46-8db2-449c-87f9-8e89e8537c42', 'On 12th April 2025, the team secured the runner-up position at Zeal Institute''s Web Development Project Competition, showcasing innovation and strong technical skills.', '2025-07-27 18:51:38.653', NULL), +('9', 'Winner At MKSSS Cummins College of Engineering CodeBid Event', '2025-04-05 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/f39c95ca-ca7e-4138-9ac4-0d9cdfdb0cb5.jpeg', '2025-07-27 19:12:45.321', 'd7d54e46-8db2-449c-87f9-8e89e8537c42', 'On 5th April 2025, the team secured the winner position at MKSSS Cummins College of Engineering''s CodeBid event, demonstrating outstanding innovation and technical excellence.', '2025-07-27 20:02:49.514', 'd7d54e46-8db2-449c-87f9-8e89e8537c42'), +('11', 'Runner Up Smart India Hackathon 2023', '2023-12-26 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/26bce2c2-9603-4b99-8868-abfeb15ed07f.jpeg', '2025-07-27 19:24:19.865', '1b5933a9-5d50-4246-861a-ca0d30bd581f', 'Crowned Winner at Smart India Hackathon 2023 for developing an AI-powered women safety solution. Our model could detect distress situations in real time and send instant alerts, showcasing innovation, social impact, and strong technical execution.', '2025-07-27 19:24:19.865', NULL), +('12', 'Winner At Hunar Intern Web Development Hackathon', '2024-08-26 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/d982a872-e4c9-4b1d-8af9-91cbc0e2f2d1.jpeg', '2025-07-27 19:26:23.925', 'd7d54e46-8db2-449c-87f9-8e89e8537c42', 'On 26th August 2024, the team secured the winner position at Hunar Intern''s Web Development Hackathon, demonstrating outstanding innovation and technical excellence.', '2025-07-27 19:42:06.922', 'd7d54e46-8db2-449c-87f9-8e89e8537c42'), +('15', 'Consecutive COEP MindSpark Finalists 2023 & 2024 ', '2024-09-23 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/03379229-271c-4e7a-b09e-9dbbaedc894c.jpeg', '2025-07-27 19:50:25.906', '1b5933a9-5d50-4246-861a-ca0d30bd581f', 'Achieved finalist positions two years in a row at COEP MindSpark, Puneβs premier techfest, competing among hundreds of participants. Selected in the top 10 for WebScape (2023) and Retracer (2024), showcasing standout skills in web development, problem-solving, and technical creativity.', '2025-07-27 19:50:25.906', NULL), +('7', 'Winner At Zeal Institute''s Web Development Project Competition', '2025-04-12 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/04c4131b-56a7-46d1-a6e0-797e095a33b6.jpeg', '2025-07-27 18:58:46.02', 'd7d54e46-8db2-449c-87f9-8e89e8537c42', 'On 12th April 2025, the team secured the winner position at Zeal Institute''s Web Development Project Competition, demonstrating outstanding innovation and technical excellence.', '2025-07-27 18:58:46.02', NULL), +('10', 'Finalist Meher Baba Drone Competition', '2022-02-13 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/ac141a2e-07f5-42f2-a5a2-6195d22f15a7.jpeg', '2025-07-27 19:16:06.848', '1b5933a9-5d50-4246-861a-ca0d30bd581f', 'Selected as a Finalist in the Meher Baba Drone Competition, showcasing innovative solutions in drone technology and aerial system design. Recognized for technical creativity, problem-solving, and practical implementation in a competitive national setting.', '2025-07-29 11:57:51.485', 'd7d54e46-8db2-449c-87f9-8e89e8537c42'), +('13', 'Runner Up Smart India Hackathon 2024', '2024-12-12 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/d99c3eae-831d-4043-90a8-4f15d9970d65.jpeg', '2025-07-27 19:27:03.366', '1b5933a9-5d50-4246-861a-ca0d30bd581f', 'Secured Runner-Up position at Smart India Hackathon 2024, a prestigious national-level innovation challenge. Built an AI-powered virtual therapy platform for individuals with speech impairments, promoting accessible and inclusive mental health support.', '2025-07-27 19:27:03.366', NULL), +('8', 'Runner Up At Clash of CSS NIT Kurukshetra', '2023-11-30 00:00:00', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/achievements/a6a92603-a4a9-4ea8-9afe-f1473f0b21ab.jpeg', '2025-07-27 19:09:12.806', '1b5933a9-5d50-4246-861a-ca0d30bd581f', 'Runner Up at the Clash of CSS Hackathon organized online by NIT Kurukshetra.\nWorked in a team of three to design a visually appealing, responsive frontend interface under time constraints, demonstrating strong UI/UX skills and effective collaboration.', '2025-07-27 19:09:12.806', NULL); + + +-- +-- Data for Name: CompletedQuestion; Type: TABLE DATA; Schema: public; Owner: - +-- + +-- Empty COPY for public."CompletedQuestion" ("memberId", "questionId") removed + + +-- +-- Data for Name: InterviewExperience; Type: TABLE DATA; Schema: public; Owner: - +-- + +INSERT INTO public."InterviewExperience" (id, company, role, verdict, content, "isAnonymous", "memberId") VALUES +('33', 'Grinder', 'Sword Department', 'Pending', 'I got selected because of my skills with my sword ( i am revealing my identity)', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('6', 'Google', 'Software Engineering', 'Rejected', '"
I SUCK
I ROCK i SHINE
I AM THE BEST
FABULOUS IS MY MIDDLE NAME
IF YOU BECAME PREGNANT ON THE DAY OF YOUR TCS INTERIVEW THEN BY THE TIME YOU GET YOUR INTERVIEW RESULT YOUR FETUS WOULD HAVE DEVLEOPED THE ABILITY TO OPEN ITS EYES, LITERAL EYES DEVELOP IN LESS TIME THAN THESE PEOPLE NEED TO TELL YOU WHETHER YOU HAVE A JOB OR NOT
', 'true', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('10', 'Meta', 'Floor sweeper', 'Rejected', '# how tf did I mess this up man\n\n*It was literally a floor sweeping job*\n\n### I even showed my floor coding skills by creating a program that does `print("I am floor sweeper!!!")`\n\n> Honestly I am quite impressed that someone can fail an interveiw for this job\n\n -my interviewer\n\nThe job requirements:\n\n\n1. Know how to walk\n2. Know how to hold a mop\n3. Know how to use it on the floor \n\nWhen I asked them why I failed they gave the following evaluation:\n\n\n- The candidate came with a hatsune miku themed mop that had "my waifu helps me clean" on it \n- He was constantly staring at the breasts of female as well as male interviewers (while licking lips)\n- When asked for an explaination for the "I love pedophilia" tshirt that the candidate was sporting, he went on a 30 minute tirade on the topic of **"Old enough to swear, then my children she can bear"**\n\n**idk man just sounds very unreasonable if you ask me :(**\n\n', 'true', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('12', 'call of code', 'President', 'Rejected', 'How tf did I get rejected for a job I already had π', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('13', 'xyz', 'asdf', 'Pending', '> asdfasdfasdf', 'true', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('14', 'asdf', 'asdf', 'Selected', '` -dsfasdf`', 'true', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('15', 'asdf', 'qwer', 'Pending', ' sarvesh', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('16', 'xvbasdf', 'l;sjdfl;jasdf', 'Pending', 'asdfasdfasdfasdfasdfasdf', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('17', 'Deolitte', 'SDE1', 'Selected', 'Guys i got selected the techical rounds were easy** **', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('18', 'Human Inc', 'Ex-Kitten', 'Selected', 'Moo. I mean, Mew.', 'true', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('19', 'Company', 'SDE', 'Selected', 'Yeahhhhhhhhh i got selected', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('11', 'Netflix', 'floor sweeper', 'Rejected', '# how tf did I mess this up man\n\n*It was literally a floor sweeping job*\n\n### I even showed my floor coding skills by creating a program that doesΒ `print("I am floor sweeper!!!")`\n\n> Honestly I am quite impressed that someone can fail an interveiw for this job\n\n```\n -my interviewer\n\n```\n\nThe job requirements:\n\n1. Know how to walk\n2. Know how to hold a mop\n3. Know how to use it on the floor\n\nWhen I asked them why I failed they gave the following evaluation:\n\n- The candidate came with a hatsune miku themed mop that had "my waifu helps me clean" on it\n', 'true', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('20', 'Google', 'Software Engineer', 'Selected', 'Yeah i got selected at Google', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('23', 'aaaaaaaaaaaa', 'aaaaaaaaaaaaaaaa', 'Selected', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('24', 'aabaasaxax', 'aaaa', 'Selected', '1234567cdcs', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('25', 'qwert', 'qwert', 'Selected', 'qwertyuiokjhgfdszxcv', 'true', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('26', 'asdfghjk', 'asdfghj', 'Selected', 'qwerdfghbnss', 'true', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('27', 'qqqqqqqqqq', 'qqqqqqqqqq', 'Selected', 'qqqqqqqqqqqqqqqqqqqqqq', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('28', 'qqqqqqqqqqqqqqq', 'qqqqqqqqqqqqqqqqqqq', 'Selected', 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('29', 'qqqqqqqqqqqqqqq', 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq', 'Pending', 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('34', 'qwed', 'ssss', 'Selected', 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('31', 'OnlyFans', 'Content Head', 'Selected', 'Yeahh i got selected because of my content ideas.', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('32', 'Tinder', 'Match Maker', 'Rejected', 'I got rejected because of my dogshit match making skillssss brooooooo', 'false', '77165f92-1a09-407c-987f-0fc9be16fad8'); + + +-- +-- Data for Name: Member; Type: TABLE DATA; Schema: public; Owner: - +-- + +INSERT INTO public."Member" (id, name, email, phone, bio, "profilePhoto", github, linkedin, twitter, geeksforgeeks, leetcode, codechef, codeforces, "passoutYear", "isApproved", "isManager", "createdAt", "updatedAt", "approvedById", birth_date) VALUES +('a6bc0b3a-71bf-4e0d-8879-ddedbbc0a766', 'your mom', 'yourmom@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2026-01-01 00:00:00', 'false', 'false', '2025-10-27 20:12:58.862', '2025-10-27 20:12:58.862', NULL, NULL), +('259a1e70-c093-43d4-8aa1-bba058a896b8', 'Sakshi Chaudhari', 'sakshi@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/27b32a1e-2edd-4663-8635-e0cf9c159967.jpeg', 'https://github.com/sakshiatul30', 'https://www.linkedin.com/in/sakshi-chaudhari-536a81324', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'false', 'false', '2025-07-27 13:44:45.59', '2025-09-15 18:34:02.698', '77165f92-1a09-407c-987f-0fc9be16fad8', NULL), +('92f0e65d-f306-4cdd-baad-059f645cf148', 'hello', 'hello123@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'false', 'false', '2025-11-20 09:01:13.485', '2025-11-20 09:01:13.485', NULL, NULL), +('db2bd9ec-25e5-4134-ae53-fea1734ca161', 'Aarya Godbole', 'aaryagodbole550@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/b7d68ce2-87c9-4ea6-9add-4e809b7ae37c.jpeg', 'https://github.com/aaryagodbole', 'https://www.linkedin.com/in/aarya-godbole/', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'false', '2025-07-27 13:56:11.408', '2025-07-27 19:37:09.064', NULL, NULL), +('22ba7f7a-14e7-45fa-bf5f-51d5f015496f', 'Vansh Waldeo', 'vansh@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/b856d400-78ea-4eb4-8f3c-1ffb1eb1b0fa.jpeg', 'https://github.com/VanshKing30', 'https://www.linkedin.com/in/vansh-waldeo-81ab31285/', NULL, NULL, NULL, NULL, NULL, '2025-01-01 00:00:00', 'true', 'false', '2025-07-27 13:12:09.267', '2025-07-27 13:12:09.267', NULL, NULL), +('6c968bfa-ebf8-4b2b-a349-36bbc9cc2870', 'Sachin Barvekar', 'sachin@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/d84017f1-8859-4444-a378-f7591f2c2456.jpeg', NULL, 'https://www.linkedin.com/in/sachin-barvekar-2874481a2/', NULL, NULL, NULL, NULL, NULL, '2025-01-01 00:00:00', 'true', 'false', '2025-07-27 14:28:13.538', '2025-07-27 14:28:13.538', NULL, NULL), +('7dd07cc1-08da-48cc-a162-f546356fe291', 'Vaishnavi Adhav', 'vaishnaviadhav@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/2ed0e256-7b52-4c90-abf1-2c759b4aeaa8.jpeg', 'https://github.com/vaishnavi4049', 'https://www.linkedin.com/in/vaishnavi-adhav-b5b346362', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'false', '2025-07-27 13:43:26.12', '2025-07-27 19:35:31.505', NULL, NULL), +('86237b63-1339-49d6-ad57-1d4b93bd5092', 'Bhaven Rathod', 'bhaven@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/df2e0e37-50e8-435e-92e7-fa2e507ca424.jpeg', NULL, 'https://www.linkedin.com/in/bhaven-rathod/', NULL, NULL, NULL, NULL, NULL, '2025-01-01 00:00:00', 'true', 'false', '2025-07-27 12:42:50.416', '2025-07-27 12:42:50.416', NULL, NULL), +('46cfa3aa-1efe-4cc6-a624-340808ef7cb8', 'Piyushaa Mahajan', 'piyushaa@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/396f4028-0423-45d0-94b4-5222e239d875.jpeg', 'https://github.com/piyushaa20', 'https://www.linkedin.com/in/piyushaa-mahajan-826594323', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'false', '2025-07-27 13:46:16.531', '2025-07-27 19:51:48.667', NULL, NULL), +('a8443783-dd59-446a-93f5-19f5e590e88b', 'Siddhesh Thorat', 'siddhesh@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/1ed6dc74-090b-4cca-9c9a-687b12a5086f.jpeg', 'https://github.com/siddhu9993', 'https://www.linkedin.com/in/d2d-siddhesh/', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'false', '2025-07-27 13:47:44.371', '2025-07-27 13:47:44.371', NULL, NULL), +('329d6d7a-9787-452e-9c0c-506481c5462a', 'Suhani Bhati', 'suhani@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/57bb90b4-817f-4aaf-ab83-6f202e50a1e7.jpeg', 'https://github.com/SuhaniBhati', 'https://www.linkedin.com/in/suhani-bhati-aa528828b', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'false', '2025-07-27 13:58:45.984', '2025-07-27 19:39:11.221', NULL, NULL), +('855fb554-02f7-4b6a-a17e-d55b7976babf', 'Sanskar Darekar', 'sanskar@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/3d832d1b-3d81-481e-a6a3-e9e206c61c65.jpeg', 'https://github.com/sanskar-sd', 'https://www.linkedin.com/in/sanskar-darekar/', NULL, NULL, NULL, NULL, NULL, '2026-01-01 00:00:00', 'true', 'false', '2025-07-27 12:53:25.333', '2025-07-27 19:40:07.915', NULL, NULL), +('738b73b4-0725-4f7c-95bf-cbdb72eb4e84', 'Vedant Bulbule', 'vedant@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/6b61cb98-edc9-4e17-9ef7-6784850b77c7.jpeg', 'https://github.com/Vedantbulbule1223', 'https://www.linkedin.com/in/vedant-bulbule-aiml/', NULL, NULL, NULL, NULL, NULL, '2026-01-01 00:00:00', 'true', 'false', '2025-07-27 12:36:06.689', '2025-07-27 12:36:06.689', NULL, NULL), +('b6f44922-fff3-48eb-a0c9-15d41e786e38', 'Anushka Bendle', 'anushka@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/f71382bc-a6d5-467e-ac77-81e6667ddbd1.jpeg', 'https://github.com/anushkabendle', 'https://www.linkedin.com/in/anushkabendle445', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'false', '2025-07-27 13:37:17.319', '2025-07-27 19:52:42.056', NULL, NULL), +('1b482f80-f649-45f9-a90b-7538a7a6e66e', 'Mukul Dhobale', 'Mukul@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/0c6b2d21-32cd-4cce-96ee-58d1e265dac5.jpeg', 'https://github.com/Mukul306', 'https://www.linkedin.com/in/mukul-dhobale-53b547333', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'false', '2025-07-27 13:36:07.064', '2025-07-27 19:55:31.61', NULL, NULL), +('1b5933a9-5d50-4246-861a-ca0d30bd581f', 'Samarth Lad', 'samarth@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/1f07275c-e0e5-4c66-b068-25c593c263cb.jpeg', 'https://github.com/samrth07', 'https://www.linkedin.com/in/samarth-lad-675b99322/', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'true', '2025-07-27 13:41:03.21', '2025-07-27 19:58:32.541', NULL, NULL), +('3e6666f5-8ad9-4686-8656-a6904360d4ba', 'Shaheen Khan', 'shaheen@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/71310f7a-2dba-4d68-9aef-eb18dc60b1e6.jpeg', 'https://github.com/Shaheen-25', 'https://www.linkedin.com/in/shaheenkhan25', NULL, NULL, NULL, NULL, NULL, '2026-01-01 00:00:00', 'true', 'false', '2025-07-27 12:49:35.89', '2025-07-27 19:34:16.322', NULL, NULL), +('8eeacf82-18e5-48f8-a11e-fdbbe2eb81ce', 'Shivaji Raut', 'shivaji@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/cb24b95d-7ff8-444e-993e-c4dae169a5b2.jpeg', 'https://github.com/shivaji43', 'https://www.linkedin.com/in/shivajiraut/', NULL, NULL, NULL, NULL, NULL, '2025-01-01 00:00:00', 'true', 'false', '2025-07-27 13:13:06.46', '2025-07-27 13:13:06.46', NULL, NULL), +('1404e81c-d567-4103-941b-0abeea7fc049', 'Swaraj Pawar', 'swaraj@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/1d6c896c-7889-4140-81df-6d1d11021b71.jpeg', 'https://github.com/Swaraj-23', 'https://www.linkedin.com/in/swaraj-pawar-webdev/', NULL, NULL, NULL, NULL, NULL, '2025-01-01 00:00:00', 'true', 'false', '2025-07-27 13:11:27.264', '2025-07-27 13:11:27.264', NULL, NULL), +('644b5e8f-910d-450e-a855-a88f31d02b7b', 'Sanica chorey', 'sanica@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/5cfd39ec-9bd1-4414-91f7-c25b2f4b96b7.jpeg', NULL, 'https://www.linkedin.com/in/sanica-chorey-0876b024b/', NULL, NULL, NULL, NULL, NULL, '2025-01-01 00:00:00', 'true', 'false', '2025-07-27 13:20:13.746', '2025-07-27 13:20:13.746', NULL, NULL), +('03a1a0f8-c1b9-4ac1-99d9-f10f26a82f7c', 'Eshwar Varpe', 'eshwar@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/23b3a8c4-5821-43fe-9f91-9e48e43ed1be.jpeg', NULL, 'https://www.linkedin.com/in/eshwar-varpe-37a309246/', NULL, NULL, NULL, NULL, NULL, '2024-01-01 00:00:00', 'true', 'false', '2025-07-27 13:00:37.038', '2025-07-27 13:00:37.038', NULL, NULL), +('0cc29a18-180c-408d-9cbe-0fc06109a5c1', 'Yash Kathane', 'yash@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/5dd11423-a067-4ccb-b24b-1a4c0eb0aed2.jpeg', NULL, 'https://www.linkedin.com/in/yash-kathane-31b3b1215/', NULL, NULL, NULL, NULL, NULL, '2024-01-01 00:00:00', 'true', 'false', '2025-07-27 13:03:02.901', '2025-07-27 13:03:02.901', NULL, NULL), +('ac713749-77c1-46b6-ae82-f16d616b1c7c', 'Pratik Bhoite', 'pratik@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/3dc40d0d-5101-4550-9999-1dc4a4f649c3.jpeg', NULL, 'https://www.linkedin.com/in/pratik-bhoite-839770232/', NULL, NULL, NULL, NULL, NULL, '2024-01-01 00:00:00', 'true', 'false', '2025-07-27 13:05:29.412', '2025-07-27 19:19:08.055', NULL, NULL), +('48724979-f9c8-46de-b9ab-9ca0186596d0', 'Aryan Jadlie', 'aryan@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/a7e96fa8-21af-481d-ad80-dcc6c55b3be2.png', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2026-01-01 00:00:00', 'false', 'false', '2025-07-27 18:47:06.554', '2025-10-15 21:42:34.758', '77165f92-1a09-407c-987f-0fc9be16fad8', NULL), +('69394246-3e41-4eb5-812a-48801b0b5f3e', 'Shubham Tohake', 'shubham@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/22dc88c0-57a4-42ca-913f-7ab9ffd92080.png', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'false', 'false', '2025-07-27 18:49:14.273', '2025-09-15 18:35:19.025', '77165f92-1a09-407c-987f-0fc9be16fad8', NULL), +('f032f524-c153-496f-9eea-e2ff8622f3d1', 'Sahil Mulani', 'sahil@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/c286bed1-186d-4c3d-aa3c-3fcfc2e33752.jpeg', 'https://github.com/MulaniSahil', 'https://www.linkedin.com/in/sahil-mulani-5b7bba2a8/', NULL, NULL, 'https://leetcode.com/u/mulanisahil/', NULL, NULL, '2026-01-01 00:00:00', 'true', 'false', '2025-07-27 19:42:24.97', '2025-07-28 10:07:00.727', NULL, NULL), +('d46a667d-5b68-4b82-9de7-fcfdf0ab0181', 'Vaishnavi Ambhore', 'vaishnavi@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/c13561c7-cebf-4bad-994e-2370c841720d.jpeg', 'https://github.com/vaish12345678', 'https://www.linkedin.com/in/vaishnavi-ambhore-157131335/', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'false', '2025-07-27 13:42:03.576', '2025-07-27 19:36:06.959', NULL, NULL), +('d7c96d3c-d45d-4bde-8c2b-39f0451a389f', 'Sarvesh Shiralkar', 'sarveshshiralkar@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/84ee8088-de1e-49ab-8bae-e044cf8d7830.jpeg', 'https://github.com/SarveshMS7', 'https://www.linkedin.com/in/sarvesh-shiralkar-69559a326/', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'false', '2025-07-27 14:02:15.223', '2025-07-27 19:41:33.591', NULL, NULL), +('d7d54e46-8db2-449c-87f9-8e89e8537c42', 'Sherin Thomas', 'sherin@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/de491aeb-c096-4995-8428-26b94607d45b.jpeg', 'https://github.com/Sherin-2711', 'https://www.linkedin.com/in/sherin-thomas-644242333', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'true', '2025-07-27 16:57:39.035', '2025-07-27 19:57:11.54', NULL, NULL), +('77165f92-1a09-407c-987f-0fc9be16fad8', 'Harish Narote', 'harish@gmail.com', '8668673365', 'DSA Head of Call Of Code.....', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/bc133101-04d9-457f-8c57-a0b3f7fd683a.jpeg', 'https://github.com/Harish-Naruto', 'https://www.linkedin.com/in/harish-narote-600717339/', NULL, NULL, NULL, NULL, NULL, '2028-01-01 18:30:00', 'true', 'true', '2025-07-27 20:43:21.525', '2025-11-21 14:20:00.495', NULL, '2025-11-28'), +('20ee0910-36b3-48d6-ad96-2112d02fd9b6', 'Dilip Choudhary', 'dillip@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/1a814f4f-cdf4-43a8-980e-f1062695f730.png', 'https://github.com/dilip7654', 'https://www.linkedin.com/in/dilip-choudhary-39966421a/', NULL, NULL, NULL, NULL, NULL, '2026-01-01 00:00:00', 'true', 'false', '2025-07-27 19:47:54.658', '2025-07-27 20:00:57.185', NULL, NULL), +('1ff4b36d-5671-4855-8476-d0a8993f9873', ' Aditya Modak', 'aditya@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/01800613-ca50-43ae-b0a4-a076c9da427d.jpeg', 'https://github.com/Aditya2002M', 'https://www.linkedin.com/in/aditya-modak-42a684250/', NULL, NULL, NULL, NULL, NULL, '2025-01-01 00:00:00', 'true', 'false', '2025-07-27 13:21:05.022', '2025-07-27 13:21:05.022', NULL, NULL), +('207bb8bd-3e48-40c8-83ce-a825cb9fe474', 'syswraith', 'syswraith@gmail.com', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'false', 'false', '2025-10-27 14:57:13.93', '2025-10-27 14:57:13.93', NULL, NULL), +('c5b2470d-7fb4-4c93-bfe2-fedc00415dc2', 'Shashwati Meshram ', 'shashwati@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/WhatsApp%20Image%202025-07-28%20at%205.00.43%20PM.jpeg', 'https://github.com/Shashwati12', 'https://www.linkedin.com/in/shashwati-meshram-785316325/', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'false', '2025-07-27 13:57:14.412', '2025-07-27 13:57:14.412', NULL, NULL), +('75ef229a-3770-46aa-adc8-f4d250c6ac81', 'Shivam Korade', 'shivam@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/be5d5c49-da35-4979-8e06-0b8bc56edea6.jpeg', NULL, 'https://www.linkedin.com/in/shivam-korade/', NULL, NULL, NULL, NULL, NULL, '2025-01-01 00:00:00', 'true', 'false', '2025-07-27 17:32:15.43', '2025-07-27 17:32:15.43', NULL, NULL), +('516af252-e8dc-48a4-80c4-5af1e0758e58', 'Sahil Lakare', 'sahillakare@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/ec07d6cd-d1b5-43b1-8749-11baca35abba.jpeg', NULL, 'https://www.linkedin.com/in/sahil-lakare-842304298/', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'false', 'false', '2025-07-27 14:07:22.15', '2025-10-06 06:38:03.943', '77165f92-1a09-407c-987f-0fc9be16fad8', NULL), +('046d352f-10d9-49b5-bbed-31d67bf4b583', 'Shreyash Kapse', 'sheryash@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/6360ee38-249d-4d40-83c2-de1bd2b03d22.png', NULL, NULL, NULL, NULL, 'https://leetcode.com', 'https://codechef.com', NULL, '2026-01-01 00:00:00', 'false', 'false', '2025-07-27 18:28:44.084', '2025-10-06 06:38:11.206', '77165f92-1a09-407c-987f-0fc9be16fad8', NULL), +('e68ca856-978b-4bb5-a2f1-6497278624bb', 'Abhiram Suradkar', 'Abhiram@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/6cec207d-5cac-466a-96cf-00faaae9141f.jpeg', 'https://github.com/abhi32GBram', 'https://www.linkedin.com/in/abhiram-suradkar-a6728622b/', NULL, NULL, NULL, NULL, NULL, '2025-01-01 00:00:00', 'true', 'false', '2025-07-27 18:41:32.116', '2025-07-27 18:41:32.116', NULL, NULL), +('ef59db8b-2ad5-4e0a-b741-f58521bf61ec', 'Komal Warake', 'komal@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/8aa5e9df-6bf1-458f-b65f-03d1a3389398.png', 'https://github.com/komalwarke1', 'https://www.linkedin.com/in/komal-warake01', NULL, NULL, NULL, NULL, NULL, '2026-01-01 00:00:00', 'true', 'false', '2025-07-27 19:29:28.585', '2025-07-27 19:32:17.955', NULL, NULL), +('bf152df5-3c23-4c1c-85aa-212e0487b420', 'Prathamesh Shinde', 'prathamesh@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/a0915ccc-c8f3-498a-96b1-1731e0f01258.jpeg', 'https://github.com/prathameshshinde555', 'https://www.linkedin.com/in/prathameshshinde555/', NULL, NULL, NULL, NULL, NULL, '2024-01-01 00:00:00', 'true', 'false', '2025-07-27 13:04:35.098', '2025-07-27 13:04:35.098', NULL, NULL), +('0b83d3e3-8685-4cfe-9f63-6cc22c1ceae4', 'Prajkta Patil', 'prajakta@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/ac073ec8-59b2-4f71-8fb0-1be1af7044b5.png', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2026-01-01 00:00:00', 'false', 'false', '2025-07-27 18:32:26.724', '2025-07-31 18:13:22.227', '77165f92-1a09-407c-987f-0fc9be16fad8', NULL), +('64464cc4-4dfc-4522-a256-6aca2371df7f', 'Veda Bhadane', 'veda@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/0b34ae85-da4a-480f-adc5-f27e83e47c32.jpeg', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2026-01-01 00:00:00', 'false', 'false', '2025-07-27 18:06:52.395', '2025-10-27 06:54:48.588', '77165f92-1a09-407c-987f-0fc9be16fad8', NULL), +('ad525dce-67dd-4878-ab95-068943923b81', 'Sarvesh Shahane', 'sarvesh@gmail.com', '9421957635', 'I am that guy.', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/2e341e00-8b4b-4276-b7c6-e0a68c45f73c.jpeg', 'https://github.com/i-am-that-guy', 'https://www.linkedin.com/in/sarveshshahane', 'https://x.com/_That_Guy_Here_', 'https://www.geeksforgeeks.org/profile/sarveshshaqram/', 'https://leetcode.com/u/This-Is-My-GitHub-Account/', 'https://www.codechef.com/users/i_am_that_guy', 'https://codeforces.com/profile/i_am_that_guy', '2025-01-01 00:00:00', 'true', 'false', '2025-07-27 13:29:37.542', '2025-07-27 21:14:03.77', NULL, '2026-01-07'), +('c494d747-5123-457f-b9cf-f3359f5a0fe8', 'Shruti Jadhav ', 'shruti@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/b01eec07-de6c-49eb-b4cd-31dbb17d1d71.jpeg', 'https://github.com/shrutiiiyet', 'https://www.linkedin.com/in/shruti-jadhav-892164277/', NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'true', 'true', '2025-07-27 17:18:50.419', '2025-07-27 19:59:53.048', NULL, NULL), +('cffc47fb-1147-4dd5-9818-21d209dbe3f3', 'Harsh Bhavsar', 'harsh@gmail.com', NULL, NULL, 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/members/66a493c8-5de1-4be8-b64a-d2639ec000e3.png', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2027-01-01 00:00:00', 'false', 'false', '2025-07-27 18:37:15.882', '2025-09-15 14:45:06.273', 'c494d747-5123-457f-b9cf-f3359f5a0fe8', NULL); + + +-- +-- Data for Name: MemberAchievement; Type: TABLE DATA; Schema: public; Owner: - +-- + +INSERT INTO public."MemberAchievement" ("memberId", "achievementId") VALUES +('22ba7f7a-14e7-45fa-bf5f-51d5f015496f', '3'), +('644b5e8f-910d-450e-a855-a88f31d02b7b', '4'), +('6c968bfa-ebf8-4b2b-a349-36bbc9cc2870', '4'), +('1ff4b36d-5671-4855-8476-d0a8993f9873', '4'), +('046d352f-10d9-49b5-bbed-31d67bf4b583', '4'), +('bf152df5-3c23-4c1c-85aa-212e0487b420', '5'), +('75ef229a-3770-46aa-adc8-f4d250c6ac81', '5'), +('48724979-f9c8-46de-b9ab-9ca0186596d0', '5'), +('86237b63-1339-49d6-ad57-1d4b93bd5092', '5'), +('1b5933a9-5d50-4246-861a-ca0d30bd581f', '6'), +('c494d747-5123-457f-b9cf-f3359f5a0fe8', '6'), +('db2bd9ec-25e5-4134-ae53-fea1734ca161', '6'), +('d46a667d-5b68-4b82-9de7-fcfdf0ab0181', '6'), +('69394246-3e41-4eb5-812a-48801b0b5f3e', '7'), +('7dd07cc1-08da-48cc-a162-f546356fe291', '7'), +('b6f44922-fff3-48eb-a0c9-15d41e786e38', '7'), +('259a1e70-c093-43d4-8aa1-bba058a896b8', '7'), +('644b5e8f-910d-450e-a855-a88f31d02b7b', '8'), +('86237b63-1339-49d6-ad57-1d4b93bd5092', '8'), +('ad525dce-67dd-4878-ab95-068943923b81', '8'), +('69394246-3e41-4eb5-812a-48801b0b5f3e', '9'), +('d7d54e46-8db2-449c-87f9-8e89e8537c42', '9'), +('46cfa3aa-1efe-4cc6-a624-340808ef7cb8', '9'), +('b6f44922-fff3-48eb-a0c9-15d41e786e38', '9'), +('bf152df5-3c23-4c1c-85aa-212e0487b420', '10'), +('0cc29a18-180c-408d-9cbe-0fc06109a5c1', '10'), +('03a1a0f8-c1b9-4ac1-99d9-f10f26a82f7c', '11'), +('64464cc4-4dfc-4522-a256-6aca2371df7f', '11'), +('046d352f-10d9-49b5-bbed-31d67bf4b583', '11'), +('69394246-3e41-4eb5-812a-48801b0b5f3e', '12'), +('c5b2470d-7fb4-4c93-bfe2-fedc00415dc2', '12'), +('0b83d3e3-8685-4cfe-9f63-6cc22c1ceae4', '13'), +('1b5933a9-5d50-4246-861a-ca0d30bd581f', '13'), +('22ba7f7a-14e7-45fa-bf5f-51d5f015496f', '13'), +('329d6d7a-9787-452e-9c0c-506481c5462a', '13'), +('64464cc4-4dfc-4522-a256-6aca2371df7f', '13'), +('cffc47fb-1147-4dd5-9818-21d209dbe3f3', '13'), +('69394246-3e41-4eb5-812a-48801b0b5f3e', '14'), +('ad525dce-67dd-4878-ab95-068943923b81', '15'), +('20ee0910-36b3-48d6-ad96-2112d02fd9b6', '15'), +('738b73b4-0725-4f7c-95bf-cbdb72eb4e84', '15'), +('f032f524-c153-496f-9eea-e2ff8622f3d1', '15'); + + +-- +-- Data for Name: MemberProject; Type: TABLE DATA; Schema: public; Owner: - +-- + +INSERT INTO public."MemberProject" ("memberId", "projectId") VALUES +('22ba7f7a-14e7-45fa-bf5f-51d5f015496f', '3'), +('22ba7f7a-14e7-45fa-bf5f-51d5f015496f', '4'), +('ad525dce-67dd-4878-ab95-068943923b81', '4'), +('1404e81c-d567-4103-941b-0abeea7fc049', '4'), +('1b5933a9-5d50-4246-861a-ca0d30bd581f', '6'), +('c494d747-5123-457f-b9cf-f3359f5a0fe8', '6'), +('db2bd9ec-25e5-4134-ae53-fea1734ca161', '6'), +('d46a667d-5b68-4b82-9de7-fcfdf0ab0181', '6'), +('d7d54e46-8db2-449c-87f9-8e89e8537c42', '8'), +('c5b2470d-7fb4-4c93-bfe2-fedc00415dc2', '8'), +('1b482f80-f649-45f9-a90b-7538a7a6e66e', '8'), +('329d6d7a-9787-452e-9c0c-506481c5462a', '8'), +('69394246-3e41-4eb5-812a-48801b0b5f3e', '10'), +('d7d54e46-8db2-449c-87f9-8e89e8537c42', '10'), +('46cfa3aa-1efe-4cc6-a624-340808ef7cb8', '10'), +('b6f44922-fff3-48eb-a0c9-15d41e786e38', '10'), +('ad525dce-67dd-4878-ab95-068943923b81', '11'), +('8eeacf82-18e5-48f8-a11e-fdbbe2eb81ce', '11'), +('1ff4b36d-5671-4855-8476-d0a8993f9873', '11'), +('22ba7f7a-14e7-45fa-bf5f-51d5f015496f', '11'), +('77165f92-1a09-407c-987f-0fc9be16fad8', '11'), +('d7d54e46-8db2-449c-87f9-8e89e8537c42', '11'), +('c5b2470d-7fb4-4c93-bfe2-fedc00415dc2', '11'), +('1b5933a9-5d50-4246-861a-ca0d30bd581f', '11'), +('c494d747-5123-457f-b9cf-f3359f5a0fe8', '11'), +('69394246-3e41-4eb5-812a-48801b0b5f3e', '13'), +('c5b2470d-7fb4-4c93-bfe2-fedc00415dc2', '13'), +('ad525dce-67dd-4878-ab95-068943923b81', '14'), +('77165f92-1a09-407c-987f-0fc9be16fad8', '14'), +('bf152df5-3c23-4c1c-85aa-212e0487b420', '15'), +('69394246-3e41-4eb5-812a-48801b0b5f3e', '16'), +('c5b2470d-7fb4-4c93-bfe2-fedc00415dc2', '16'), +('22ba7f7a-14e7-45fa-bf5f-51d5f015496f', '16'), +('db2bd9ec-25e5-4134-ae53-fea1734ca161', '16'); + + +-- +-- Data for Name: Project; Type: TABLE DATA; Schema: public; Owner: - +-- + +INSERT INTO public."Project" (id, name, "imageUrl", "githubUrl", "deployUrl", "createdAt", "createdById", "updatedAt", "updatedById") VALUES +('3', 'Codenest', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/projects/ea048f75-12ca-426f-a0f7-da5bd8d2a6ab.jpeg', 'https://github.com/VanshKing30/codenest', NULL, '2025-07-27 20:24:12.073', 'c494d747-5123-457f-b9cf-f3359f5a0fe8', '2025-07-27 20:24:12.073', NULL), +('4', 'FoodiesWeb', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/projects/5b2ea1df-4bbb-40f5-896a-add8dd6573c7.png', 'https://github.com/VanshKing30/FoodiesWeb', 'https://foodies-web-app.vercel.app/', '2025-07-27 20:25:05.349', 'c494d747-5123-457f-b9cf-f3359f5a0fe8', '2025-07-27 20:25:05.349', NULL), +('6', 'Sportify', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/projects/2cfe891d-f838-4118-9a89-bae85a5adab6.png', 'https://github.com/call-0f-code/Sportify', NULL, '2025-07-27 20:40:13.289', 'c494d747-5123-457f-b9cf-f3359f5a0fe8', '2025-07-27 20:40:13.289', NULL), +('8', 'EventHub', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/projects/259f8cf5-d265-4948-bbc7-987710af6b7e.jpeg', 'https://github.com/Shashwati12/Event-Hub', NULL, '2025-07-27 20:51:06.857', 'c494d747-5123-457f-b9cf-f3359f5a0fe8', '2025-07-27 20:51:06.857', NULL), +('10', 'Wanderlust', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/projects/0da621d2-a076-4043-ac6a-04d2aabbcebf.jpeg', 'https://github.com/Sherin-2711/Wanderlust', NULL, '2025-07-27 20:58:40.253', 'c494d747-5123-457f-b9cf-f3359f5a0fe8', '2025-07-27 20:58:40.253', NULL), +('11', 'CallOfCode', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/projects/fb99db7f-4262-4093-af16-c45fdadea1e8.png', 'https://github.com/call-0f-code/call-of-code', 'https://callofcode.in/', '2025-07-27 21:07:20.413', 'c494d747-5123-457f-b9cf-f3359f5a0fe8', '2025-07-27 21:07:20.413', NULL), +('13', 'WellnessWave', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/projects/54f25849-1484-4746-aa77-aa623b52bf54.png', 'https://github.com/SHUBHAMTOHAKE0203/WellnessWave-Hospital-Hunar-Intern-Hackathon', 'https://shubhamtohake0203.github.io/WellnessWave-Hospital-Hunar-Intern-Hackathon/', '2025-07-27 21:10:42.857', 'c494d747-5123-457f-b9cf-f3359f5a0fe8', '2025-07-27 21:10:42.857', NULL), +('14', 'EventHub', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/projects/79e2f1c2-4fa6-4267-9847-b8b6ae516a76.png', 'https://github.com/i-am-that-guy/EventHub', 'https://eventhub-1ukr.onrender.com/', '2025-07-27 21:28:03.777', 'c494d747-5123-457f-b9cf-f3359f5a0fe8', '2025-07-27 21:28:03.777', NULL), +('15', 'Technothon', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/projects/a4fae4ae-3829-47c6-a088-b5e3f2d1d51a.png', 'https://github.com/call-0f-code/technothon', 'https://call-0f-code.github.io/technothon/', '2025-07-27 21:29:52.401', 'c494d747-5123-457f-b9cf-f3359f5a0fe8', '2025-07-27 21:29:52.401', NULL), +('16', 'CureWave', 'https://riqqtbuoaycwwiemnmri.supabase.co/storage/v1/object/public/images/projects/bedabfee-a51e-49e4-86e7-f9d263002f1f.png', 'https://github.com/SHUBHAMTOHAKE0203/CureWave', 'https://cure-wave-one.vercel.app/', '2025-07-27 21:33:33.669', 'c494d747-5123-457f-b9cf-f3359f5a0fe8', '2025-07-27 21:33:33.669', NULL); + + +-- +-- Data for Name: Question; Type: TABLE DATA; Schema: public; Owner: - +-- + +INSERT INTO public."Question" (id, "questionName", difficulty, link, "topicId", "createdById", "createdAt", "updatedAt", "updatedById") VALUES +('19', 'Remove element', 'Medium', 'https://leetcode.com/problems/remove-element/description/?envType=study-plan-v2&envId=top-interview-150', '22', '77165f92-1a09-407c-987f-0fc9be16fad8', '2025-10-14 19:05:11.882', '2025-10-15 03:49:46.988', '77165f92-1a09-407c-987f-0fc9be16fad8'); + + +-- +-- Data for Name: Topic; Type: TABLE DATA; Schema: public; Owner: - +-- + +INSERT INTO public."Topic" (id, title, description, "createdById", "createdAt", "updatedAt", "updatedById") VALUES +('23', 'TOPIC NAME ', 'JSBDNREMC DKVNROEJFPWMC REGFIRHGR VSM CLSMJFORJO HA HEE ITJAOJJA', '77165f92-1a09-407c-987f-0fc9be16fad8', '2025-10-14 18:47:47.527', '2025-10-14 18:47:47.527', '77165f92-1a09-407c-987f-0fc9be16fad8'), +('22', 'demoooooo', 'who the fuck writes demo like this ', '77165f92-1a09-407c-987f-0fc9be16fad8', '2025-10-14 18:31:21.048', '2025-10-15 03:43:53.083', '77165f92-1a09-407c-987f-0fc9be16fad8'); + + +-- Supabase compatibility fixes (appended for local setup) +-- Ensure enum types used by Prisma exist +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'Difficulty') THEN + CREATE TYPE public."Difficulty" AS ENUM ('Easy','Medium','Hard'); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'Verdict') THEN + CREATE TYPE public."Verdict" AS ENUM ('Selected','Rejected','Pending'); + END IF; +END$$; + +-- Create auth schema and minimal users table if missing +CREATE SCHEMA IF NOT EXISTS auth; + +CREATE TABLE IF NOT EXISTS auth.users ( + id uuid PRIMARY KEY, + aud text, + role text, + email text, + encrypted_password text, + email_confirmed boolean DEFAULT false, + raw_user_meta_data jsonb, + created_at timestamptz DEFAULT now(), + updated_at timestamptz DEFAULT now() +); + +-- Insert a sample auth user mapped to an existing Member (no-op if already exists) +INSERT INTO auth.users (id, aud, role, email, email_confirmed) +VALUES ('77165f92-1a09-407c-987f-0fc9be16fad8','authenticated','authenticated','harish@gmail.com', true) +ON CONFLICT (id) DO NOTHING; + +-- Create simple DB roles for local testing and grant access +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'anon') THEN + CREATE ROLE anon; + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'service_role') THEN + CREATE ROLE service_role; + END IF; +END$$; + +-- Grant read access to anon and full access to service_role for local development only +GRANT USAGE ON SCHEMA public TO anon, service_role; +GRANT SELECT ON ALL TABLES IN SCHEMA public TO anon; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO service_role; + +-- Ensure future tables inherit privileges +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO anon; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO service_role; + +-- End of appended Supabase compatibility SQL + + +-- +-- Data for Name: _prisma_migrations; Type: TABLE DATA; Schema: public; Owner: - +-- + +INSERT INTO public._prisma_migrations (id, checksum, finished_at, migration_name, logs, rolled_back_at, started_at, applied_steps_count) VALUES +('2b698eba-1b33-44fd-b82a-c5c6043ef533', '89625be867ca784d446e9010c858e973cdac24ce7375c77db48bf56c294ec822', '2025-07-27 11:16:59.055498+00', '20250713111533_first_prisma_migration', NULL, NULL, '2025-07-27 11:16:58.7832+00', '1'), +('62ee3fac-53d8-4a55-84a1-3b92da855594', 'a13448142741adec639e1f8a1c28c39c767427e580ead7fac23fe3a6a3f394ae', '2025-07-27 11:16:59.4006+00', '20250718201903_init', NULL, NULL, '2025-07-27 11:16:59.145301+00', '1'), +('f8043ab8-18cb-4d4e-a20c-acb3ef6cdf68', '14515e15ca68369491dabe78a7f059e4d5157e51eab6a28d38483afe49afcb39', '2025-07-27 11:16:59.703971+00', '20250719123800_image_url_githuburk_make_complusory', NULL, NULL, '2025-07-27 11:16:59.501527+00', '1'), +('f0a81f30-46ba-4561-a537-60ce243a191e', 'e312f3719ce8eb87764107f8467b419001c25510c4302f444e7743fa20b0c30d', '2025-07-27 11:18:21.610297+00', '20250727111821_passoutyear_optional', NULL, NULL, '2025-07-27 11:18:21.388193+00', '1'), +('6446026f-c163-4250-a5f1-e1e89e8269a0', '247a03b37d029c1af7ade67e7c91eced3e5adeec02cd9b8c7e67e08e148cbfd6', '2025-10-16 19:11:32.783002+00', '20251016191132_birth_date_added_to_member', NULL, NULL, '2025-10-16 19:11:32.620529+00', '1'), +('e87cdcbd-1fbb-4285-8738-e7072925decb', 'f8c188dd8523234c3b95f96c4a8a78a585828817f07fdbf1c294502072ace662', '2025-10-16 19:38:12.06791+00', '20251016193811_birth_date_type_fix', NULL, NULL, '2025-10-16 19:38:11.846426+00', '1'); + + +-- +-- Data for Name: schema_migrations; Type: TABLE DATA; Schema: realtime; Owner: - +-- + +INSERT INTO realtime.schema_migrations (version, inserted_at) VALUES +('20211116024918', '2025-07-13 10:28:51'), +('20211116045059', '2025-07-13 10:28:52'), +('20211116050929', '2025-07-13 10:28:53'), +('20211116051442', '2025-07-13 10:28:53'), +('20211116212300', '2025-07-13 10:28:54'), +('20211116213355', '2025-07-13 10:28:55'), +('20211116213934', '2025-07-13 10:28:55'), +('20211116214523', '2025-07-13 10:28:56'), +('20211122062447', '2025-07-13 10:28:57'), +('20211124070109', '2025-07-13 10:28:57'), +('20211202204204', '2025-07-13 10:28:58'), +('20211202204605', '2025-07-13 10:28:59'), +('20211210212804', '2025-07-13 10:29:01'), +('20211228014915', '2025-07-13 10:29:01'), +('20220107221237', '2025-07-13 10:29:02'), +('20220228202821', '2025-07-13 10:29:02'), +('20220312004840', '2025-07-13 10:29:03'), +('20220603231003', '2025-07-13 10:29:04'), +('20220603232444', '2025-07-13 10:29:05'), +('20220615214548', '2025-07-13 10:29:05'), +('20220712093339', '2025-07-13 10:29:06'), +('20220908172859', '2025-07-13 10:29:07'), +('20220916233421', '2025-07-13 10:29:07'), +('20230119133233', '2025-07-13 10:29:08'), +('20230128025114', '2025-07-13 10:29:09'), +('20230128025212', '2025-07-13 10:29:09'), +('20230227211149', '2025-07-13 10:29:10'), +('20230228184745', '2025-07-13 10:29:11'), +('20230308225145', '2025-07-13 10:29:11'), +('20230328144023', '2025-07-13 10:29:12'), +('20231018144023', '2025-07-13 10:29:12'), +('20231204144023', '2025-07-13 10:29:13'), +('20231204144024', '2025-07-13 10:29:14'), +('20231204144025', '2025-07-13 10:29:15'), +('20240108234812', '2025-07-13 10:29:15'), +('20240109165339', '2025-07-13 10:29:16'), +('20240227174441', '2025-07-13 10:29:17'), +('20240311171622', '2025-07-13 10:29:18'), +('20240321100241', '2025-07-13 10:29:19'), +('20240401105812', '2025-07-13 10:29:21'), +('20240418121054', '2025-07-13 10:29:22'), +('20240523004032', '2025-07-13 10:29:24'), +('20240618124746', '2025-07-13 10:29:25'), +('20240801235015', '2025-07-13 10:29:25'), +('20240805133720', '2025-07-13 10:29:26'), +('20240827160934', '2025-07-13 10:29:26'), +('20240919163303', '2025-07-13 10:29:27'), +('20240919163305', '2025-07-13 10:29:28'), +('20241019105805', '2025-07-13 10:29:29'), +('20241030150047', '2025-07-13 10:29:31'), +('20241108114728', '2025-07-13 10:29:32'), +('20241121104152', '2025-07-13 10:29:32'), +('20241130184212', '2025-07-13 10:29:33'), +('20241220035512', '2025-07-13 10:29:34'), +('20241220123912', '2025-07-13 10:29:34'), +('20241224161212', '2025-07-13 10:29:35'), +('20250107150512', '2025-07-13 10:29:35'), +('20250110162412', '2025-07-13 10:29:36'), +('20250123174212', '2025-07-13 10:29:37'), +('20250128220012', '2025-07-13 10:29:37'), +('20250506224012', '2025-07-13 10:29:38'), +('20250523164012', '2025-07-13 10:29:38'), +('20250714121412', '2025-07-18 09:59:25'), +('20250905041441', '2025-10-03 22:35:03'), +('20251103001201', '2025-11-13 08:50:05'), +('20251120212548', '2026-03-23 05:56:26'), +('20251120215549', '2026-03-23 05:56:26'), +('20260218120000', '2026-03-23 05:56:27'); + +-- +-- Name: Account Account_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Account" + ADD CONSTRAINT "Account_pkey" PRIMARY KEY (id); + + +-- +-- Name: Achievement Achievement_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Achievement" + ADD CONSTRAINT "Achievement_pkey" PRIMARY KEY (id); + + +-- +-- Name: CompletedQuestion CompletedQuestion_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."CompletedQuestion" + ADD CONSTRAINT "CompletedQuestion_pkey" PRIMARY KEY ("memberId", "questionId"); + + +-- +-- Name: InterviewExperience InterviewExperience_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."InterviewExperience" + ADD CONSTRAINT "InterviewExperience_pkey" PRIMARY KEY (id); + + +-- +-- Name: MemberAchievement MemberAchievement_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."MemberAchievement" + ADD CONSTRAINT "MemberAchievement_pkey" PRIMARY KEY ("memberId", "achievementId"); + + +-- +-- Name: MemberProject MemberProject_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."MemberProject" + ADD CONSTRAINT "MemberProject_pkey" PRIMARY KEY ("memberId", "projectId"); + + +-- +-- Name: Member Member_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Member" + ADD CONSTRAINT "Member_pkey" PRIMARY KEY (id); + + +-- +-- Name: Project Project_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Project" + ADD CONSTRAINT "Project_pkey" PRIMARY KEY (id); + + +-- +-- Name: Question Question_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Question" + ADD CONSTRAINT "Question_pkey" PRIMARY KEY (id); + + +-- +-- Name: Topic Topic_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Topic" + ADD CONSTRAINT "Topic_pkey" PRIMARY KEY (id); + + +-- +-- Name: _prisma_migrations _prisma_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public._prisma_migrations + ADD CONSTRAINT _prisma_migrations_pkey PRIMARY KEY (id); + + +-- +-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: realtime; Owner: - +-- + +ALTER TABLE ONLY realtime.schema_migrations + ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); + +-- +-- Name: Account Account_memberId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Account" + ADD CONSTRAINT "Account_memberId_fkey" FOREIGN KEY ("memberId") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: Achievement Achievement_createdById_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Achievement" + ADD CONSTRAINT "Achievement_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE SET NULL; + + +-- +-- Name: Achievement Achievement_updatedById_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Achievement" + ADD CONSTRAINT "Achievement_updatedById_fkey" FOREIGN KEY ("updatedById") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE SET NULL; + + +-- +-- Name: CompletedQuestion CompletedQuestion_memberId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."CompletedQuestion" + ADD CONSTRAINT "CompletedQuestion_memberId_fkey" FOREIGN KEY ("memberId") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: CompletedQuestion CompletedQuestion_questionId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."CompletedQuestion" + ADD CONSTRAINT "CompletedQuestion_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES public."Question"(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: InterviewExperience InterviewExperience_memberId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."InterviewExperience" + ADD CONSTRAINT "InterviewExperience_memberId_fkey" FOREIGN KEY ("memberId") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: MemberAchievement MemberAchievement_achievementId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."MemberAchievement" + ADD CONSTRAINT "MemberAchievement_achievementId_fkey" FOREIGN KEY ("achievementId") REFERENCES public."Achievement"(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: MemberAchievement MemberAchievement_memberId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."MemberAchievement" + ADD CONSTRAINT "MemberAchievement_memberId_fkey" FOREIGN KEY ("memberId") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: MemberProject MemberProject_memberId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."MemberProject" + ADD CONSTRAINT "MemberProject_memberId_fkey" FOREIGN KEY ("memberId") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: MemberProject MemberProject_projectId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."MemberProject" + ADD CONSTRAINT "MemberProject_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES public."Project"(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: Member Member_approvedById_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Member" + ADD CONSTRAINT "Member_approvedById_fkey" FOREIGN KEY ("approvedById") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE SET NULL; + + +-- +-- Name: Project Project_createdById_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Project" + ADD CONSTRAINT "Project_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE SET NULL; + + +-- +-- Name: Project Project_updatedById_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Project" + ADD CONSTRAINT "Project_updatedById_fkey" FOREIGN KEY ("updatedById") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE SET NULL; + + +-- +-- Name: Question Question_createdById_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Question" + ADD CONSTRAINT "Question_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE SET NULL; + + +-- +-- Name: Question Question_topicId_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Question" + ADD CONSTRAINT "Question_topicId_fkey" FOREIGN KEY ("topicId") REFERENCES public."Topic"(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: Question Question_updatedById_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Question" + ADD CONSTRAINT "Question_updatedById_fkey" FOREIGN KEY ("updatedById") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE SET NULL; + + +-- +-- Name: Topic Topic_createdById_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Topic" + ADD CONSTRAINT "Topic_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE SET NULL; + + +-- +-- Name: Topic Topic_updatedById_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Topic" + ADD CONSTRAINT "Topic_updatedById_fkey" FOREIGN KEY ("updatedById") REFERENCES public."Member"(id) ON UPDATE CASCADE ON DELETE SET NULL; + + +COMMIT; diff --git a/src/app.ts b/src/app.ts index 2a4470d..c9dab81 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,16 +4,10 @@ import multer from "multer"; import { json, urlencoded } from "body-parser"; import routes from "./routes"; import { errorHandler } from "./utils/apiError"; -import { createClient } from "@supabase/supabase-js"; -import config from "./config"; import path from "path"; import { logger } from "./utils/logger"; import morgan from "morgan"; -// Initialize Supabase client for storage operations -export const supabase = createClient( - config.SUPABASE_URL, - config.SUPABASE_SERVICE_ROLE_KEY, -); +import config from "./config"; const app = express(); class LoggerStream { @@ -44,7 +38,10 @@ app.use("/health",(req,res)=>{ res.status(200).json({ message: "OK" }); }) -app.use("/api/v1", routes(upload, supabase)); +app.use("/api/v1", routes(upload)); + +// Serve API documentation +app.use("/docs", express.static(path.join(__dirname, "..", "doc"))); // 404 handler app.use((req, res) => { @@ -55,6 +52,4 @@ app.use((req, res) => { // Global error handler app.use(errorHandler); -// Serve API documentation -app.use("/docs", express.static(path.join(__dirname, "..", "docs/apidoc"))); export default app; diff --git a/src/controllers/achievement.controller.ts b/src/controllers/achievement.controller.ts index b7e176d..6edc1b4 100644 --- a/src/controllers/achievement.controller.ts +++ b/src/controllers/achievement.controller.ts @@ -1,8 +1,8 @@ import { Request, Response } from "express"; import * as achievementService from "../services/achievement.service"; import { uploadImage, deleteImage } from "../utils/imageUtils"; -import { supabase } from "../app"; import { ApiError } from "../utils/apiError"; +import { supabase } from "../utils/supabaseClient"; export const getAchievements = async (req: Request, res: Response) => { const achievements = await achievementService.getAchievements(); diff --git a/src/controllers/emailTemplate.controller.ts b/src/controllers/emailTemplate.controller.ts new file mode 100644 index 0000000..ad27a33 --- /dev/null +++ b/src/controllers/emailTemplate.controller.ts @@ -0,0 +1,66 @@ +import { Request, Response } from "express"; +import * as emailTemplateService from "../services/emailTemplate.service"; +import { ApiError } from "../utils/apiError"; + +// GET /email/templates +export const listTemplates = async (_req: Request, res: Response) => { + const templates = await emailTemplateService.listTemplates(); + res.status(200).json({ success: true, templates }); +}; + +// GET /email/templates/:id +export const getTemplate = async (req: Request, res: Response) => { + const id = Number(req.params.id); + if (isNaN(id)) throw new ApiError("Invalid template id", 400); + + const template = await emailTemplateService.getTemplateById(id); + if (!template) throw new ApiError("Template not found", 404); + + res.status(200).json({ success: true, template }); +}; + +// POST /email/templates +export const createTemplate = async (req: Request, res: Response) => { + const { name, subject, htmlBody, textBody, createdById } = req.body; + + if (!name || !subject || !htmlBody) { + throw new ApiError("name, subject and htmlBody are required", 400); + } + + const template = await emailTemplateService.createTemplate( + name, + subject, + htmlBody, + textBody, + createdById, + ); + + res.status(201).json({ success: true, template }); +}; + +// PATCH /email/templates/:id +export const updateTemplate = async (req: Request, res: Response) => { + const id = Number(req.params.id); + if (isNaN(id)) throw new ApiError("Invalid template id", 400); + + const { name, subject, htmlBody, textBody, updatedById } = req.body; + + const template = await emailTemplateService.updateTemplate(id, { + name, + subject, + htmlBody, + textBody, + updatedById, + }); + + res.status(200).json({ success: true, template }); +}; + +// DELETE /email/templates/:id +export const deleteTemplate = async (req: Request, res: Response) => { + const id = Number(req.params.id); + if (isNaN(id)) throw new ApiError("Invalid template id", 400); + + await emailTemplateService.deleteTemplate(id); + res.status(200).json({ success: true, message: "Template deleted" }); +}; diff --git a/src/controllers/member.controller.ts b/src/controllers/member.controller.ts index 525854f..19ee9e9 100644 --- a/src/controllers/member.controller.ts +++ b/src/controllers/member.controller.ts @@ -3,6 +3,7 @@ import * as memberService from "../services/member.service"; import { ApiError } from "../utils/apiError"; import { uploadImage } from "../utils/imageUtils"; import { SupabaseClient } from "@supabase/supabase-js"; +import { Role } from "../generated/prisma/client"; // List all approved members export const listAllApprovedMembers = async (req: Request, res: Response) => { @@ -114,8 +115,6 @@ export const updateRequest = async (req: Request, res: Response) => { throw new ApiError("No essential creds provided", 400); } - if(!isApproved) throw new ApiError("Someone interrupting the backend flow", 400); - const update = await memberService.approveRequest( isApproved, adminId, @@ -156,3 +155,59 @@ export const getUserInterviews = async (req: Request, res: Response) => { const interviews = await memberService.getInterviews(memberId); res.status(200).json({ success: true, interviews }); }; + +// Update a member's role (SUPER_ADMIN only) +export const updateMemberRole = async (req: Request, res: Response) => { + const { memberId } = req.params; + const { adminId, role } = req.body; + + if (!memberId || !adminId || !role) { + throw new ApiError("memberId, adminId, and role are required", 400); + } + + const validRoles = Object.values(Role); + if (!validRoles.includes(role)) { + throw new ApiError( + `Invalid role. Must be one of: ${validRoles.join(", ")}`, + 400, + ); + } + + const updated = await memberService.updateMemberRole(adminId, memberId, role as Role); + + res.status(200).json({ + success: true, + user: updated, + message: `Role updated to ${role}`, + }); +}; + +// Ghost or unghost a member (Dead Zone) β ADMIN & SUPER_ADMIN only +export const ghostMember = async (req: Request, res: Response) => { + const { memberId } = req.params; + const { adminId, ghost = true } = req.body; + + if (!memberId || !adminId) { + throw new ApiError("memberId and adminId are required", 400); + } + + if (typeof ghost !== "boolean") { + throw new ApiError('"ghost" must be a boolean', 400); + } + + const updated = await memberService.ghostMember(adminId, memberId, ghost); + + const action = ghost ? "ghosted" : "unghosted"; + const movement = ghost ? "moved to Dead Zone" : "restored from Dead Zone"; + res.status(200).json({ + success: true, + user: updated, + message: `Member ${action} and ${movement}`, + }); +}; + +// Get all ghosted members (Dead Zone audit list) β ADMIN & SUPER_ADMIN only +export const getDeadZoneMembers = async (req: Request, res: Response) => { + const members = await memberService.deadZoneMembers(); + res.status(200).json({ success: true, members }); +}; diff --git a/src/controllers/project.controller.ts b/src/controllers/project.controller.ts index 1821604..c8be4b9 100644 --- a/src/controllers/project.controller.ts +++ b/src/controllers/project.controller.ts @@ -2,7 +2,7 @@ import * as projectService from "../services/project.service"; import { Request, Response } from "express"; import { ApiError } from "../utils/apiError"; import { deleteImage, uploadImage } from "../utils/imageUtils"; -import { supabase } from "../app"; +import { supabase } from "../utils/supabaseClient"; export const getProjects = async (req: Request, res: Response) => { diff --git a/src/controllers/site-content.controller.ts b/src/controllers/site-content.controller.ts new file mode 100644 index 0000000..184768d --- /dev/null +++ b/src/controllers/site-content.controller.ts @@ -0,0 +1,220 @@ +import { Request, Response } from "express"; +import * as siteContentService from "../services/site-content.service"; +import { uploadImage, deleteImage } from "../utils/imageUtils"; +import { supabase } from "../utils/supabaseClient"; +import { ApiError } from "../utils/apiError"; + +function parseSiteContentData(body: RecordJoin WhatsApp: {{whatsapp_link}}
" + * }' + */ + router.post("/templates", emailTemplateCtrl.createTemplate); + + /** + * @api {patch} /email/templates/:id Update an email template + * @apiName UpdateEmailTemplate + * @apiGroup EmailTemplate + * + * @apiParam (URL Params) {Number} id Template ID. + * @apiBody {String} [name] New unique name. + * @apiBody {String} [subject] New subject. + * @apiBody {String} [htmlBody] New HTML body. + * @apiBody {String} [textBody] New plain-text body. + * + * @apiSuccess {Object} template Updated template object. + * + * @apiExample {curl} Example usage: + * curl -X PATCH http://localhost:3000/api/v1/email/templates/1 \ + * -H "Content-Type: application/json" \ + * -d '{"subject": "Welcome aboard!"}' + */ + router.patch("/templates/:id", emailTemplateCtrl.updateTemplate); + + /** + * @api {delete} /email/templates/:id Delete an email template + * @apiName DeleteEmailTemplate + * @apiGroup EmailTemplate + * + * @apiParam (URL Params) {Number} id Template ID. + * + * @apiSuccess {String} message Confirmation message. + * + * @apiExample {curl} Example usage: + * curl -X DELETE http://localhost:3000/api/v1/email/templates/1 + */ + router.delete("/templates/:id", emailTemplateCtrl.deleteTemplate); + + return router; +} diff --git a/src/routes/index.ts b/src/routes/index.ts index 74757ee..74ddb5b 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -8,14 +8,16 @@ import topicRouter from './topics' import quetionsRouter from './questions' import progressRouter from './progress' import membersRouter from './members' +import siteContentRouter from './site-content' +import emailRouter from './email' -export default function routes(upload: Multer, supabase: SupabaseClient) { +export default function routes(upload: Multer) { const router = Router(); - router.use('/members', membersRouter(upload, supabase)) + router.use('/members', membersRouter(upload)) - router.use('/projects', projectsRouter(upload, supabase)) + router.use('/projects', projectsRouter(upload)) - router.use('/achievements' ,acheivementsRouter(upload, supabase)); + router.use('/achievements' ,acheivementsRouter(upload)); router.use('/interviews', interviewRouter()); @@ -25,6 +27,9 @@ export default function routes(upload: Multer, supabase: SupabaseClient) { router.use("/progress", progressRouter()); + router.use("/site-content", siteContentRouter(upload)); + router.use("/email", emailRouter()); + return router; } diff --git a/src/routes/members.ts b/src/routes/members.ts index 1a7addc..c0a97bb 100644 --- a/src/routes/members.ts +++ b/src/routes/members.ts @@ -1,11 +1,11 @@ import express from "express"; import * as memberCtrl from "../controllers/member.controller"; import { Multer } from "multer"; -import { SupabaseClient } from "@supabase/supabase-js"; +import { supabase } from "../utils/supabaseClient"; + export default function membersRouter( upload: Multer, - supabase: SupabaseClient, ) { const router = express.Router(); @@ -17,9 +17,25 @@ export default function membersRouter( * @apiSuccess {Object[]} unapprovedMembers List of unapproved members. * * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/unapproved + * curl -X GET http://localhost:3000/api/v1/members/unapproved */ router.get("/unapproved", memberCtrl.getUnapprovedMembers); + + /** + * @api {get} /members/dead-zone List all ghosted members (Dead Zone) + * @apiName GetDeadZoneMembers + * @apiGroup Member + * + * @apiDescription Returns all member records that have been ghosted by an admin. + * These members are hidden from the public approved list and the pending + * approval queue. Data is preserved for audit purposes. + * + * @apiSuccess {Object[]} members List of ghosted member objects (includes ghostedBy admin info). + * + * @apiExample {curl} Example usage: + * curl -X GET http://localhost:3000/api/v1/members/dead-zone + */ + router.get("/dead-zone", memberCtrl.getDeadZoneMembers); /** * @api {get} /members/:memberId Get a member's details @@ -32,7 +48,7 @@ export default function membersRouter( * @apiError (Error 400) BadRequest No memberId provided. * * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/123 + * curl -X GET http://localhost:3000/api/v1/members/123 */ router.get("/:memberId", memberCtrl.getUserDetails); @@ -54,10 +70,10 @@ export default function membersRouter( * @apiError (400) IncorrectEmail The provided email does not match any user. * * @apiExample {curl} Example usage (list all): - * curl -X GET http://localhost:3000/members + * curl -X GET http://localhost:3000/api/v1/members * * @apiExample {curl} Example usage (get by email): - * curl -X GET "http://localhost:3000/members?email=john@example.com" + * curl -X GET "http://localhost:3000/api/v1/members?email=john@example.com" */ router.get("/", memberCtrl.listAllApprovedMembers); @@ -86,7 +102,7 @@ export default function membersRouter( * -F "password=securePass123" \ * -F "passoutYear=2026" \ * -F "provider=credentials" \ - * http://localhost:3000/members + * http://localhost:3000/api/v1/members */ router.post("/", upload.single("file"), memberCtrl.createAMember(supabase)); @@ -124,7 +140,7 @@ export default function membersRouter( * @apiExample {curl} Example usage: * curl -X PATCH -F "file=@profile.jpg" \ * -F 'memberData={"name":"John Doe","email":"john@example.com"}' \ - * http://localhost:3000/members/123 + * http://localhost:3000/api/v1/members/123 */ router.patch( "/:memberId", @@ -146,12 +162,45 @@ export default function membersRouter( * @apiError (Error 400) BadRequest Missing required fields. * * @apiExample {curl} Example usage: - * curl -X PATCH http://localhost:3000/members/approve/123 \ + * curl -X PATCH http://localhost:3000/api/v1/members/approve/123 \ * -H "Content-Type: application/json" \ * -d '{"isApproved": true, "adminId": "admin123"}' */ router.patch("/approve/:memberId", memberCtrl.updateRequest); + /** + * @api {patch} /members/ghost/:memberId Ghost or unghost a member (Dead Zone) + * @apiName GhostMember + * @apiGroup Member + * + * @apiDescription Moves a member into the Dead Zone by setting isGhosted=true. + * Ghosted members are silently removed from the pending approval queue and + * the public member listing without hard-deleting their data. + * Setting ghost=false restores the member back to the active queue. + * + * @apiParam (URL Params) {String} memberId Target member's ID. + * @apiBody {String} adminId ID of the Admin or Super Admin performing the action. + * @apiBody {Boolean} [ghost=true] true to ghost, false to unghost. + * + * @apiSuccess {Boolean} success Request status. + * @apiSuccess {Object} user Updated member object. + * @apiSuccess {String} message Confirmation message. + * + * @apiError (Error 400) BadRequest Missing required fields or invalid ghost value. + * @apiError (Error 403) Forbidden Only Admins and Super Admins can ghost members. + * + * @apiExample {curl} Ghost a member: + * curl -X PATCH http://localhost:3000/api/v1/members/ghost/123 \ + * -H "Content-Type: application/json" \ + * -d '{"adminId": "admin-id", "ghost": true}' + * + * @apiExample {curl} Unghost a member: + * curl -X PATCH http://localhost:3000/api/v1/members/ghost/123 \ + * -H "Content-Type: application/json" \ + * -d '{"adminId": "admin-id", "ghost": false}' + */ + router.patch("/ghost/:memberId", memberCtrl.ghostMember); + /** * @api {get} /members/:memberId/achievements Get member's achievements @@ -163,7 +212,7 @@ export default function membersRouter( * @apiSuccess {Object[]} achievements List of achievements. * * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/123/achievements + * curl -X GET http://localhost:3000/api/v1/members/123/achievements */ router.get("/:memberId/achievements", memberCtrl.getUserAchievements); @@ -177,7 +226,7 @@ export default function membersRouter( * @apiSuccess {Object[]} projects List of projects. * * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/123/projects + * curl -X GET http://localhost:3000/api/v1/members/123/projects */ router.get("/:memberId/projects", memberCtrl.getUserProjects); @@ -191,9 +240,32 @@ export default function membersRouter( * @apiSuccess {Object[]} interviews List of interviews. * * @apiExample {curl} Example usage: - * curl -X GET http://localhost:3000/members/123/interviews + * curl -X GET http://localhost:3000/api/v1/members/123/interviews */ router.get("/:memberId/interviews", memberCtrl.getUserInterviews); + /** + * @api {patch} /members/:memberId/role Update a member's role + * @apiName UpdateMemberRole + * @apiGroup Member + * + * @apiParam (URL Params) {String} memberId Target member's ID. + * @apiBody {String} adminId ID of the Super Admin performing the change. + * @apiBody {String="SUPER_ADMIN","ADMIN","FOUNDER","MEMBER"} role New role to assign. + * + * @apiSuccess {Boolean} success Request status. + * @apiSuccess {Object} user Updated member object. + * @apiSuccess {String} message Confirmation message. + * + * @apiError (Error 400) BadRequest Missing required fields or invalid role. + * @apiError (Error 403) Forbidden Only Super Admins can assign roles. + * + * @apiExample {curl} Example usage: + * curl -X PATCH http://localhost:3000/api/v1/members/123/role \ + * -H "Content-Type: application/json" \ + * -d '{"adminId": "superadmin-id", "role": "ADMIN"}' + */ + router.patch("/:memberId/role", memberCtrl.updateMemberRole); + return router; } diff --git a/src/routes/projects.ts b/src/routes/projects.ts index 8463e62..002086b 100644 --- a/src/routes/projects.ts +++ b/src/routes/projects.ts @@ -1,6 +1,5 @@ import { NextFunction, Request, Response, Router } from 'express' import { Multer } from 'multer' -import { SupabaseClient } from '@supabase/supabase-js' import { addMembers, createProject, @@ -26,8 +25,7 @@ function parseProjectData(req: Request, res: Response, next: NextFunction) { } export default function projectsRouter( - upload: Multer, - supabase: SupabaseClient, + upload: Multer, ) { const router = Router(); diff --git a/src/routes/site-content.ts b/src/routes/site-content.ts new file mode 100644 index 0000000..4d7e5d6 --- /dev/null +++ b/src/routes/site-content.ts @@ -0,0 +1,173 @@ +import express from "express"; +import { Multer } from "multer"; +import { Request, Response, NextFunction } from "express"; +import * as siteContentCtrl from "../controllers/site-content.controller"; + +function parseSiteContentData(req: Request, res: Response, next: NextFunction) { + if (req.body.siteContentData) { + try { + req.body.siteContentData = JSON.parse(req.body.siteContentData); + } catch { + return res + .status(400) + .json({ message: "Invalid JSON in siteContentData field" }); + } + } + next(); +} + +function parsePhotoData(req: Request, res: Response, next: NextFunction) { + if (req.body.photoData) { + try { + req.body.photoData = JSON.parse(req.body.photoData); + } catch { + return res.status(400).json({ message: "Invalid JSON in photoData field" }); + } + } + next(); +} + +function parseActionData(req: Request, res: Response, next: NextFunction) { + if (req.body.actionData) { + try { + req.body.actionData = JSON.parse(req.body.actionData); + } catch { + return res.status(400).json({ message: "Invalid JSON in actionData field" }); + } + } + next(); +} + +export default function siteContentRouter(upload: Multer) { + const router = express.Router(); + + /** + * @api {get} /site-content Get published site content + * @apiName getSiteContent + * @apiGroup SiteContent + * + * @apiSuccess {Boolean} success Request status + * @apiSuccess {Object} data Site content including actions, hero, and gallery + * @apiError (500) InternalServerError Failed to fetch site content + */ + router.get("/", siteContentCtrl.getSiteContent); + + /** + * @api {patch} /site-content Update site content + * @apiName updateSiteContent + * @apiGroup SiteContent + * + * @apiBody (FormData) {File} [image] Optional hero image file + * @apiBody (FormData) {String} siteContentData JSON string of fields: + * - adminId: string (required) + * - heroCaption?: string + * - heroAltText?: string + * + * @apiSuccess {Boolean} success Request status + * @apiSuccess {Object} data Updated site content + * @apiError (400) BadRequest Missing or invalid data + * @apiError (403) Forbidden Manager access required + * @apiError (500) InternalServerError Server error + */ + router.patch( + "/", + upload.single("image"), + parseSiteContentData, + siteContentCtrl.updateSiteContent, + ); + + /** + * @api {patch} /site-content/actions/:key Update a site action + * @apiName updateSiteAction + * @apiGroup SiteContent + * + * @apiParam (Path Params) {String} key Action key (e.g. recruitment) + * @apiBody {String} adminId ID of the manager (required) + * @apiBody {String} [label] Button label + * @apiBody {String} [url] Action URL + * @apiBody {Boolean} [isVisible] Whether the action is visible + * + * @apiSuccess {Boolean} success Request status + * @apiSuccess {Object} data Updated site content + * @apiError (400) BadRequest Missing or invalid data + * @apiError (403) Forbidden Manager access required + * @apiError (404) NotFound Site action not found + * @apiError (500) InternalServerError Server error + */ + router.patch( + "/actions/:key", + parseActionData, + siteContentCtrl.updateSiteAction, + ); + + /** + * @api {post} /site-content/gallery Add a gallery photo + * @apiName addGalleryPhoto + * @apiGroup SiteContent + * + * @apiBody (FormData) {File} image Image file + * @apiBody (FormData) {String} photoData JSON string of fields: + * - adminId: string (required) + * - caption?: string + * - altText?: string + * - sortOrder?: number + * + * @apiSuccess {Boolean} success Request status + * @apiSuccess {Object} data Updated site content + * @apiError (400) BadRequest Missing or invalid data + * @apiError (403) Forbidden Manager access required + * @apiError (500) InternalServerError Server error + */ + router.post( + "/gallery", + upload.single("image"), + parsePhotoData, + siteContentCtrl.addGalleryPhoto, + ); + + /** + * @api {patch} /site-content/gallery/:photoId Update a gallery photo + * @apiName updateGalleryPhoto + * @apiGroup SiteContent + * + * @apiParam (Path Params) {String} photoId Gallery photo ID + * @apiBody (FormData) {File} [image] Optional new image + * @apiBody (FormData) {String} photoData JSON string of fields: + * - adminId: string (required) + * - caption?: string + * - altText?: string + * - sortOrder?: number + * + * @apiSuccess {Boolean} success Request status + * @apiSuccess {Object} data Updated site content + * @apiError (400) BadRequest Missing or invalid data + * @apiError (403) Forbidden Manager access required + * @apiError (404) NotFound Gallery photo not found + * @apiError (500) InternalServerError Server error + */ + router.patch( + "/gallery/:photoId", + upload.single("image"), + parsePhotoData, + siteContentCtrl.updateGalleryPhoto, + ); + + /** + * @api {delete} /site-content/gallery/:photoId Delete a gallery photo + * @apiName deleteGalleryPhoto + * @apiGroup SiteContent + * + * @apiParam (Path Params) {String} photoId Gallery photo ID + * @apiBody {String} adminId ID of the manager performing the delete + * + * @apiSuccess {Boolean} success Request status + * @apiSuccess {String} message Deletion confirmation + * @apiError (400) BadRequest Missing or invalid data + * @apiError (403) Forbidden Manager access required + * @apiError (404) NotFound Gallery photo not found + * @apiError (500) InternalServerError Server error + */ + router.delete("/gallery/:photoId", siteContentCtrl.deleteGalleryPhoto); + + return router; +} diff --git a/src/services/emailTemplate.service.ts b/src/services/emailTemplate.service.ts new file mode 100644 index 0000000..dcd4cbe --- /dev/null +++ b/src/services/emailTemplate.service.ts @@ -0,0 +1,47 @@ + import prisma from "../db/client"; +import { ApiError } from "../utils/apiError"; + +export const createTemplate = async ( + name: string, + subject: string, + htmlBody: string, + textBody?: string, + createdById?: string, +) => { + return await prisma.emailTemplate.create({ + data: { name, subject, htmlBody, textBody, createdById }, + }); +}; + +export const listTemplates = async () => { + return await prisma.emailTemplate.findMany({ + orderBy: { createdAt: "desc" }, + }); +}; + +export const getTemplateById = async (id: number) => { + return await prisma.emailTemplate.findUnique({ where: { id } }); +}; + +export const updateTemplate = async ( + id: number, + payload: Partial<{ + name: string; + subject: string; + htmlBody: string; + textBody: string; + updatedById: string; + }>, +) => { + const exists = await prisma.emailTemplate.findUnique({ where: { id } }); + if (!exists) throw new ApiError("Template not found", 404); + + return await prisma.emailTemplate.update({ where: { id }, data: payload }); +}; + +export const deleteTemplate = async (id: number) => { + const exists = await prisma.emailTemplate.findUnique({ where: { id } }); + if (!exists) throw new ApiError("Template not found", 404); + + return await prisma.emailTemplate.delete({ where: { id } }); +}; diff --git a/src/services/member.service.ts b/src/services/member.service.ts index 804a175..6bd1466 100644 --- a/src/services/member.service.ts +++ b/src/services/member.service.ts @@ -1,5 +1,8 @@ import prisma from "../db/client"; import { ApiError } from "../utils/apiError"; +import { Role } from "../generated/prisma/client"; + +const GHOST_ALLOWED_ROLES: Role[] = [Role.ADMIN, Role.SUPER_ADMIN]; export const getUserByEmail = async(email: string) => { return await prisma.member.findUnique({ @@ -9,7 +12,7 @@ export const getUserByEmail = async(email: string) => { select: { id: true, isApproved: true, - isManager: true, + role: true, accounts: { select: { password: true @@ -23,6 +26,7 @@ export const approvedMembers = async () => { return await prisma.member.findMany({ where: { isApproved: true, + isGhosted: false, }, }); }; @@ -97,7 +101,7 @@ export const updatePassword = async(id: string, password: string) => { export const unapprovedMembers = async () => { return await prisma.member.findMany({ - where: { isApproved: false }, + where: { isApproved: false, isGhosted: false }, }); }; @@ -143,4 +147,85 @@ export const getInterviews = async (id: string) => { return await prisma.interviewExperience.findMany({ where: { memberId: id }, }); +}; + +export const updateMemberRole = async ( + superAdminId: string, + memberId: string, + newRole: Role, +) => { + // Verify the requester is a SUPER_ADMIN + const requester = await prisma.member.findUnique({ + where: { id: superAdminId }, + select: { role: true }, + }); + + if (!requester || requester.role !== Role.SUPER_ADMIN) { + throw new ApiError("Forbidden: only Super Admins can assign roles", 403); + } + + // Prevent modifying own role + if (superAdminId === memberId) { + throw new ApiError("Cannot modify your own role", 400); + } + + return await prisma.member.update({ + where: { id: memberId }, + data: { role: newRole }, + }); +}; + +/** + * Ghost or unghost a member request (Dead Zone). + * Only ADMIN and SUPER_ADMIN are allowed to perform this action. + */ +export const ghostMember = async ( + adminId: string, + memberId: string, + ghost: boolean, +) => { + // Verify requester exists and has the right role + const requester = await prisma.member.findUnique({ + where: { id: adminId }, + select: { role: true }, + }); + + if (!requester || !GHOST_ALLOWED_ROLES.includes(requester.role)) { + throw new ApiError("Forbidden: only Admins and Super Admins can ghost members", 403); + } + + // Prevent self-ghosting + if (adminId === memberId) { + throw new ApiError("Cannot ghost yourself", 400); + } + + const target = await prisma.member.findUnique({ + where: { id: memberId }, + select: { id: true }, + }); + if (!target) { + throw new ApiError("Member not found", 404); + } + + return await prisma.member.update({ + where: { id: memberId }, + data: { + isGhosted: ghost, + ghostedBy: ghost ? { connect: { id: adminId } } : { disconnect: true }, + }, + }); +}; + +/** + * Retrieve all members currently in the Dead Zone (ghosted). + */ +export const deadZoneMembers = async () => { + return await prisma.member.findMany({ + where: { isGhosted: true }, + include: { + ghostedBy: { + select: { id: true, name: true, email: true, role: true }, + }, + }, + }); }; \ No newline at end of file diff --git a/src/services/site-content.service.ts b/src/services/site-content.service.ts new file mode 100644 index 0000000..37c2119 --- /dev/null +++ b/src/services/site-content.service.ts @@ -0,0 +1,256 @@ +import { v4 as uuidv4 } from "uuid"; +import prisma from "../db/client"; +import { ApiError } from "../utils/apiError"; +import { Prisma, Role } from "../generated/prisma/client"; + +const PAGE_CONTENT_ID = 1; + +function toGalleryJson(gallery: GalleryPhoto[]): Prisma.InputJsonValue { + return gallery as unknown as Prisma.InputJsonValue; +} + +const ADMIN_ROLES: Role[] = [Role.SUPER_ADMIN, Role.ADMIN]; + +async function assertAdmin(adminId: string) { + const member = await prisma.member.findUnique({ + where: { id: adminId }, + select: { role: true }, + }); + + if (!member || !ADMIN_ROLES.includes(member.role)) { + throw new ApiError("Forbidden: admin access required", 403); + } +} + +function parseGalleryPhotos(value: unknown): GalleryPhoto[] { + if (!Array.isArray(value)) { + return []; + } + + return value + .filter( + (item): item is GalleryPhoto => + typeof item === "object" && + item !== null && + typeof (item as GalleryPhoto).id === "string" && + typeof (item as GalleryPhoto).imageUrl === "string" && + typeof (item as GalleryPhoto).sortOrder === "number", + ) + .sort((a, b) => a.sortOrder - b.sortOrder); +} + +function toSiteContentResponse( + page: { + heroImageUrl: string | null; + heroCaption: string | null; + heroAltText: string | null; + galleryPhotos: unknown; + }, + actions: { + key: string; + label: string | null; + url: string | null; + isVisible: boolean; + }[], +): SiteContentResponse { + return { + actions: actions.map(({ key, label, url, isVisible }) => ({ + key, + label, + url, + isVisible, + })), + hero: { + imageUrl: page.heroImageUrl, + caption: page.heroCaption, + altText: page.heroAltText, + }, + gallery: parseGalleryPhotos(page.galleryPhotos), + }; +} + +async function getPageContent() { + return prisma.sitePageContent.findUniqueOrThrow({ + where: { id: PAGE_CONTENT_ID }, + }); +} + +async function getActions() { + return prisma.siteAction.findMany({ + orderBy: { key: "asc" }, + }); +} + +async function buildSiteContentResponse(): Promise