Skip to content

Commit 86b3312

Browse files
committed
Document legacyCompat and add integration test
1 parent 567962f commit 86b3312

File tree

3 files changed

+91
-1
lines changed

3 files changed

+91
-1
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ Example payload:
105105
}
106106
```
107107

108+
Set `metadata.legacyCompat: true` to add a legacy-friendly tag per category. When enabled, each category gets an extra tag keyed by its `id` with value `"yes"`, in addition to the default `{ categoryId: "<id>" }`.
109+
108110
#### Field/type mapping (v2)
109111
- `select``selectOne`
110112
- `multiselect``selectMultiple`
@@ -119,7 +121,7 @@ Example payload:
119121
- Categories with `track: true` are added to the track list (must not be empty if any track is declared).
120122

121123
#### Limits & validation (v2)
122-
- JSON body ≤ 1 MB (enforced while streaming parse).
124+
- JSON body ≤ 10 MB (streaming-validated).
123125
- SVG icons ≤ 2 MB each; icons can be provided via:
124126
- `svgData` (inline SVG string)
125127
- `svgUrl` (remote fetch with 5s timeout and content-type check)

context/api.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,51 @@ curl -X POST \
102102

103103
---
104104

105+
### 3. Build Configuration (JSON, v2)
106+
107+
#### `POST /v2`
108+
109+
Processes a JSON payload and returns a built `.comapeocat` file using `comapeocat@1.1.0` Writer.
110+
111+
**Content-Type**: `application/json`
112+
113+
**Request Body** (required):
114+
- `metadata`: `{ name: string; version?: string; description?: string; legacyCompat?: boolean }`
115+
- `legacyCompat` (optional): when `true`, every category gets an extra tag keyed by its `id` with value `"yes"` (e.g., `{ "mahali-pa-kihistoria": "yes" }`) in addition to the default `{ categoryId: <id> }` tag to support legacy consumers.
116+
- `categories`: array of categories with `id`, `name`, `appliesTo` (`observation`/`track`), optional `tags`, `fields`, `iconId`, `addTags`, `removeTags`, `terms`, `color`, `track`.
117+
- `fields`: array of fields with `id`, `type`, optional `name/label/tagKey/options/...` (legacy types are mapped automatically).
118+
- `icons` (optional): array with `id` plus either `svgData` or `svgUrl` (data URIs allowed).
119+
- `translations` (optional): object keyed by BCP‑47 locale.
120+
121+
**Default tags**: If a category provides no `tags`, the service sets `{ categoryId: <id> }`. With `legacyCompat: true`, it also adds `{ <id>: "yes" }`.
122+
123+
**Response**: Binary stream of `.comapeocat` file.
124+
125+
**Status Codes**:
126+
- `200 OK` - file built and validated
127+
- `400 Bad Request` - validation errors (e.g., size limits, bad schema, path traversal)
128+
- `422 Unprocessable Entity` - build produced a file but downstream validation timed out/failed
129+
130+
**Examples**:
131+
```bash
132+
curl -X POST \
133+
-H "Content-Type: application/json" \
134+
-d @config.json \
135+
--output output.comapeocat \
136+
http://localhost:3000/v2
137+
```
138+
139+
**Implementation**: `src/controllers/settingsController.ts` (dispatch) and `src/services/comapeocatBuilder.ts` (build logic)
140+
141+
**Key validation/limits** (see `src/config/app.ts`):
142+
- JSON body ≤ 10 MB (streaming enforced in `app.ts` onParse hook)
143+
- Icons ≤ 2 MB each, 5s fetch timeout
144+
- Entries (categories + fields + icons + translations + options) ≤ 10,000
145+
- Locales must be valid BCP‑47 (normalized); path traversal blocked in metadata name/version
146+
- Legacy field types mapped to Writer equivalents
147+
148+
---
149+
105150
## CORS Configuration
106151

107152
The API has CORS enabled for all origins using `@elysiajs/cors`.

src/tests/integration/routes.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,49 @@ describe('API routes', () => {
7171
expect(buffer.byteLength).toBeGreaterThan(0); // Expect a non-empty file
7272
});
7373

74+
it('adds legacyCompat tag in /v2 output', async () => {
75+
const payload = {
76+
metadata: { name: 'legacy-test', version: '1.0.0', legacyCompat: true },
77+
categories: [
78+
{
79+
id: 'mahali-pa-kihistoria',
80+
name: 'Historic Place',
81+
appliesTo: ['observation', 'track'],
82+
fields: ['field-1'],
83+
track: true,
84+
},
85+
],
86+
fields: [{ id: 'field-1', name: 'Field', tagKey: 'field-1', type: 'text' }],
87+
};
88+
89+
const res = await app.handle(
90+
new Request('http://localhost/v2', {
91+
method: 'POST',
92+
headers: { 'content-type': 'application/json' },
93+
body: JSON.stringify(payload),
94+
})
95+
);
96+
97+
expect(res.status).toBe(200);
98+
const buffer = Buffer.from(await res.arrayBuffer());
99+
100+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'comapeo-legacy-'));
101+
const filePath = path.join(tmpDir, 'out.comapeocat');
102+
await fs.writeFile(filePath, buffer);
103+
104+
const reader = new Reader(filePath);
105+
const categories = await reader.categories();
106+
const cat = categories.get('mahali-pa-kihistoria');
107+
108+
expect(cat?.tags).toEqual({
109+
categoryId: 'mahali-pa-kihistoria',
110+
'mahali-pa-kihistoria': 'yes',
111+
});
112+
113+
await reader.close();
114+
await fs.rm(tmpDir, { recursive: true, force: true });
115+
});
116+
74117
it('returns 422 when comapeocat validation hangs', async () => {
75118
const payload = {
76119
metadata: { name: 'timeout-test', version: '1.0.0' },

0 commit comments

Comments
 (0)