{"id":28426044,"url":"https://github.com/marcolink/snapshots-manager","last_synced_at":"2026-04-28T23:31:23.674Z","repository":{"id":295132754,"uuid":"453473120","full_name":"marcolink/snapshots-manager","owner":"marcolink","description":"Track granular changes of contentful entries","archived":false,"fork":false,"pushed_at":"2025-06-02T17:03:41.000Z","size":3681,"stargazers_count":0,"open_issues_count":13,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-05T11:08:31.301Z","etag":null,"topics":["app","contentful","jsonpatch"],"latest_commit_sha":null,"homepage":"https://github.com/marcolink/snapshots-manager","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/marcolink.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}},"created_at":"2022-01-29T17:46:37.000Z","updated_at":"2025-05-27T08:43:47.000Z","dependencies_parsed_at":"2025-05-23T20:30:57.208Z","dependency_job_id":null,"html_url":"https://github.com/marcolink/snapshots-manager","commit_stats":null,"previous_names":["marcolink/snapshots-manager"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/marcolink/snapshots-manager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcolink%2Fsnapshots-manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcolink%2Fsnapshots-manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcolink%2Fsnapshots-manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcolink%2Fsnapshots-manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcolink","download_url":"https://codeload.github.com/marcolink/snapshots-manager/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcolink%2Fsnapshots-manager/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32404340,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T19:38:08.556Z","status":"ssl_error","status_checked_at":"2026-04-28T19:37:55.688Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["app","contentful","jsonpatch"],"created_at":"2025-06-05T11:08:31.402Z","updated_at":"2026-04-28T23:31:23.669Z","avatar_url":"https://github.com/marcolink.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Welcome to the Time Machine!\nA contentful app that allows you to travel back in time and see how the world has changed.\n\n![screenshot-01.png](assets/screenshot-01.png)\n\n## How it works\nThis app uses [App Events](https://www.contentful.com/developers/docs/extensibility/app-framework/app-events/) (similar to webhooks) to listen for changes of entries in the contentful space. \nWhen a change is detected, the app will create a patch for each change and store it in the data store. \nThis lets you scroll through the history of your content and see how it has evolved over time.\n\nIn context of contentful content, a patch describes the change of a value for a field and a locale. This can be represented with a JSON patch operation like this:\n\n```json\n{\n  \"op\": \"replace\",\n  \"path\": \"/fields/title/en-US\",\n  \"value\": \"hello world\"\n}\n```\nWe can also combine a set of operations into a more meaningful change:\n\n```json\n[\n  {\n    \"op\": \"replace\",\n    \"path\": \"/fields/title/en-US\",\n    \"value\": \"hello world\"\n  },\n  {\n    \"op\": \"replace\",\n    \"path\": \"/fields/amount/en-US\",\n    \"value\": 1\n  }\n]\n```\n\nWe can store metadata such as space, environment, entry, version along the patch, to later receive a stream of changes of a particular entry on a specific space environment. \n```json\n{\n  \"space\": \"\u003cspace-id\u003e\",\n  \"environment\": \"\u003cenvironment-id\u003e\",\n  \"entry\": \"\u003centry-id\u003e\",\n  \"patch\": [\n    {\n      \"op\": \"replace\",\n      \"path\": \"/fields/title/en-US\",\n      \"value\": \"hello world\"\n    }\n  ]\n}\n```\n\nIf we create a new patch entry for every event (`save`, `auto_save`, `delete`, `archive`, `unarchive`, `publish`, `unpublish`) in our system, we get the highest possible resolution (keep in mind that some of these events are not changing content, but its availability).\n\n![flow-diagram.png](assets/flow-diagram.png)\n\n### App Configuration\n#### Define Entry and Sidebar Location\n![screenshot-02.png](assets/screenshot-02.png)\n\n#### Define App Events\n![screenshot-03.png](assets/screenshot-03.png)\n\u003e Provide the public url of the app with `/api/webhook` appended to it.\n\n### App Locations in Contentful\n#### Define Entry editor location\n![screenshot-04.png](assets/screenshot-04.png)\n\n#### Define Sidebar location\n![screenshot-05.png](assets/screenshot-05.png)\n## Development\n\nRun the dev server:\n```shellscript\nnpm run dev\n```\n\nView the database:\n```shellscript\nnpm run db-view\n```\n\n### Run Locally\nYou can either run the app with a remote database, or you can spin up a local database.\n\n#### Local Database\nThis repo includes a docker compose file with a postgres database. spin up the database with the following command:\n```shellscript\ndocker compose up -d\n```\nProvide the local database url in the `.env` file or via `VITE_POSTGRES_URL` env var.:\n```\npostgres://default:pg-password@localhost:5432/time-machine\n```\n\n#### Tunnel with ngrok\nUse [ngrok](https://ngrok.com/) to tunnel the local server to the internet and make it reachable for [App Events](https://www.contentful.com/developers/docs/extensibility/app-framework/app-events/). \n\nInstall ngrok locally and authenticate with your account.\n\nThen run the following command:\n```shellscript\nngrok http http://localhost:5173\n```\nThis will provide you with the public forwarding url that you can use in the Contentful app configuration.\nDon't forget to append `/api/webhook` to the url to reach the the right endpoint on your local server.\n\n## Deployment\nThis app is build with a \"bring your own database\" setup. \nYou can provide a postgres database connection string in the `.env` file (or via `VITE_POSTGRES_URL` env var).\n\n### Drizzle Setup\nAfter setting the connection string, you must push your schema via `npx drizzle-kit push`. \nAlternatively, you can also run `npx drizzle-kit migrate` to apply the schema via migrations.\n\n### App Template\nThis app is bootstrapped with the [Contentful Remix App Template](https://github.com/contentful/apps/tree/master/examples/remix) (well, actually the template is grown out of this app).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcolink%2Fsnapshots-manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcolink%2Fsnapshots-manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcolink%2Fsnapshots-manager/lists"}