{"id":31714685,"url":"https://github.com/johnstonmatt/roorooroo-app","last_synced_at":"2026-05-05T18:40:26.231Z","repository":{"id":318454609,"uuid":"1062998256","full_name":"johnstonmatt/roorooroo-app","owner":"johnstonmatt","description":"a website monitor which updates by sms or email","archived":false,"fork":false,"pushed_at":"2025-10-07T09:27:05.000Z","size":1289,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-07T10:25:05.465Z","etag":null,"topics":["demo","deno","dogfooding","supabase"],"latest_commit_sha":null,"homepage":"https://roorooroo.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnstonmatt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-24T03:05:42.000Z","updated_at":"2025-10-07T09:27:08.000Z","dependencies_parsed_at":"2025-10-07T10:25:12.179Z","dependency_job_id":"d146539c-cb01-4dc8-88a0-a93d9dbc3331","html_url":"https://github.com/johnstonmatt/roorooroo-app","commit_stats":null,"previous_names":["johnstonmatt/roorooroo-app"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/johnstonmatt/roorooroo-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnstonmatt%2Froorooroo-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnstonmatt%2Froorooroo-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnstonmatt%2Froorooroo-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnstonmatt%2Froorooroo-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnstonmatt","download_url":"https://codeload.github.com/johnstonmatt/roorooroo-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnstonmatt%2Froorooroo-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279000711,"owners_count":26082895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["demo","deno","dogfooding","supabase"],"created_at":"2025-10-09T01:45:52.327Z","updated_at":"2025-10-09T01:45:53.246Z","avatar_url":"https://github.com/johnstonmatt.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[roorooroo.com](https://roorooroo.com)\n\n[![Next.js 14](https://img.shields.io/badge/Next.js-14-black?logo=next.js)](https://nextjs.org/)\n[![React 18](https://img.shields.io/badge/React-18-61DAFB?logo=react)](https://react.dev/)\n[![Tailwind CSS v4](https://img.shields.io/badge/Tailwind_CSS-4-38B2AC?logo=tailwindcss)](https://tailwindcss.com/)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript)](https://www.typescriptlang.org/)\n[![Supabase](https://img.shields.io/badge/Supabase-Postgres%20%2B%20Auth-3FCF8E?logo=supabase)](https://supabase.com/)\n[![Deno](https://img.shields.io/badge/Deno-2-black?logo=deno)](https://deno.com/)\n[![Hono](https://img.shields.io/badge/Hono-4-FF7E33)](https://hono.dev/)\n[![MIT License](https://img.shields.io/badge/License-MIT-yellow.svg)](#license)\n\n![list of active website monitors including a select.supabase.com monitor looking for the string 2026](/docs/img/dashboard.png)\n\nA full‑stack application composed of a static‑exported\n[Next.js 14](https://nextjs.org/) frontend and a Supabase‑backed API built as\n[Deno](https://deno.com/) Edge Functions using [Hono](https://hono.dev/). It\nprovides website “watchers” (monitors) that check a URL for a pattern and notify\nusers via email or SMS when content appears, disappears, or errors.\n\n- Frontend: Next.js 14 + React 18 +\n  [Tailwind CSS v4](https://tailwindcss.com/) +\n  [Radix UI](https://www.radix-ui.com/) primitives, TypeScript\n- Backend/API: Supabase Edge Functions (Deno v2) using Hono\n- Database/Auth/Storage: [Supabase](https://supabase.com/) (Postgres + RLS,\n  Auth, Storage, Realtime)\n- Notifications: Email ([Resend](https://resend.com/)) and SMS\n  ([Twilio](https://www.twilio.com/))\n- Scheduling: [pg_cron](https://github.com/citusdata/pg_cron) via RPC helpers\n- CI/CD: [GitHub Actions](https://github.com/features/actions) for function\n  deploys and DB ops\n\n\u003e Tip: Quick links — [frontend/](frontend/) •\n\u003e [functions/api](supabase/functions/api/) • [migrations](supabase/migrations/)\n\u003e • [workflows](.github/workflows/)\n\n---\n\n## Table of contents\n\n- [Motivation](#motivation)\n- [Limitations](#limitations)\n- [Future Releases](#future-releases)\n- [Why `/functions/v1/api` ?](#why-functionsv1api-)\n- [Repository structure](#repository-structure)\n- [Architecture](#architecture)\n- [API](#api)\n- [Environment variables](#environment-variables)\n- [Database and migrations](#database-and-migrations)\n- [Frontend notes](#frontend-notes)\n- [Deployment](#deployment)\n- [Security](#security)\n- [Roadmap](#roadmap)\n- [License](#license)\n\n## Motivation\n\nThis project has two motivations:\n\n1. Build something with as many [Supabase](https://supabase.com/) features as\n   possible for my onboarding “dogfooding” project at Supabase.\n2. Create a tool that can watch websites for changes so people don’t need to\n   waste time refreshing websites by hand.\n\n## Limitations\n\n- Users must have a `@supabase.io` email address\n- Content must exist pre‑hydration in the raw HTML (no client‑only content)\n- Does not adhere to\n  [`robots.txt`](https://developers.google.com/search/docs/crawling-indexing/robots/intro)\n\n## Future Releases\n\n- Support for Supabase Realtime\n- Support for browser automation (flexibility and reliability)\n- LLM integration for natural language watcher specifications\n- Caching and performance optimizations\n\n## Why `/functions/v1/api` ?\n\nThis repo deliberately uses an Edge Function for an API rather than a typical\nSupabase architecture that avoids an API altogether. This is mostly because I’m\non the Edge Functions team and wanted to see how far I could take it, but there\nare benefits, like portability (not relying on proprietary frontend cloud APIs)\nby shipping purely static assets to the browser.\n\n## Repository structure\n\n```text\nfrontend/\n  app/\n  components/ui/\n  lib/supabase/\n  next.config.mjs\n  package.json\nsupabase/\n  config.toml\n  functions/api/\n    index.ts\n    routes/\n    middleware/\n    lib/\n  migrations/\n.github/\n  workflows/\n```\n\n- Frontend: Next.js app with UI primitives in\n  [`components/ui`](frontend/components/ui/) and Supabase helpers in\n  [`lib/supabase`](frontend/lib/supabase/). See\n  [`next.config.mjs`](frontend/next.config.mjs).\n- Supabase: Local config [`config.toml`](supabase/config.toml), SQL migrations\n  in [`migrations/`](supabase/migrations/), and Edge Function in\n  [`functions/api`](supabase/functions/api/).\n- Workflows: GitHub Actions under [`.github/workflows/`](.github/workflows/).\n\n## Architecture\n\n```mermaid\nflowchart TD\n  A[\"Browser (Next.js static site)\"]\n  C[\"/*\"]\n  A -- HTTP --\u003e C\n  A -- HTTP --\u003e Z\n\n  J[\"create_monitor_cron_job rpc\"]\n  \n  subgraph Supabase Cloud\n    subgraph EdgeFunctions\n      subgraph /api\n        C[\"HTTP /api/:resource\"]\n        Z[\"POST /api/monitors\"]\n        M[\"POST /api/monitors/check\"]\n      end\n    end\n\n    subgraph Database\n      K[\"Tables\"]\n      D[\"pg_cron\"]\n      C -- CRUD --\u003e K\n      Z -- Invokes RPC --\u003e J\n      J -- Schedules --\u003e D\n      D -- HTTP --\u003e M\n    end\n\n  end\n\n  subgraph external\n    G[\"Twilio SMS API\"]\n    H[\"Resend Email API\"]\n    M -- Sends Email --\u003e H\n    M -- Sends SMS --\u003e G\n  end\n```\n\n- Frontend (static): Next.js 14 outputs a static site (`output: \"export\"`)\n  served by any static host. Auth/data via Supabase client SDK.\n- API: Single Supabase Edge Function named `api` exposing routes under `/api`\n  using Hono.\n- DB: RLS‑secured Postgres tables. `pg_cron` triggers monitor checks via HTTP\n  back to the `api` function.\n- Notifications: Email (Resend) and SMS (Twilio) with delivery status webhook.\n\n## API\n\nFunction name: `api` (served under `/functions/v1/api` on Supabase)\n\n| Method | Path                       | Auth                             | Notes                                     |\n| -----: | -------------------------- | -------------------------------- | ----------------------------------------- |\n|    GET | `/api/health`              | Public                           | Health check                              |\n|    GET | `/api/status`              | Public                           | Service metadata and endpoints            |\n|    GET | `/api/meta`                | Public                           | Simple metadata                           |\n|   POST | `/api/auth/login`          | Public                           | Email/password login → returns tokens     |\n|   POST | `/api/auth/signup`         | Public                           | Email/password signup; sends confirmation |\n|   POST | `/api/auth/logout`         | Public                           | Acknowledge logout                        |\n|    GET | `/api/webhooks/sms-status` | Public                           | Twilio webhook verification/health        |\n|   POST | `/api/webhooks/sms-status` | Public + signature validation    | Twilio status callbacks                   |\n|   POST | `/api/monitors/check`      | Cron only (X‑Cron‑Secret or SRK) | Invoked by cron; executes a check         |\n|    GET | `/api/monitors`            | Bearer user JWT                  | List monitors                             |\n|   POST | `/api/monitors`            | Bearer user JWT                  | Create monitor (+ schedule cron)          |\n|    PUT | `/api/monitors/:id`        | Bearer user JWT                  | Update monitor                            |\n|    GET | `/api/notifications`       | Bearer user JWT                  | List notifications (`since`, `limit`)     |\n\n\u003e Cron auth: see\n\u003e [`middleware/cron.ts`](supabase/functions/api/middleware/cron.ts). Accepts\n\u003e `X-Cron-Secret` or Supabase service‑role credentials.\n\n## Environment variables\n\nFrontend (`frontend/.env.local`)\n\n| Name                            | Required | Example                   | Notes                |\n| ------------------------------- | -------- | ------------------------- | -------------------- |\n| `NEXT_PUBLIC_SUPABASE_URL`      | Yes      | `https://xyz.supabase.co` | Supabase project URL |\n| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Yes      | `eyJhbGciOi...`           | Anonymous key        |\n| `VERCEL_ANALYTICS_ID`           | No       | `abc123`                  | Optional             |\n\nEdge Functions / API (Supabase project secrets or local `.env`)\n\n| Name                        | Required    | Notes                                  |\n| --------------------------- | ----------- | -------------------------------------- |\n| `SUPABASE_URL`              | Yes         | Project URL used by server SDK         |\n| `SUPABASE_ANON_KEY`         | Yes         | Anon key                               |\n| `SUPABASE_SERVICE_ROLE_KEY` | Yes         | Service role key (server‑side only)    |\n| `FRONTEND_URL`              | No          | e.g., `http://localhost:3000`          |\n| `PRODUCTION_FRONTEND_URL`   | No          | e.g., `https://roorooroo.app`          |\n| `LOG_LEVEL`                 | No          | `debug`                                |\n| `CRON_SECRET`               | Recommended | Shared secret for cron endpoint        |\n| `TWILIO_ACCOUNT_SID`        | If SMS      | Twilio account SID                     |\n| `TWILIO_AUTH_TOKEN`         | If SMS      | Twilio auth token                      |\n| `TWILIO_PHONE_NUMBER`       | If SMS      | Sending phone number                   |\n| `TWILIO_WEBHOOK_URL`        | No          | Public URL to receive status callbacks |\n| `RESEND_API_KEY`            | If Email    | Enables email notifications            |\n\nSupabase Auth (referenced in [`supabase/config.toml`](supabase/config.toml))\n\n| Name         | Required | Notes                               |\n| ------------ | -------- | ----------------------------------- |\n| `JWT_SECRET` | Yes      | JWT signing key managed in Supabase |\n\nGitHub Actions (repo secrets)\n\n- `SUPABASE_PROJECT_ID`, `SUPABASE_ACCESS_TOKEN`, `JWT_SECRET`\n- `DATABASE_URL` (used by the example db workflow)\n\n## Database and migrations\n\n- SQL lives in [`supabase/migrations/`](supabase/migrations/); applied via the\n  Supabase CLI.\n- Core schema: see\n  [`20240101000000_create_tables.sql`](supabase/migrations/20240101000000_create_tables.sql)\n- Cron helpers: see\n  [`20240101000004_create_cron_functions.sql`](supabase/migrations/20240101000004_create_cron_functions.sql)\n\n\u003e The example workflow [`db-push.yml`](.github/workflows/db-push.yml) expects\n\u003e `scripts/*.sql`. Prefer the migrations folder, or update the workflow to use\n\u003e `supabase db push`.\n\n## Frontend notes\n\n- Static export is enabled in [`next.config.mjs`](frontend/next.config.mjs):\n  `output: \"export\"`, `trailingSlash: true`, images unoptimized.\n- Supabase helpers live under [`frontend/lib/supabase`](frontend/lib/supabase/):\n  browser, server, and middleware utilities.\n- Scripts in [`frontend/package.json`](frontend/package.json):\n  - `pnpm dev` — dev server\n  - `pnpm build` — production build\n  - `pnpm start` — start prod server (not used for static export)\n  - `pnpm lint` — Next/ESLint\n\n## Deployment\n\n- Functions: deployed via [`fns-push.yml`](.github/workflows/fns-push.yml) using\n  Deno and `supabase/setup-cli`\n  - Requires `SUPABASE_PROJECT_ID` and `SUPABASE_ACCESS_TOKEN` secrets\n- Database: managed via migrations in CI (Supabase CLI) or manually\n- Frontend: static export can be hosted on Vercel or any static host\n\n## Security\n\n- Authenticated routes require `Authorization: Bearer \u003cSupabase user JWT\u003e`\n- Cron endpoints accept either `X-Cron-Secret` or Supabase service‑role\n  credentials\n- CORS allows only configured frontend origins — set `FRONTEND_URL` and\n  `PRODUCTION_FRONTEND_URL`\n- Store secrets in Supabase project secrets, GitHub Actions secrets, or local\n  `.env` files (never commit secrets)\n\n## Roadmap\n\n- The function deploy workflow is marked as WIP; validate before relying on it\n  for production\n- If you plan to manage DB changes via CI, align workflows to\n  `supabase/migrations/` instead of `scripts/`\n\n## License\n\nMIT (unless otherwise specified).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnstonmatt%2Froorooroo-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnstonmatt%2Froorooroo-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnstonmatt%2Froorooroo-app/lists"}