Cosme Check décode les ingrédients, vérifie les promesses marketing et te dit si le produit te correspond vraiment. Application gratuite sur iOS et Android.
翻譯準備中
生活風格TypeScript · HTMLGitHub @lespassiones上線中營運者未確認
試用過了嗎? 點一下就好,不需要登入。
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Application qui décode les cosmétiques : composition, promesses marketing, ingrédients clefs. 15 722 ingrédients indexés, classés par tolérance (vert / jaune / orange / rouge), avec autocomplete fuzzy et fiches détaillées. Stack : Next.js 15 + React 19 + Tailwind CSS 3 + Supabase free tier. Aucun service payant. Light mode strict. Le site démarre sur http://localhost:3000. L'application est immédiatement opérationnelle sur les 15 722 ingrédients déjà chargés dans Supabase. Le fichier .env contient les identifiants Supabase : Le scraping est séparé du site. Quatre phases :檢視完整 README ↗
Application qui décode les cosmétiques : composition, promesses marketing, ingrédients clefs. 15 722 ingrédients indexés, classés par tolérance (vert / jaune / orange / rouge), avec autocomplete fuzzy et fiches détaillées.
Stack : Next.js 15 + React 19 + Tailwind CSS 3 + Supabase free tier. Aucun service payant. Light mode strict.
npm install
npm run dev
Le site démarre sur http://localhost:3000. L'application est immédiatement opérationnelle sur les 15 722 ingrédients déjà chargés dans Supabase.
Le fichier .env contient les identifiants Supabase :
| Clé | Rôle |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | URL de l'API Supabase |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Clé publique (lecture seule via RLS) |
SUPABASE_SERVICE_ROLE_KEY | Clé secrète, côté serveur uniquement. Utilisée par les scripts Python |
Cosme Check/
├── app/ ← Next.js App Router
│ ├── layout.tsx ← layout global, light mode forcé
│ ├── page.tsx ← page d'accueil (SearchBar centrée)
│ ├── globals.css
│ ├── about/page.tsx ← page "À propos"
│ ├── search/page.tsx ← liste de résultats étendue
│ ├── i/[slug]/page.tsx ← fiche ingrédient (ISR 24h)
│ ├── api/search/route.ts ← endpoint autocomplete (rate limited)
│ ├── robots.txt/route.ts ← robots.txt strict
│ └── not-found.tsx ← 404 stylée
├── components/
│ ├── SearchBar.tsx ← input + dropdown avec debounce 150ms
│ ├── ColorBadge.tsx ← badge de classification
│ └── Logo.tsx
├── lib/
│ ├── supabase.ts ← clients (anon, service) + types TS
│ ├── ratelimit.ts ← rate-limit in-memory + blacklist
│ └── colors.ts ← palette + classes Tailwind par rating
├── middleware.ts ← filtre UA + bot traps
├── scripts/ ← pipeline Python (indépendant du site)
│ └── (scripts ETL internes)
├── data/
│ └── reference/
└── docs/cahier-des-charges.md ← spec complète
Le scraping est séparé du site. Quatre phases :
cosme_check.ingredients peuplée).cosmetwiki-products. Le site n'a plus aucune dépendance externe pour ses ressources.Performances mesurées (enrichissement détaillé) :
--no-images)details_scraped en BDDEstimation pour les 15 622 ingrédients restants :
Toutes les données vivent dans le schéma isolé cosme_check (la base est partagée avec une autre app, le schéma public n'est pas touché).
| Table | Rôle |
|---|---|
cosme_check.ingredients | 15 722 ingrédients INCI, full-text + trigram |
cosme_check.products | produits scrapés (avec image_url en hotlink) |
cosme_check.product_ingredients | jonction produits ↔ ingrédients |
cosme_check.search_log | log anonyme des recherches (service_role only) |
Le client web ne tape pas directement les tables : il appelle des fonctions RPC définies dans public en SECURITY DEFINER :
| RPC | Rôle | Accès |
|---|---|---|
cosme_check_search(q, result_limit) | autocomplete fuzzy (trigram + tsvector) | anon |
cosme_check_get_ingredient(slug) | fiche complète | anon |
cosme_check_products_for_ingredient(id, limit) | produits associés | anon |
cosme_check_popular_suggestions(limit) | suggestions home | anon |
cosme_check_pending_ingredients(limit) | liste à enrichir | service_role only |
cosme_check_upsert_ingredients(rows) | upsert batch ingrédients | service_role only |
cosme_check_upsert_products(rows) | upsert batch produits + composition | service_role only |
| Couche | Implémentation |
|---|---|
| RLS | toutes les tables ont ENABLE ROW LEVEL SECURITY, policy SELECT pour anon, écriture service_role uniquement |
| Rate limit | 30 req/min/IP en mémoire, sur /api/search (lib/ratelimit.ts) |
| Honeypot | champ email_confirm masqué dans la SearchBar → IP blacklistée 24 h si rempli |
| Bot traps | URLs piégées (/admin-bot-trap, /wp-admin, /wp-login.php) → 404 + log |
| UA filter | middleware bloque curl, python-requests, GPTBot, ClaudeBot, etc. sur /api/ |
| robots.txt | Googlebot/Bingbot/DuckDuckBot autorisés, IA scrapers (GPTBot, ClaudeBot, CCBot, Bytespider) bannis |
| Headers | X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy minimale |
| ISR | fiches mises en cache 24 h côté Vercel → ~95 % des hits ne tapent pas Supabase |
hCaptcha n'est pas activé dans cette première version pour ne pas bloquer le démarrage. Pour l'ajouter plus tard : créer un compte gratuit sur hcaptcha.com, ajouter
NEXT_PUBLIC_HCAPTCHA_SITE_KEYetHCAPTCHA_SECRET_KEYau.env, intégrer dansSearchBar.
prefers-color-scheme: dark est ignoré).soft et ink.fade-in, slide-down).glycerin en tapant glicerine, glycérol, 56-81-5 (CAS).| Quota | Free tier | Estimation | Marge |
|---|---|---|---|
| Supabase DB | 500 MB | ~80 MB après enrichissement complet | ✅ x6 |
| Supabase Bandwidth | 5 GB/mois | dépend du trafic ; ISR aide | ⚠️ surveiller |
| Vercel Bandwidth | 100 GB/mois | ~10 GB pour 100 visites/jour | ✅ |
| Storage Supabase | 1 GB | ~330 MB une fois toutes les images téléchargées (75 000 × ~4.5 KB WebP) | ✅ marge x3 |
| Commande | Effet |
|---|---|
npm run dev | dev server sur http://localhost:3000 |
npm run build | build de production |
npm run start | serve le build |
Voir PROJECT_STATUS.md pour la roadmap complète et l'historique des décisions.