{"id":31932775,"url":"https://github.com/flohoss/mittagskarte","last_synced_at":"2026-04-13T20:01:36.053Z","repository":{"id":314232693,"uuid":"1054674624","full_name":"flohoss/mittagskarte","owner":"flohoss","description":"A lightweight web application for recording and displaying daily lunch specials for restaurants and butcher shops. Built in Go and Vue with Tailwind CSS, it provides simple management and an attractive presentation of daily menus.","archived":false,"fork":false,"pushed_at":"2026-04-02T09:15:25.000Z","size":7775,"stargazers_count":2,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-03T09:38:30.224Z","etag":null,"topics":["crawling","food","golang","htmlx","playwright","templ"],"latest_commit_sha":null,"homepage":"https://schniddzl.de/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flohoss.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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-11T07:12:25.000Z","updated_at":"2026-04-02T09:15:31.000Z","dependencies_parsed_at":"2025-09-11T10:47:27.090Z","dependency_job_id":"b9880592-72d7-4539-b027-96d132414aa8","html_url":"https://github.com/flohoss/mittagskarte","commit_stats":null,"previous_names":["flohoss/mittagskarte"],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/flohoss/mittagskarte","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flohoss%2Fmittagskarte","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flohoss%2Fmittagskarte/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flohoss%2Fmittagskarte/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flohoss%2Fmittagskarte/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flohoss","download_url":"https://codeload.github.com/flohoss/mittagskarte/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flohoss%2Fmittagskarte/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31768649,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-13T15:25:13.801Z","status":"ssl_error","status_checked_at":"2026-04-13T15:25:09.162Z","response_time":93,"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":["crawling","food","golang","htmlx","playwright","templ"],"created_at":"2025-10-14T05:24:28.397Z","updated_at":"2026-04-13T20:01:36.046Z","avatar_url":"https://github.com/flohoss.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mittagskarte\n\nMittagskarte fetches, uploads, converts, and serves lunch menus for restaurants.\n\nThe current stack is:\n\n- Go backend with PocketBase\n- Vue 3 frontend built with Vite\n- PocketBase collections and migrations for restaurant data\n- Playwright, MuPDF, and ImageMagick for scraping and file conversion\n\n## Screenshots\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"images/dark.webp\" width=\"800\" /\u003e\n  \u003cimg src=\"images/light.webp\" width=\"800\" /\u003e\n\u003c/p\u003e\n\n## Architecture\n\nThe app is split into two parts during development:\n\n- `backend/`: PocketBase app, custom API routes, migrations, scraping pipeline\n- `frontend/`: Vue application\n\nIn production, the Vue app is built to `dist` and served directly by the Go/PocketBase server.\n\nRoutes:\n\n- `/`: Vue app entrypoint (`index.html`)\n- `/assets/*`: built frontend assets from Vite\n- `/static/*`: static frontend assets\n- `/_`: PocketBase dashboard and internal UI\n- `/api/*`: PocketBase API plus custom backend endpoints\n- `/health`: simple health endpoint returning `.`\n\n## PocketBase Data Model\n\nThe application schema is managed through PocketBase migrations in `backend/migrations`.\n\nCurrent app collections:\n\n- `restaurants`\n- `selectors`\n- `menus`\n\nAll three collections are publicly listable and viewable.\n\nCollection relationships:\n\n- each restaurant can reference multiple `selectors` via `navigate`\n- each menu belongs to exactly one restaurant (`menus.restaurant`)\n- each restaurant stores a rolling list of latest menu ids in `restaurants.menus`\n\nMenu retention is limited by the backend setting `MAX_AMOUNT_OF_MENUS` (default: `10`).\n\nThe backend also uses PocketBase auth for protected operations.\n\nProtected custom endpoints:\n\n- `POST /api/restaurants/scrape`\n\n## How It Works\n\nRestaurant menus can be obtained in three ways:\n\n- scrape content from websites\n- download files directly\n- upload files manually\n\nFiles are normalized by the backend and stored in PocketBase-managed records. The app supports PDF and image sources and generates browser-friendly menu images for the frontend.\n\nFor each menu create event, the backend:\n\n- converts input files to optimized `webp`\n- stores calculated dimensions (`width`, `height`, `landscape`)\n- computes a content hash and rejects unchanged uploads/scrapes\n- prepends the new menu id to the restaurant `menus` relation\n- removes older menu records beyond `MAX_AMOUNT_OF_MENUS`\n\n## Selectors and Examples\n\nIn the PocketBase model, each restaurant can reference multiple selector records through the relation field `navigate`.\n\nEach selector record contains:\n\n- `locator`: CSS selector or XPath\n- `attribute`: optional attribute to read (for example `src` or `href`)\n- `style`: optional CSS injected before interaction, useful to hide overlays or cookie banners\n\nTypical usage pattern:\n\n- First selector steps are used for navigation (clicks)\n- Last selector step is used for extraction or screenshot logic\n\nExample selector list:\n\n```yaml\nnavigate:\n\t- locator: \".cookie-accept-btn\"\n\t\tattribute: \"\"\n\t\tstyle: \"\"\n\t- locator: \"a.mittag-link\"\n\t\tattribute: \"href\"\n\t\tstyle: \"\"\n```\n\n### Example 1: Direct Download (method = download)\n\nUse `method = download` and set the first selector locator to the direct file URL.\n\n```yaml\nrestaurant:\n\tmethod: \"download\"\n\tcontent_type: \"pdf\"\n\tnavigate:\n\t\t- locator: \"https://example.com/mittagskarte.pdf\"\n\t\t\tattribute: \"\"\n\t\t\tstyle: \"\"\n```\n\n### Example 2: Image Download via Attribute (method = scrape)\n\nSet `attribute` on the last selector to read an image or link source.\n\n```yaml\nrestaurant:\n\tmethod: \"scrape\"\n\tcontent_type: \"image\"\n\tnavigate:\n\t\t- locator: \".menu-image img\"\n\t\t\tattribute: \"src\"\n\t\t\tstyle: \"\"\n```\n\n### Example 3: PDF Link via XPath (method = scrape)\n\nXPath works as locator input as well.\n\n```yaml\nrestaurant:\n\tmethod: \"scrape\"\n\tcontent_type: \"pdf\"\n\tnavigate:\n\t\t- locator: \"//a[contains(text(), 'Mittagstisch')]\"\n\t\t\tattribute: \"href\"\n\t\t\tstyle: \"\"\n```\n\n### Example 4: HTML Screenshot with Injected Style (method = scrape)\n\nFor HTML content, the scraper can screenshot the selected element. Optional style can hide fixed headers or navigation.\n\n```yaml\nrestaurant:\n\tmethod: \"scrape\"\n\tcontent_type: \"html\"\n\tnavigate:\n\t\t- locator: \"p.paragraph-mittagstisch-right-corona\"\n\t\t\tattribute: \"\"\n\t\t\tstyle: \".w-nav { display: none !important; }\"\n```\n\n### Dynamic Date Placeholders in Locators\n\nYou can use date placeholders in selector locators:\n\n```yaml\nlocator: \"//div[@class='calendar']//span[text()='{{date(format=02.01.2006, day=fr, offset=-1)}}']\"\n```\n\nSupported arguments:\n\n- `format`: Go date format, for example `02.01.2006`\n- `lang`: language, for example `en` or `de`\n- `day`: target weekday, for example `monday` or `fr`\n- `offset`: week offset, for example `-1`, `0`, `1`\n- `upper`: uppercase output toggle\n\n## Deployment\n\nThe production image is built from the repository root and includes:\n\n- the compiled Go/PocketBase binary\n- the built Vue `dist` output\n- runtime dependencies required for Playwright, MuPDF, and ImageMagick\n\nBuild the release image with the same target architecture used in CI:\n\n```sh\ndocker build --platform=linux/amd64 -t mittagskarte .\n```\n\nRun it:\n\n```sh\ndocker run --rm -p 8090:8090 mittagskarte\n```\n\nThen open http://localhost:8090.\n\nImportant runtime paths:\n\n- PocketBase data directory: `data/pb`\n- frontend bundle served by backend: `dist`\n- temporary download/processing files: `/tmp/downloads` (ephemeral in container lifecycle)\n\n## Development\n\nLocal development uses Docker Compose with separate backend and frontend containers.\n\nStart the stack:\n\n```sh\ndocker compose up --build --force-recreate\n```\n\nDevelopment URLs:\n\n- frontend: `http://localhost:5173`\n- backend: `http://localhost:8090`\n- PocketBase dashboard: `http://localhost:8090/_/`\n\nCompose services:\n\n- `backend`: PocketBase backend with hot reload via `air`\n- `frontend`: Vite dev server\n- `go`: helper container for Go commands\n- `yarn`: helper container for frontend package commands\n\n## Common Commands\n\nInstall or update frontend dependencies:\n\n```sh\ndocker compose run --rm yarn install --frozen-lockfile\ndocker compose run --rm yarn upgrade --latest\n```\n\nUpdate Go modules:\n\n```sh\ndocker compose run --rm go get -u ./...\ndocker compose run --rm go mod tidy\n```\n\nBuild the frontend manually:\n\n```sh\ncd frontend\nyarn build\n```\n\n## Notes\n\n- The backend expects a built frontend bundle in `dist` when serving the production app.\n- The repository currently targets `linux/amd64` for Docker builds because of native library dependencies used by `go-fitz` and ImageMagick.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflohoss%2Fmittagskarte","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflohoss%2Fmittagskarte","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflohoss%2Fmittagskarte/lists"}