{"id":14968896,"url":"https://github.com/mrmartineau/otter","last_synced_at":"2025-04-05T02:05:59.208Z","repository":{"id":200617317,"uuid":"696948162","full_name":"mrmartineau/Otter","owner":"mrmartineau","description":"Otter is a self-hosted bookmark manager made with Next.js and Supabase with Mastodon integration.","archived":false,"fork":false,"pushed_at":"2024-12-01T20:52:28.000Z","size":5889,"stargazers_count":162,"open_issues_count":6,"forks_count":29,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-05T02:05:30.135Z","etag":null,"topics":["reactjs","supabase-auth","supabase-db","supabase-js","typescript"],"latest_commit_sha":null,"homepage":"https://otter.zander.wtf","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/mrmartineau.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}},"created_at":"2023-09-26T18:34:50.000Z","updated_at":"2025-03-25T06:31:56.000Z","dependencies_parsed_at":"2024-02-08T20:25:15.710Z","dependency_job_id":"c66f69b8-edf4-439d-89b6-005879755708","html_url":"https://github.com/mrmartineau/Otter","commit_stats":{"total_commits":253,"total_committers":1,"mean_commits":253.0,"dds":0.0,"last_synced_commit":"832c3b02cf6dea2e6af977253e3cea519664bc60"},"previous_names":["mrmartineau/otter-2","mrmartineau/otter"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrmartineau%2FOtter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrmartineau%2FOtter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrmartineau%2FOtter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrmartineau%2FOtter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrmartineau","download_url":"https://codeload.github.com/mrmartineau/Otter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247276163,"owners_count":20912288,"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","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":["reactjs","supabase-auth","supabase-db","supabase-js","typescript"],"created_at":"2024-09-24T13:40:46.684Z","updated_at":"2025-04-05T02:05:59.193Z","avatar_url":"https://github.com/mrmartineau.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n  \u003ch1\u003e\u003cimg\n        src=\"https://raw.githubusercontent.com/mrmartineau/Otter/main/public/otter-logo.svg\"\n        width=\"90\"\n        height=\"90\"\n      /\u003e\u003cbr/\u003eOtter\u003c/h1\u003e\n\n\u003e Otter is a self-hosted bookmark manager made with [Next.js](https://nextjs.org) and [Supabase](https://supabase.com) with Mastodon integration.\n\n  \u003cp\u003e\n    \u003ca\n      href=\"https://github.com/MrMartineau/Otter/blob/master/LICENSE\"\n    \u003e\n      \u003cimg\n        src=\"https://img.shields.io/badge/license-MIT-blue.svg\"\n        alt=\"Otter is released under the MIT license.\"\n      /\u003e\n    \u003c/a\u003e\n    \u003cimg\n      src=\"https://img.shields.io/badge/PRs-welcome-brightgreen.svg\"\n      alt=\"PRs welcome!\"\n    /\u003e\n    \u003ca href=\"https://main.elk.zone/toot.cafe/@zander\"\u003e\n      \u003cimg src=\"https://img.shields.io/mastodon/follow/90758?domain=https%3A%2F%2Ftoot.cafe\" alt=\"Follow @zander\" /\u003e\n    \u003c/a\u003e\n  \u003c/p\u003e\n\n  \u003cp\u003e\n    \u003ca href=\"#features\"\u003eFeatures\u003c/a\u003e •\n    \u003ca href=\"#getting-started\"\u003eGetting started\u003c/a\u003e •\n    \u003ca href=\"#docs\"\u003eDocs\u003c/a\u003e •\n    \u003ca href=\"#otter-ecosystem\"\u003eEcosystem\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n## Features\n\n- Private bookmarking app with search, tagging and filtering\n- Dark/light colour modes\n- Mastodon integration - backup of your own toots as well as your favourite toots\n- Raycast extension to search your bookmarks, view recent bookmarks and create new ones\n- Chrome extension for easy bookmarking\n- Bookmarklet\n\n### Screenshots\n\n| Feed (dark mode) \u003cbr/\u003e \u003cimg src=\"https://raw.githubusercontent.com/mrmartineau/Otter/main/screens/feed.png?raw=true\" width=\"400\" /\u003e                    | Feed (light mode) \u003cbr/\u003e \u003cimg src=\"https://raw.githubusercontent.com/mrmartineau/Otter/main/screens/feed-light.png?raw=true\" width=\"400\" /\u003e |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |\n| New bookmark \u003cbr/\u003e \u003cimg src=\"https://raw.githubusercontent.com/mrmartineau/Otter/main/screens/add-new.png?raw=true\" width=\"400\" /\u003e                     | Search \u003cbr/\u003e \u003cimg src=\"https://raw.githubusercontent.com/mrmartineau/Otter/main/screens/search.png?raw=true\" width=\"400\" /\u003e                |\n| Feed (showing tags sidebar) \u003cbr/\u003e \u003cimg src=\"https://raw.githubusercontent.com/mrmartineau/Otter/main/screens/tags-sidebar.png?raw=true\" width=\"400\" /\u003e | Toots feed \u003cbr/\u003e \u003cimg src=\"https://raw.githubusercontent.com/mrmartineau/Otter/main/screens/toots.png?raw=true\" width=\"400\" /\u003e             |\n\n## Getting started\n\n### Prerequisites\n\n- [pnpm](https://pnpm.io) - install with `npm i -g pnpm`\n- [Vercel](https://vercel.com) account and the [Vercel CLI](https://vercel.com/cli) - install with `npm i -g vercel`\n- [Supabase](https://supabase.com) account and the [Supabase CLI](https://supabase.com/docs/reference/cli/introduction) - install with `npm i -g supabase`\n- [Cloudflare](https://cloudflare.com) account (optional) - used for the page scraper and Mastodon to Supabase worker\n\n### Setup\n\n1. Fork this repo\n2. 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.\n3. Link your Supabase project to your local dev environment: `pnpm supabase:link`\n4. Seed your database with `pnpm supabase:setup`\n5. Install npm dependencies with [pnpm](https://pnpm.io): `pnpm install`\n6. Create a new project on vercel and setup env vars (see below)\n7. To allow signups, set the value of `ALLOW_SIGNUP` in `./src/constants.ts` to `true`\n8. Run the app locally using `pnpm dev`\n9. Visit [`http://localhost:5678`](http://localhost:5678) and create an account\n\n### Env vars\n\nSet 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.\n\n```bash\n# Find these in your Supabase project settings \u003e API\nNEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co\nNEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key\nSUPABASE_SERVICE_KEY=your-service-key # only needed for testing APIs using the `*.rest` files\n\nPERSONAL_MASTODON_ACCESS_TOKEN=your-personal-app-mastodon-access-token\nBOT_MASTODON_ACCESS_TOKEN=your-bot-app-mastodon-access-token\nOTTER_API_TOKEN=your-otter-api-token\n```\n\n### Docs\n\n### API Endpoints\n\nInteractive API docs can be found in the various `*.rest` files in the `/app/api` directory.\n\n- `POST /api/new` - create new item in Otter\n- `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\n- `GET /api/bookmarks` - returns all bookmarks\n\u003c!-- - `GET /api/bookmarks/:id` - returns a single bookmark --\u003e\n- `GET /api/search?searchTerm=zander` - search bookmark\n- `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) \u0026 [@zander@toot.cafe](https://toot.cafe/@zander)). It only sends a toot if the bookmark has the `public` column set to `true`.\n\n### Mastodon integration\n\nOtter 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.\n\nThe trigger function below uses an environment variable in the `Authorization` header to ensure only the owner of the Otter instance can call the endpoint.\n\n```sql\ncreate trigger \"toot-otter-items\"\nafter insert\nor\nupdate on bookmarks for each row\nexecute function supabase_functions.http_request (\n  'https://{your-otter-instance}/api/toot',\n  'POST',\n  -- replace {OTTER_API_TOKEN} with your own token\n  '{\"Content-type\":\"application/json\",\"Authorization\":\"{OTTER_API_TOKEN}\"}',\n  '{}',\n  '1000'\n);\n```\n\nTODO:\n\n- [ ] document the PostgreSQL trigger function that calls the `/api/toot` endpoint\n\n### Bookmarks\n\n#### Adding new bookmark types\n\n1. Add the new type to the types enum `ALTER TYPE type ADD VALUE '???';`\n2. Run `pnpm run supabase:types` to update the TypeScript types\n3. Add a new `case` to the `TypeToIcon` component\n4. Add a new `TypeRadio` component to the `BookmarkForm` component\n\n## Otter ecosystem\n\nI use various other tools to make Otter even better:\n\n- [Raycast extension](https://www.raycast.com/mrmartineau/otter) (on the Raycast extension store)\n- [Chrome extension](https://github.com/mrmartineau/otter-extension) (not currently on the Chrome webstore)\n- [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\n- [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\n- [Mastodon to Supabase Cloudflare worker](https://github.com/mrmartineau/mastodon-to-supabase) used to backup my Mastodon toots to Supabase\n\n## License\n\n[MIT](https://choosealicense.com/licenses/mit/) © [Zander Martineau](https://zander.wtf)\n\n\u003e Made by Zander • [zander.wtf](https://zander.wtf) • [GitHub](https://github.com/mrmartineau/) • [Mastodon](https://main.elk.zone/toot.cafe/@zander)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrmartineau%2Fotter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrmartineau%2Fotter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrmartineau%2Fotter/lists"}