File size: 2,753 Bytes
dfa877e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f4d870a
dfa877e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# API contract

All routes are served by the Next.js app. Auth is either:
- `Cookie: ll_session=<jwt>` (web) — set by the OAuth callback.
- `Authorization: Bearer <jwt>` (mobile) — JWT delivered via the `langlearn://auth/callback?token=…` redirect.

The JWT is signed HS256 with `AUTH_SECRET` and contains `{ userId, hfUsername }`. TTL is 7 days.

## Auth

### `GET /api/auth/login`
Starts the HuggingFace OAuth dance.

Query params:
- `client=mobile` — if set, the callback will 302 to `langlearn://auth/callback?token=…` instead of setting a cookie. Otherwise, cookie + 302 to `/practice`.

### `GET /api/auth/callback/huggingface`
Handles HF's redirect back. Validates state + verifier cookies, exchanges code, upserts user, mints JWT. Never call directly.

### `POST /api/auth/logout`
Clears the session cookie. For mobile, the client just drops the stored JWT.

## Profile

### `GET /api/me`
```json
{
  "user": { "id": "uuid", "hfUsername": "alice", "email": "a@x.y", "avatarUrl": "…" },
  "profile": { "nativeLang": "en", "targetLang": "es", "level": "A2" } | null
}
```

### `PUT /api/me/profile`
Body:
```json
{ "nativeLang": "en", "targetLang": "es", "level": "A2" }
```
Responses: 200 with `{ profile }`, 400 on validation errors.
Mobile bearer-token requests also receive `{ token }` with a refreshed JWT carrying the updated profile.

## Stories

### `GET /api/stories`
Lists stories matching the user's profile (targetLang + level). Response:
```json
{ "stories": [{ "id", "title", "targetLang", "level", "createdAt" }], "profile": {...} }
```

### `POST /api/stories`
Generates a new story for the user's profile.
Body (optional): `{ "topic": "a walk in the park" }`.
Response: `{ "story": { id, title, content, vocab, ... } }`.

### `GET /api/stories/:id`
`{ "story": { ... full story ... } }`

### `POST /api/stories/:id/read`
Marks the story as read by the current user.

## Translate

### `POST /api/translate`
```json
{ "text": "perro", "from": "es", "to": "en" }
```
Response: `{ "translation": "dog", "cached": true? }`.

## Vision

### `POST /api/vision/analyze`
Multipart form with field `image` (≤ 8 MB). Response:
```json
{
  "id": "uuid",
  "caption": "a cat sitting on a couch",
  "objects": [
    { "label": "cat", "translation": "gato", "box": [x1,y1,x2,y2], "score": 0.92 }
  ],
  "sentences": [
    { "target": "El gato está sobre el sofá.", "gloss": "The cat is on the sofa." }
  ],
  "imageUrl": "https://…"  // empty if blob storage isn't configured
}
```

## Error shape

All failures return `{ "error": "code", "message"?: "…" }` with an appropriate HTTP status.
Common codes: `unauthenticated` (401), `invalid_input` (400), `no_profile` (400), `inference_failed` (502), `image_too_large` (413).