Cursorist Docs
Supabase

Edge Functions

Supabase Edge Functions for plugin publishing, metrics, and install tracking.

Cursorist uses Supabase Edge Functions for server-side operations that need privileged database access — publishing plugins to GitHub, tracking installs, and gathering admin metrics. They run on Deno at the edge and share a common set of utilities for auth, CORS, and response formatting.

Structure

supabase/functions/
├── _shared/
│   ├── auth.ts        # Admin authentication helper
│   ├── cors.ts        # CORS headers + json/error response helpers
│   ├── github.ts      # GitHub API (create/delete files in repo)
│   └── supabase.ts    # Supabase admin client (service role)
├── publish-rule/      # Publish a plugin to GitHub
├── unpublish-rule/    # Remove a plugin from GitHub
├── increment-install/ # Bump plugin install count
└── admin-metrics/     # Platform-wide stats

Shared Utilities

_shared/supabase.ts

Creates a single Supabase client with the service role key. This bypasses RLS so functions can read/write any table.

import { createClient } from "@supabase/supabase-js";

export const supabaseAdmin = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);

_shared/auth.ts

Extracts the admin check that all protected functions need. Returns the user ID on success or an error Response on failure.

import { supabaseAdmin } from "./supabase.ts";
import { error } from "./cors.ts";

export async function requireAdmin(req: Request): Promise<\{ userId: string \} | Response> \{
  const token = req.headers.get("Authorization")?.replace("Bearer ", "");
  if (!token) return error("Unauthorized", 401);

  const \{ data: \{ user \} \} = await supabaseAdmin.auth.getUser(token);
  if (!user) return error("Invalid token", 401);

  const \{ data: profile \} = await supabaseAdmin
    .from("users").select("is_admin").eq("id", user.id).single();

  if (!profile?.is_admin) return error("Admin access required", 403);
  return \{ userId: user.id \};
\}

_shared/cors.ts

CORS headers plus json() and error() helpers that every function uses, so you never repeat the Response boilerplate.

_shared/github.ts

Wraps the GitHub Contents API to create, update, and delete files in the cursorist repository. Used by publish-rule and unpublish-rule to push plugin JSON to the repo.

Functions

publish-rule

Auth: Admin only | Method: POST

Publishes a plugin to GitHub by:

  1. Verifying admin auth via requireAdmin()
  2. Fetching the plugin, its latest stable version, and assets
  3. Building a JSON payload and pushing it to the repo via GitHub API
  4. Marking the plugin as is_published = true in the database

Request body:

\{ "plugin_id": "uuid" \}

unpublish-rule

Auth: Admin only | Method: POST

Reverses a publish by deleting the plugin JSON from GitHub and setting is_published = false.

Request body:

\{ "plugin_id": "uuid" \}

increment-install

Auth: None (public) | Method: POST

Calls the increment_plugin_install_count database function to atomically bump a plugin's install count. Called from the frontend when a user installs a plugin.

Request body:

\{ "plugin_id": "uuid" \}

admin-metrics

Auth: Admin only | Method: GET

Returns platform-wide metrics in a single response:

\{
  "users": \{ "total": 42, "new_last_7_days": 5 \},
  "organizations": \{ "total": 8 \},
  "teams": \{ "total": 15 \},
  "plugins": \{ "total": 23, "published": 18, "top": [...] \}
\}

Deployment

Deploy all functions at once:

supabase functions deploy publish-rule
supabase functions deploy unpublish-rule
supabase functions deploy increment-install
supabase functions deploy admin-metrics

Set the GitHub token secret (needed for publish/unpublish):

supabase secrets set GITHUB_TOKEN=ghp_your_token_here

Calling from the Frontend

import { createClient } from "@/lib/supabase/client";

const supabase = createClient();

await supabase.functions.invoke("publish-rule", \{
  body: \{ plugin_id: "..." \},
\});

await supabase.functions.invoke("increment-install", \{
  body: \{ plugin_id: "..." \},
\});

const \{ data \} = await supabase.functions.invoke("admin-metrics");