尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Collaborative mushroom foraging map for Utah
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
World Mushroom Foraging is a guest-first field companion with two distinct collections. The mushroom side is a worldwide public field desk for recent reviewed observations and identification education. The herbal side, The Verdant Hours, is a daylight field almanac for learning harvest windows, reading local weather, and keeping a private seasonal record. One optional account serves both experiences.查看完整 README ↗World Mushroom Foraging is a guest-first field companion with two distinct collections. The mushroom side is a worldwide public field desk for recent reviewed observations and identification education. The herbal side, The Verdant Hours, is a daylight field almanac for learning harvest windows, reading local weather, and keeping a private seasonal record. One optional account serves both experiences.
The project takes inspiration from the usefulness of community location catalogues such as Rockhounding.org while treating sensitive biological locations more carefully. Exact coordinates remain in the contributor's logbook by default; the public map receives a stable point shifted roughly 1 to 2.5 miles away.
A map observation is not an identification. Never eat a wild mushroom based on this site. Confirm with a qualified local expert, follow current land rules, and call Poison Control after a suspected exposure.
Product and interface decisions are documented in PRODUCT.md and DESIGN.md. Research and planning for the mushroom identification education section live in docs/education/.
frontend/ React, Vite, Tailwind CSS, Mapbox GL JS
backend/app/ FastAPI application and SQLAlchemy models
backend/alembic/ Database migrations
backend/crawler/ iNaturalist ingestion
data-pipeline/ iNaturalist open-data squares, growing zones, and ID index (see its README)
backend/scripts/ Seed and end-to-end API smoke tests
.github/workflows/ Frontend and backend CI
AI assistants and automation have their own front door: /llms.txt, a markdown twin of every page (same URL plus .md), open CC BY 4.0 data at /data, and a read-only MCP server at /mcp. The build writes these from the same content as the pages (frontend/scripts/build-agent-access.mjs). Known bots are logged by frontend/middleware.js into the agent_traffic table, and a weekly Action exports a summary to docs/agent-review/. Plan, decisions and review log: docs/planning/agent-access-2026-10.md. Extra variables: AGENT_LOG_SECRET on both Vercel projects, and a CRON_SECRET GitHub Actions secret for the weekly export.
cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
alembic upgrade head
python -m scripts.seed --demo
uvicorn app.main:app --reload
--demo adds illustrative Utah sightings, community reports, events and clubs for local development. They are not real reviewed records, so the production seed below runs without it and only writes the species catalogue and resource guides.
The local default is sqlite:///./utah_forage_map.db. Set DATABASE_URL to a PostgreSQL URL in production. Development creates missing tables for convenience; production requires alembic upgrade head.
cd frontend
cp .env.example .env
npm ci
npm run dev
Set a valid public VITE_MAPBOX_TOKEN in frontend/.env (or the ignored frontend/.env.local); both field maps and place search require it. Vite proxies /api to http://127.0.0.1:8000, so authentication stays same-origin. Fungi observations come from that backend's database, which must contain imported records to show markers.
Vite serves /herb-observations and /visitor-country through the same handlers used in production, in both development and npm run preview. Herb records load directly from the public iNaturalist service through the local handler. Country detection uses Vercel's country header when available; localhost opens the world view unless a country header is provided by a development proxy. Restart Vite after changing map configuration.
Open http://127.0.0.1:5173.
The home page (/) is the Fungi Library. The observation map lives at /map; fungi logos and collection switches return to the library. /learn redirects home, while /learn/species/* and /learn/foraging/* retain their URLs. Old root links with map filters or account-action tokens redirect to /map with their query parameters preserved. The herb landing page remains /herbs.
[ ] private repo + main protected + CI [x] dependabot + lockfile [ ] advisors clean (or exceptions noted) [ ] backups/PITR confirmed [ ] PostHog + exceptions on [ ] uptime incl. checkout URL [ ] privacy / terms / contact (+refund/shipping/disclaimers if selling) [ ] support@ email works [ ] domain auto-renew on [ ] revenue lands in business account [ ] sales tax configured (physical goods)
cd frontend
npm run lint
npm run build
npm audit
cd ../backend
python -m scripts.auth_smoke
python -m scripts.import_smoke
python -m compileall app crawler scripts
The API smoke tests cover registration, verification, private/public coordinate separation, multi-photo records, structured review, fungi and herbal watch zones, herbal inventory and wishes, follows, regional summaries, seasonal caching, saved-place revisits, recovery, session revocation, owner edits, account deletion, import updates, and retired source records.
Run the importer manually:
cd backend
python -m crawler.inaturalist
Only research-grade, wild, geolocated worldwide observations from the rolling 90-day window and matching catalogue species are imported. Each cycle reconciles the complete matching iNaturalist result set: new observations are inserted, changed locations and found dates are updated, and records that leave the current research-grade window are retired from the public map. crawled_sources.source_url is unique, making repeat runs safe. Imported map points still use public approximation.
Two production crons call the importer with Authorization: Bearer $CRON_SECRET. GET /api/cron/inaturalist (daily, 11:00 UTC) runs the incremental import of recently updated observations. GET /api/cron/inaturalist-reconcile (daily, 12:00 UTC) runs the full reconciliation: persisted sync state starts a new cycle only when 14 days have elapsed, processes at most 3,600 records per invocation, and resumes the following day until the worldwide result set is complete. This keeps each invocation bounded and allows retry after failure. The importer follows iNaturalist's recommended 200-record pages, one request per second, cursor pagination, and identifying user agent. Separate alert jobs evaluate mushroom watches weekly and herbal watches daily; an email is sent only when all signals a person selected are aligned.
The intended deployment is two Vercel projects:
The existing Vercel project names and URLs remain stable during the global transition:
utah-forage-api uses backend/, FastAPI zero configuration, a Vercel-managed Neon PostgreSQL database, and the scheduled import.utah-forage-map uses frontend/ and rewrites /api/* to the backend project, preserving first-party session cookies.Required backend variables:
DATABASE_URL
SECRET_KEY
LOCATION_PRIVACY_SECRET
CRON_SECRET
ENVIRONMENT=production
APP_URL
CORS_ORIGINS
ADMIN_EMAILS
RESEND_API_KEY
EMAIL_FROM
Optional backend variables for photo ID suggestions in Suggest an ID:
ANTHROPIC_API_KEY # enables the photo button; without it the helper uses field marks only
IDENTIFY_PHOTO_MODEL # default claude-opus-5
IDENTIFY_PHOTO_HOURLY_LIMIT # checks per network address per hour, default 10
IDENTIFY_PHOTO_DAILY_LIMIT # checks across the whole site per day, default 300
The browser resizes each photo to at most 1024 px and redraws it, which drops EXIF data such as GPS position. The API sends it once to Claude with the fixed catalogue list and keeps no copy. Declined requests are retried on Anthropic's default fallback model (fallbacks: "default").
Run migrations and seed once against the production database before the first release:
cd backend
DATABASE_URL="$DATABASE_URL" alembic upgrade head
DATABASE_URL="$DATABASE_URL" python -m scripts.seed
Never commit .env files, database URLs, Mapbox tokens, email keys, or generated session secrets.
Google Ads conversion tracking, optional Google Analytics 4, and Google AdSense are wired into the frontend but ship inactive. No third-party script loads and no ads.txt is generated until the matching VITE_* variables are configured on the utah-forage-map Vercel project, so nothing changes for visitors until you switch it on.
Frontend variables (all optional):
VITE_GOOGLE_ADS_ID # AW-XXXXXXXXXX, loads gtag.js for Google Ads
VITE_GOOGLE_ADS_SIGNUP_LABEL # conversion label fired on account signup
VITE_GOOGLE_ADS_SUBMIT_LABEL # conversion label fired on sighting submission
VITE_GA_MEASUREMENT_ID # G-XXXXXXXXXX, optional GA4 through the same tag
VITE_ADSENSE_CLIENT # ca-pub-XXXXXXXXXXXXXXXX, loads AdSense and ads.txt
The tag helpers live in frontend/src/lib/googleTag.js and load once from frontend/src/main.jsx. Conversions fire from the register and sighting-submission mutations and stay off until their label is set, so each conversion is activated individually.
To turn on conversion tracking:
AW-…) and label.VITE_GOOGLE_ADS_ID plus VITE_GOOGLE_ADS_SIGNUP_LABEL / VITE_GOOGLE_ADS_SUBMIT_LABEL and redeploy.To turn on AdSense:
ca-pub-…).VITE_ADSENSE_CLIENT and redeploy. The build writes dist/ads.txt from that ID (Vercel serves it at /ads.txt), and the AdSense loader is included on every page.AdSlot component (frontend/src/components/AdSlot.jsx) using a slot ID from AdSense: <AdSlot slot="1234567890" />.Consent Mode v2 starts with ad storage, ad user data, ad personalization and analytics storage all denied. Optional analytics (Google Analytics and PostHog) load only after a visitor chooses Allow analytics in the consent control (frontend/src/components/AnalyticsConsent.jsx); advertising storage and personalization stay denied either way.
Public:
GET /healthGET /api/data-status (observation freshness)GET /api/open-data/regional-signal (aggregated 90-day field signal per region, counts only)GET /api/speciesGET /api/sightings (supports recency and world-coordinate bounds)GET /api/sightings/{id}/recordGET /api/regions, /api/regions/{slug}GET /api/seasonalityGET /api/community/findsGET /api/community/eventsGET /api/community/clubsGET /api/resourcesAccounts:
POST /api/auth/register, /login, /logoutGET /api/auth/me (204 with an empty body when there is no active session)POST /api/auth/verify-email, /verification/resendPOST /api/auth/password/forgot, /password/resetGET /api/account/logbook, /saved, /alerts, /sessionsPATCH|DELETE /api/account/logbook/{id}POST|PATCH|DELETE /api/account/savedPOST|PATCH|DELETE /api/account/alertsDELETE /api/accountContribution and moderation:
POST /api/sightingsPOST /api/sightings/{id}/verificationsGET|PATCH /api/moderation/sightingsGET /api/cron/inaturalist, /api/cron/inaturalist-reconcile, /api/cron/alerts, /api/cron/herb-alerts (bearer CRON_SECRET)Public sighting responses omit owner IDs, hide private observations, exclude unreviewed community submissions, and transform approximate coordinates deterministically.