# REST API — Recipe Manager

**Base URL:** `/api/v1` · **Auth:** ‏`Authorization: Bearer <JWT>` (access ‏15 דק') + refresh ב-HttpOnly cookie
**Pagination:** ‏`?page=1&limit=20` → ‏`{ items, page, limit, total, hasMore }`
**שגיאות (פורמט אחיד):** `{ "error": { "code", "message", "details" } }` — ‏400 ולידציה, 401 לא מאומת, 403 אין הרשאה, 404 לא נמצא, 409 קונפליקט, 429 rate limit, 500 שרת.

## Auth
| Method | Path | תיאור | הרשאה | Rate limit |
|---|---|---|---|---|
| POST | `/auth/register` | ‏`{email, password, displayName}` → ‏`{user, accessToken}` | פתוח | 5/דקה/IP |
| POST | `/auth/login` | ‏`{email, password}` → ‏`{user, accessToken}` + refresh cookie | פתוח | 10/דקה/IP |
| POST | `/auth/refresh` | ‏cookie → ‏`{accessToken}` | cookie | 30/שעה |
| POST | `/auth/logout` | מבטל refresh | מחובר | — |

## Recipes
| Method | Path | תיאור | הרשאה |
|---|---|---|---|
| GET | `/recipes` | רשימה + מסננים: `status,cuisine,mealType,diet,maxTime,difficulty,authorId` | ציבורי (טיוטות: בעלים) |
| POST | `/recipes` | יצירה (ברירת מחדל `draft`) | מחובר |
| GET | `/recipes/:id` | מלא: מרכיבים, שלבים, תמונות, דירוג | לפי privacy; ‏`?token=` ל-link |
| PATCH | `/recipes/:id` | עדכון חלקי; מפעיל re-embed בתור | בעלים/מנהל |
| DELETE | `/recipes/:id` | soft-delete | בעלים/מנהל |
| POST | `/recipes/:id/publish` | ‏draft→published (ולידציה: מרכיב+שלב+תמונה) | בעלים |
| PUT | `/recipes/:id/ingredients` | החלפה אטומית של כל הרשימה (כולל קבוצות וסדר) | בעלים |
| PUT | `/recipes/:id/steps` | החלפה אטומית של השלבים | בעלים |

## Images (presigned)
| Method | Path | תיאור |
|---|---|---|
| POST | `/uploads/presign` | ‏`{contentType, sizeBytes, purpose: recipe\|step\|comment\|avatar}` → ‏`{uploadUrl, fileUrl}`. מגבלה: ‏10MB, ‏jpeg/png/webp. ‏429: ‏30/שעה |
| POST | `/recipes/:id/images` | רישום אחרי העלאה: `{fileUrl, altText, isPrimary}` (בעלים) |
| PATCH | `/recipes/:id/images/:imageId` | ‏altText / isPrimary / position (בעלים) |
| DELETE | `/recipes/:id/images/:imageId` | הסרה (בעלים/מנהל) |

## Comments (מקונן)
| Method | Path | תיאור | הרשאה |
|---|---|---|---|
| GET | `/recipes/:id/comments` | ‏`?sort=new\|top\|helpful&kind=` — עץ עם תשובות | לפי privacy |
| POST | `/recipes/:id/comments` | ‏`{body, kind, parentId?, imageUrl?}` | מחובר, 10/דקה |
| PATCH | `/comments/:id` | עריכה עצמית (סימון edited) | מחבר |
| DELETE | `/comments/:id` | soft-delete | מחבר/מנהל |
| POST | `/comments/:id/helpful` | ‏toggle מועיל | מחובר |
| POST | `/comments/:id/author-reply` | סימון "תשובת המחבר" | בעל המתכון |
| POST | `/comments/:id/report` | ‏`{reason}` | מחובר, 5/שעה |

## Social
| Method | Path | תיאור |
|---|---|---|
| PUT/DELETE | `/recipes/:id/rating` | ‏`{stars: 1..5}` — ‏upsert/הסרה |
| PUT/DELETE | `/recipes/:id/favorite` | מועדפים |
| POST | `/recipes/:id/made` | "הכנתי" `{note?, imageUrl?}` |
| GET/POST | `/collections` · CRUD ‏`/collections/:id` | אוספים |
| PUT/DELETE | `/collections/:id/recipes/:recipeId` | שיוך מתכון |
| GET | `/notifications` · POST `/notifications/read` | התראות |

## Search — סמנטי + מסננים קשיחים
```
GET /search?q=ארוחת ערב מהירה עם עוף בלי גלוטן
    &includeIngredients=&excludeIngredients=&excludeAllergens=gluten
    &maxTotalMinutes=30&difficulty=easy&diet=kid_friendly&minRating=4
```
- ‏`q` עובר ניתוח LLM לחילוץ אילוצים + ‏embedding דרך **שירות ה-BRAIN** (ראה semantic-search.md)
- אלרגנים = **מסנן SQL מחייב**, לעולם לא רק ציון
- תשובה: `{ items: [{recipe, score, matchReason}], parsedConstraints, page... }`
- ‏Rate limit: ‏20/דקה למחובר, 5/דקה לאורח · אורח רואה רק public

## Admin (`role: admin/moderator`)
`GET /admin/reports` · `POST /admin/reports/:id/resolve|dismiss` · `POST /admin/users/:id/ban` · `DELETE /admin/recipes/:id` · `POST /admin/comments/:id/hide` · `GET /admin/stats`
