|  | 
| 6 | 6 |         height="90" | 
| 7 | 7 |       /><br/>Otter</h1> | 
| 8 | 8 | 
 | 
| 9 |  | -> Otter is a self-hosted bookmark manager made with [Next.js](https://nextjs.org) and [Supabase](https://supabase.com) with Mastodon integration. | 
|  | 9 | +> Otter is a self-hosted bookmark manager made with React and [Supabase](https://supabase.com) and hosted on [Cloudflare](https://cloudflare.com) | 
| 10 | 10 | 
 | 
| 11 | 11 |   <p> | 
| 12 | 12 |     <a | 
|  | 
| 55 | 55 | ### Prerequisites | 
| 56 | 56 | 
 | 
| 57 | 57 | - [pnpm](https://pnpm.io) - install with `npm i -g pnpm` | 
| 58 |  | -- [Vercel](https://vercel.com) account and the [Vercel CLI](https://vercel.com/cli) - install with `npm i -g vercel` | 
| 59 | 58 | - [Supabase](https://supabase.com) account and the [Supabase CLI](https://supabase.com/docs/reference/cli/introduction) - install with `npm i -g supabase` | 
| 60 | 59 | - [Cloudflare](https://cloudflare.com) account (optional) - used for the page scraper and Mastodon to Supabase worker | 
| 61 | 60 | 
 | 
| 62 |  | -### Setup | 
| 63 |  | - | 
| 64 |  | -1. Fork this repo | 
| 65 |  | -2. Go to [database.new](https://database.new) and create a new [Supabase](https://supabase.com) project. You will need the project ID (found in the project settings page) and the the database password for the next step. | 
| 66 |  | -3. Link your Supabase project to your local dev environment: `pnpm supabase:link` | 
| 67 |  | -4. Seed your database with `pnpm supabase:setup` | 
| 68 |  | -5. Install npm dependencies with [pnpm](https://pnpm.io): `pnpm install` | 
| 69 |  | -6. Create a new project on vercel and setup env vars (see below) | 
| 70 |  | -7. To allow signups, set the value of `ALLOW_SIGNUP` in `./src/constants.ts` to `true` | 
| 71 |  | -8. Run the app locally using `pnpm dev` | 
| 72 |  | -9. Visit [`http://localhost:5678`](http://localhost:5678) and create an account | 
| 73 |  | - | 
| 74 |  | -### Env vars | 
| 75 |  | - | 
| 76 |  | -Set up the following env vars using either the Vercel CLI or through the Vercel project settings. Once they are added run `vc env pull` to pull them down to your local dev environment. | 
| 77 |  | - | 
| 78 |  | -```bash | 
| 79 |  | -# Find these in your Supabase project settings > API | 
| 80 |  | -NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co | 
| 81 |  | -NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key | 
| 82 |  | -SUPABASE_SERVICE_KEY=your-service-key # only needed for testing APIs using the `*.rest` files | 
| 83 |  | - | 
| 84 |  | -PERSONAL_MASTODON_ACCESS_TOKEN=your-personal-app-mastodon-access-token | 
| 85 |  | -BOT_MASTODON_ACCESS_TOKEN=your-bot-app-mastodon-access-token | 
| 86 |  | -OTTER_API_TOKEN=your-otter-api-token | 
| 87 |  | -``` | 
| 88 |  | - | 
| 89 |  | -### Docs | 
| 90 |  | - | 
| 91 |  | -### API Endpoints | 
| 92 |  | - | 
| 93 |  | -Interactive API docs can be found in the various `*.rest` files in the `/app/api` directory. | 
| 94 |  | - | 
| 95 |  | -- `POST /api/new` - create new item in Otter | 
| 96 |  | -- `GET /api/new?url=https://example.com` - quick create new item in Otter. Pass in a `url` query param and it will create a new item with that URL and includes its metadata too | 
| 97 |  | -- `GET /api/bookmarks` - returns all bookmarks | 
| 98 |  | -<!-- - `GET /api/bookmarks/:id` - returns a single bookmark --> | 
| 99 |  | -- `GET /api/search?searchTerm=zander` - search bookmark | 
| 100 |  | -- `POST /api/toot` - A PostgreSQL trigger function calls this endpoint anytime a bookmark is created or edited which then creates a new toot on two of my Mastodon accounts ([@otterbot@botsin.space](https://botsin.space/@otterbot) & [@zander@toot.cafe](https://toot.cafe/@zander)). It only sends a toot if the bookmark has the `public` column set to `true`. | 
| 101 |  | - | 
| 102 |  | -### Mastodon integration | 
| 103 |  | - | 
| 104 |  | -Otter has the ability to auto-toot to 2 Mastodon accounts when a new bookmark is created or edited. This is done via a PostgreSQL trigger function that calls the `/api/toot` endpoint. | 
| 105 |  | - | 
| 106 |  | -The trigger function below uses an environment variable in the `Authorization` header to ensure only the owner of the Otter instance can call the endpoint. | 
| 107 |  | - | 
| 108 |  | -```sql | 
| 109 |  | -create trigger "toot-otter-items" | 
| 110 |  | -after insert | 
| 111 |  | -or | 
| 112 |  | -update on bookmarks for each row | 
| 113 |  | -execute function supabase_functions.http_request ( | 
| 114 |  | -  'https://{your-otter-instance}/api/toot', | 
| 115 |  | -  'POST', | 
| 116 |  | -  -- replace {OTTER_API_TOKEN} with your own token | 
| 117 |  | -  '{"Content-type":"application/json","Authorization":"{OTTER_API_TOKEN}"}', | 
| 118 |  | -  '{}', | 
| 119 |  | -  '1000' | 
| 120 |  | -); | 
| 121 |  | -``` | 
| 122 |  | - | 
| 123 |  | -TODO: | 
| 124 |  | - | 
| 125 |  | -- [ ] document the PostgreSQL trigger function that calls the `/api/toot` endpoint | 
| 126 |  | - | 
| 127 |  | -### Bookmarks | 
| 128 |  | - | 
| 129 |  | -#### Adding new bookmark types | 
| 130 |  | - | 
| 131 |  | -1. Add the new type to the types enum `ALTER TYPE type ADD VALUE '???';` | 
| 132 |  | -2. Run `pnpm run supabase:types` to update the TypeScript types | 
| 133 |  | -3. Add a new `case` to the `TypeToIcon` component | 
| 134 |  | -4. Add a new `TypeRadio` component to the `BookmarkForm` component | 
| 135 |  | - | 
| 136 | 61 | ## Otter ecosystem | 
| 137 | 62 | 
 | 
| 138 | 63 | I use various other tools to make Otter even better: | 
| 139 | 64 | 
 | 
| 140 | 65 | - [Raycast extension](https://www.raycast.com/mrmartineau/otter) (on the Raycast extension store) | 
| 141 | 66 | - [Chrome extension](https://github.com/mrmartineau/otter-extension) (not currently on the Chrome webstore) | 
| 142 | 67 | - [Apple Shortcut](https://github.com/mrmartineau/Otter/blob/main/public/Add%20to%20Otter.shortcut) - download this shortcut and update your Otter instance URL within it. Then you can add it to your iOS share sheet and quickly add new bookmarks to Otter | 
| 143 |  | -- [Page scraper Cloudflare worker](https://github.com/mrmartineau/cloudflare-worker-scraper) used to scrape the metadata of a URL. This is used when adding new bookmarks to Otter | 
| 144 | 68 | - [Mastodon to Supabase Cloudflare worker](https://github.com/mrmartineau/mastodon-to-supabase) used to backup my Mastodon toots to Supabase | 
| 145 | 69 | 
 | 
| 146 | 70 | ## License | 
|  | 
0 commit comments