rotifex 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1958 -0
  3. package/bin/rotifex.js +18 -0
  4. package/config.default.json +29 -0
  5. package/package.json +48 -0
  6. package/src/ai/agent.routes.js +110 -0
  7. package/src/ai/agent.service.js +326 -0
  8. package/src/ai/agents.config.js +66 -0
  9. package/src/ai/ai.config.js +84 -0
  10. package/src/ai/ai.routes.js +149 -0
  11. package/src/ai/ai.service.js +275 -0
  12. package/src/ai/ai.usage.js +58 -0
  13. package/src/ai/tools/registry.js +156 -0
  14. package/src/auth/auth.controller.js +118 -0
  15. package/src/auth/auth.routes.js +27 -0
  16. package/src/auth/auth.service.js +182 -0
  17. package/src/auth/jwt.middleware.js +41 -0
  18. package/src/auth/password.util.js +6 -0
  19. package/src/commands/init.js +30 -0
  20. package/src/commands/migrate.js +62 -0
  21. package/src/commands/start.js +96 -0
  22. package/src/db/adapters/base.js +73 -0
  23. package/src/db/adapters/sqlite.js +86 -0
  24. package/src/db/connection.js +45 -0
  25. package/src/db/index.js +12 -0
  26. package/src/db/migrator.js +142 -0
  27. package/src/db/schema.js +48 -0
  28. package/src/engine/index.js +76 -0
  29. package/src/engine/queryBuilder.js +52 -0
  30. package/src/engine/routeFactory.js +119 -0
  31. package/src/engine/schemaLoader.js +75 -0
  32. package/src/engine/schemaStore.js +35 -0
  33. package/src/engine/tableSync.js +28 -0
  34. package/src/engine/zodFactory.js +54 -0
  35. package/src/lib/config.js +120 -0
  36. package/src/lib/logBuffer.js +75 -0
  37. package/src/lib/logger.js +26 -0
  38. package/src/server/index.js +8 -0
  39. package/src/server/middleware/errorHandler.js +26 -0
  40. package/src/server/middleware/requestLogger.js +23 -0
  41. package/src/server/plugins.js +38 -0
  42. package/src/server/routes/admin.js +276 -0
  43. package/src/server/routes/files.js +188 -0
  44. package/src/server/routes/health.js +14 -0
  45. package/src/server/server.js +149 -0
  46. package/src/storage/fileTable.js +22 -0
  47. package/src/storage/index.js +5 -0
  48. package/src/storage/storageManager.js +225 -0
package/README.md ADDED
@@ -0,0 +1,1958 @@
1
+ # Rotifex — Complete Documentation Source
2
+
3
+ > This file is the single source of truth for generating full product documentation.
4
+ > It is structured for compatibility with Docusaurus, Mintlify, GitBook, and similar systems.
5
+
6
+ ---
7
+
8
+ ## Table of Contents
9
+
10
+ 1. [Application Overview](#1-application-overview)
11
+ 2. [Feature Documentation](#2-feature-documentation)
12
+ 3. [API Documentation](#3-api-documentation)
13
+ 4. [Authentication](#4-authentication)
14
+ 5. [Models / Data Structures](#5-models--data-structures)
15
+ 6. [AI / LLM Integration](#6-ai--llm-integration)
16
+ 7. [File Storage / Media Handling](#7-file-storage--media-handling)
17
+ 8. [Admin Panel Features](#8-admin-panel-features)
18
+ 9. [Error Handling](#9-error-handling)
19
+ 10. [Environment Configuration](#10-environment-configuration)
20
+ 11. [Deployment](#11-deployment)
21
+ 12. [Example Workflows](#12-example-workflows)
22
+ 13. [Notes for Documentation Generators](#13-notes-for-documentation-generators)
23
+
24
+ ---
25
+
26
+ ## 1. Application Overview
27
+
28
+ ### Description
29
+
30
+ **Rotifex** is a self-hosted backend-as-a-service platform. It lets developers define data models through a JSON schema file or a visual admin panel, and instantly get a full REST API — no code generation required. It ships with JWT authentication, file storage, AI/LLM integration, agent execution, and a built-in admin dashboard.
31
+
32
+ ### Core Purpose
33
+
34
+ Rotifex eliminates boilerplate for backend development. Instead of writing CRUD endpoints, migrations, and auth logic manually, developers define a schema and Rotifex handles everything: table creation, route registration, validation, pagination, filtering, and sorting — live, without restarts.
35
+
36
+ ### Key Capabilities
37
+
38
+ - **Schema-driven REST API** — define a model, get five CRUD endpoints instantly
39
+ - **Live schema updates** — add or remove models at runtime without restarting the server
40
+ - **JWT Authentication** — register, login, refresh tokens, role-based access control
41
+ - **File storage** — upload, download, manage public and private files with signed URLs
42
+ - **AI/LLM integration** — connect OpenAI, Anthropic, Gemini, and Ollama; generate and chat
43
+ - **AI Agents** — ReAct-loop agents with tools: calculator, web search, HTTP GET, DB query, datetime
44
+ - **Token usage tracking** — persistent per-provider token consumption logged to disk
45
+ - **Admin dashboard** — full SPA for managing schemas, users, files, AI providers, agents, logs, and settings
46
+ - **Rate limiting, CORS, structured logging** — production-ready out of the box
47
+
48
+ ### Target Users
49
+
50
+ - Solo developers who need a backend quickly
51
+ - Startups prototyping a product without a dedicated backend engineer
52
+ - Internal tools teams who need structured data storage with an admin interface
53
+ - Developers integrating LLMs into their applications
54
+
55
+ ### Architecture Overview
56
+
57
+ ```
58
+ +-----------------------------------------------------+
59
+ | Rotifex Server |
60
+ | +----------+ +------------+ +-----------------+ |
61
+ | | Fastify | | Schema | | SQLite (via | |
62
+ | | HTTP | | Engine | | better-sqlite3)| |
63
+ | | Server | | (live) | | | |
64
+ | +----------+ +------------+ +-----------------+ |
65
+ | +----------+ +------------+ +-----------------+ |
66
+ | | JWT | | Storage | | AI / Agents | |
67
+ | | Auth | | Manager | | System | |
68
+ | +----------+ +------------+ +-----------------+ |
69
+ | +------------------------------------------------+ |
70
+ | | Admin SPA (React + Vite) | |
71
+ | +------------------------------------------------+ |
72
+ +-----------------------------------------------------+
73
+ ```
74
+
75
+ - **Framework:** Fastify v5 (Node.js)
76
+ - **Database:** SQLite via `better-sqlite3`
77
+ - **Admin frontend:** React 19 + Vite, served as a static SPA from `/`
78
+ - **Config format:** JSON (`schema.json`, `ai.config.json`, `agents.config.json`)
79
+ - **Persistence:** `.env` for secrets, JSON files for AI/agent config, SQLite for all app data
80
+
81
+ ---
82
+
83
+ ## 2. Feature Documentation
84
+
85
+ ### 2.1 Dynamic REST Engine
86
+
87
+ **Description:** The core of Rotifex. Reads `schema.json`, creates SQLite tables, and registers five CRUD routes per model — all at startup and live when models are added via the admin API.
88
+
89
+ **Use case:** A developer defines a `Product` model with `name`, `price`, and `in_stock` fields. Rotifex immediately exposes `/api/products` with full CRUD, filtering, sorting, and pagination — no restart needed.
90
+
91
+ **How it works:**
92
+
93
+ 1. `schema.json` is parsed by `schemaLoader.js` into normalized model definitions.
94
+ 2. `tableSync.js` runs `CREATE TABLE IF NOT EXISTS` for each model.
95
+ 3. `routeFactory.js` registers generic parametric routes (`/api/:table`) that resolve the model from an in-memory store at request time.
96
+ 4. When a model is added/removed via the admin API, the in-memory store is updated and routes resolve immediately.
97
+
98
+ **Field types supported:**
99
+
100
+ | Schema Type | SQLite Type | Notes |
101
+ | ----------- | ----------- | ------------- |
102
+ | `string` | `TEXT` | |
103
+ | `number` | `REAL` | Float |
104
+ | `integer` | `INTEGER` | |
105
+ | `boolean` | `INTEGER` | Stored as 0/1 |
106
+
107
+ **Field definition formats:**
108
+
109
+ ```json
110
+ // Shorthand
111
+ { "name": "string" }
112
+
113
+ // Full form
114
+ {
115
+ "name": {
116
+ "type": "string",
117
+ "required": true,
118
+ "unique": false,
119
+ "default": "Unnamed"
120
+ }
121
+ }
122
+ ```
123
+
124
+ **Auto-generated columns:** Every model automatically gets `id` (UUID), `created_at` (ISO 8601), and `updated_at` (ISO 8601).
125
+
126
+ **Table naming:** Model names are lowercased and pluralized. `Product` -> table `products`, route `/api/products`.
127
+
128
+ ---
129
+
130
+ ### 2.2 JWT Authentication
131
+
132
+ **Description:** Full authentication system with access tokens, refresh tokens, password hashing, and role-based access control.
133
+
134
+ **Use case:** Secure user registration and login for a Rotifex-backed application. Protect admin routes from regular users.
135
+
136
+ **How it works:**
137
+
138
+ 1. `POST /auth/register` hashes the password with bcrypt and inserts a user row.
139
+ 2. `POST /auth/login` verifies credentials and issues a short-lived access token (1 hour) and long-lived refresh token (30 days).
140
+ 3. The JWT middleware runs on every request, verifies the `Authorization: Bearer` header, and injects `x-user-id` / `x-user-role` headers that downstream routes use for authorization.
141
+ 4. `POST /auth/refresh` issues a new token pair without requiring the password.
142
+
143
+ **Roles:** `user` (default) and `admin`. Admin access is required for all `/admin/api/*` endpoints.
144
+
145
+ **Password rules:**
146
+
147
+ - Minimum 8 characters
148
+ - At least one letter
149
+ - At least one number
150
+
151
+ ---
152
+
153
+ ### 2.3 File Storage
154
+
155
+ **Description:** Upload, download, list, and delete files. Supports public and private visibility with HMAC-signed URLs for private file access.
156
+
157
+ **Use case:** Allow users to upload profile pictures, documents, or any binary files. Private files can only be accessed by their uploader or admins, or via a time-limited signed URL.
158
+
159
+ **How it works:**
160
+
161
+ 1. Files are uploaded via multipart form to `POST /files/upload`.
162
+ 2. The `StorageManager` validates MIME type and per-user storage quota.
163
+ 3. Files are stored on disk with UUID-based names in separate `public/` and `private/` directories.
164
+ 4. Metadata (name, MIME type, size, uploader, visibility) is recorded in the `_files` SQLite table.
165
+ 5. Private files require a signed URL generated by `GET /files/:id/signed-url`. The URL includes an HMAC token and expiry timestamp; it is verified on download.
166
+
167
+ ---
168
+
169
+ ### 2.4 AI / LLM Integration
170
+
171
+ **Description:** Connect multiple LLM providers and use them for text generation and multi-turn chat from a unified API.
172
+
173
+ **Use case:** Add AI-powered features to your application — content generation, Q&A, summarization — using whichever provider you have access to.
174
+
175
+ **How it works:**
176
+
177
+ 1. Providers (OpenAI, Anthropic, Gemini, Ollama) are configured via the admin panel; API keys are stored in `ai.config.json`.
178
+ 2. `POST /api/ai/generate` and `POST /api/ai/chat` proxy requests to the selected provider using native `fetch`.
179
+ 3. Each call records token usage to `ai.usage.json` for persistent tracking.
180
+
181
+ ---
182
+
183
+ ### 2.5 AI Agents
184
+
185
+ **Description:** Configurable AI agents that use a ReAct (Reasoning + Acting) loop to complete multi-step tasks using tools.
186
+
187
+ **Use case:** An agent tasked with "What's 15% of the current Bitcoin price?" will search the web for the price, then use the calculator tool to compute the result — all automatically.
188
+
189
+ **How it works:**
190
+
191
+ 1. Agents are defined with a name, provider, model, system prompt, tool list, temperature, and iteration limits.
192
+ 2. When run, the agent sends the task to the LLM with tool definitions.
193
+ 3. If the LLM calls a tool, the tool is executed and the result is appended to the conversation.
194
+ 4. The loop continues until the LLM returns a final answer or `maxIterations` is reached.
195
+ 5. Steps (thinking, tool call, tool result, final answer) are returned for inspection.
196
+
197
+ **Available tools:**
198
+
199
+ | Tool | Description |
200
+ | ---------------- | ------------------------------------------------------------------------ |
201
+ | `get_datetime` | Returns current ISO 8601 datetime, UTC string, and Unix timestamp |
202
+ | `calculate` | Safely evaluates math expressions (`+`, `-`, `*`, `/`, `%`, parentheses) |
203
+ | `web_search` | DuckDuckGo instant-answer search (no API key required) |
204
+ | `http_get` | Makes HTTP GET requests to public URLs; returns up to 4000 chars of body |
205
+ | `database_query` | Runs read-only `SELECT` queries on the Rotifex SQLite database |
206
+
207
+ ---
208
+
209
+ ### 2.6 Admin Dashboard
210
+
211
+ **Description:** A React SPA served at `/` providing a visual interface for all administrative operations.
212
+
213
+ **Tabs:**
214
+
215
+ - **Dashboard** — stat cards (schemas, records, users, files, storage, connected LLMs, agents, uptime, status) and overview tables
216
+ - **Database Schemas** — create, view, and delete data models
217
+ - **User Management** — view and manage registered users
218
+ - **File Browser** — browse, preview, and delete uploaded files
219
+ - **AI Integration** — configure providers, playground for generate/chat, agent management, API docs
220
+ - **Server Logs** — real-time in-memory log viewer with level filtering
221
+ - **Settings** — edit environment variables (port, CORS, rate limits, JWT secrets, storage limits)
222
+
223
+ ---
224
+
225
+ ## 3. API Documentation
226
+
227
+ ### Base URL
228
+
229
+ ```
230
+ http://localhost:3000
231
+ ```
232
+
233
+ All API responses follow the envelope format:
234
+
235
+ - **Success:** `{ "data": <payload>, "meta"?: <pagination> }`
236
+ - **Error:** `{ "error": "<type>", "message": "<detail>", "statusCode": <number> }`
237
+
238
+ ---
239
+
240
+ ### 3.1 Health
241
+
242
+ #### `GET /health`
243
+
244
+ Check server status.
245
+
246
+ **Auth:** None
247
+
248
+ **Response:**
249
+
250
+ ```json
251
+ {
252
+ "status": "ok",
253
+ "uptime": 3600.5,
254
+ "timestamp": "2026-03-06T12:00:00.000Z"
255
+ }
256
+ ```
257
+
258
+ ---
259
+
260
+ ### 3.2 Authentication Endpoints
261
+
262
+ #### `POST /auth/register`
263
+
264
+ Register a new user.
265
+
266
+ **Auth:** None
267
+
268
+ **Request Body:**
269
+
270
+ ```json
271
+ {
272
+ "email": "user@example.com",
273
+ "password": "secret123",
274
+ "display_name": "Jane Doe",
275
+ "role": "user"
276
+ }
277
+ ```
278
+
279
+ | Field | Type | Required | Notes |
280
+ | -------------- | ------ | -------- | ------------------------------------------- |
281
+ | `email` | string | Yes | Must be a valid email |
282
+ | `password` | string | Yes | Min 8 chars, 1 letter, 1 number |
283
+ | `display_name` | string | No | Display name |
284
+ | `role` | string | No | `"user"` or `"admin"`. Defaults to `"user"` |
285
+
286
+ **Response `201`:**
287
+
288
+ ```json
289
+ {
290
+ "data": {
291
+ "id": "uuid",
292
+ "email": "user@example.com",
293
+ "display_name": "Jane Doe",
294
+ "role": "user",
295
+ "created_at": "2026-03-06T12:00:00.000Z"
296
+ },
297
+ "message": "User registered successfully"
298
+ }
299
+ ```
300
+
301
+ **Errors:**
302
+
303
+ | Code | Reason |
304
+ | ----- | ------------------------------------------------ |
305
+ | `400` | Validation failed (invalid email, weak password) |
306
+ | `409` | Email already in use |
307
+
308
+ ---
309
+
310
+ #### `POST /auth/login`
311
+
312
+ Authenticate and receive tokens.
313
+
314
+ **Auth:** None
315
+
316
+ **Request Body:**
317
+
318
+ ```json
319
+ {
320
+ "email": "user@example.com",
321
+ "password": "secret123"
322
+ }
323
+ ```
324
+
325
+ **Response `200`:**
326
+
327
+ ```json
328
+ {
329
+ "data": {
330
+ "accessToken": "<jwt>",
331
+ "refreshToken": "<jwt>",
332
+ "user": {
333
+ "id": "uuid",
334
+ "email": "user@example.com",
335
+ "display_name": "Jane Doe",
336
+ "role": "user"
337
+ }
338
+ }
339
+ }
340
+ ```
341
+
342
+ **Errors:**
343
+
344
+ | Code | Reason |
345
+ | ----- | ------------------------- |
346
+ | `400` | Missing email or password |
347
+ | `401` | Invalid credentials |
348
+
349
+ ---
350
+
351
+ #### `POST /auth/refresh`
352
+
353
+ Exchange a refresh token for a new token pair.
354
+
355
+ **Auth:** None
356
+
357
+ **Request Body:**
358
+
359
+ ```json
360
+ {
361
+ "refreshToken": "<jwt>"
362
+ }
363
+ ```
364
+
365
+ **Response `200`:**
366
+
367
+ ```json
368
+ {
369
+ "data": {
370
+ "accessToken": "<new-jwt>",
371
+ "refreshToken": "<new-jwt>"
372
+ }
373
+ }
374
+ ```
375
+
376
+ **Errors:**
377
+
378
+ | Code | Reason |
379
+ | ----- | -------------------------------- |
380
+ | `400` | Missing refreshToken |
381
+ | `401` | Invalid or expired refresh token |
382
+
383
+ ---
384
+
385
+ #### `GET /auth/me`
386
+
387
+ Return the currently authenticated user.
388
+
389
+ **Auth:** `Authorization: Bearer <accessToken>`
390
+
391
+ **Response `200`:**
392
+
393
+ ```json
394
+ {
395
+ "data": {
396
+ "id": "uuid",
397
+ "email": "user@example.com",
398
+ "display_name": "Jane Doe",
399
+ "role": "user",
400
+ "created_at": "2026-03-06T12:00:00.000Z"
401
+ }
402
+ }
403
+ ```
404
+
405
+ **Errors:**
406
+
407
+ | Code | Reason |
408
+ | ----- | ------------------------ |
409
+ | `401` | Missing or invalid token |
410
+ | `404` | User not found |
411
+
412
+ ---
413
+
414
+ ### 3.3 Dynamic CRUD Endpoints
415
+
416
+ These endpoints are available for every model defined in `schema.json`. Replace `:table` with the model's table name (e.g. `products` for a `Product` model).
417
+
418
+ #### `GET /api/:table`
419
+
420
+ List records with filtering, sorting, and pagination.
421
+
422
+ **Auth:** Optional
423
+
424
+ **Query Parameters:**
425
+
426
+ | Parameter | Type | Default | Description |
427
+ | --------- | ------- | ------------ | ---------------------------------------- |
428
+ | `page` | integer | `1` | Page number |
429
+ | `limit` | integer | `20` | Records per page (max 100) |
430
+ | `sort` | string | `created_at` | Field to sort by |
431
+ | `order` | string | `DESC` | `ASC` or `DESC` |
432
+ | `<field>` | any | — | Filter by exact value on any model field |
433
+
434
+ **Example:** `GET /api/products?sort=price&order=ASC&page=2&limit=10&in_stock=1`
435
+
436
+ **Response `200`:**
437
+
438
+ ```json
439
+ {
440
+ "data": [
441
+ {
442
+ "id": "uuid",
443
+ "name": "Widget",
444
+ "price": 9.99,
445
+ "in_stock": 1,
446
+ "created_at": "2026-03-06T12:00:00.000Z",
447
+ "updated_at": "2026-03-06T12:00:00.000Z"
448
+ }
449
+ ],
450
+ "meta": {
451
+ "total": 42,
452
+ "page": 2,
453
+ "limit": 10,
454
+ "pages": 5
455
+ }
456
+ }
457
+ ```
458
+
459
+ ---
460
+
461
+ #### `GET /api/:table/:id`
462
+
463
+ Get a single record by ID.
464
+
465
+ **Response `200`:**
466
+
467
+ ```json
468
+ {
469
+ "data": {
470
+ "id": "uuid",
471
+ "name": "Widget",
472
+ "price": 9.99
473
+ }
474
+ }
475
+ ```
476
+
477
+ **Errors:**
478
+
479
+ | Code | Reason |
480
+ | ----- | --------------------------------- |
481
+ | `404` | Record not found or unknown table |
482
+
483
+ ---
484
+
485
+ #### `POST /api/:table`
486
+
487
+ Create a new record.
488
+
489
+ **Auth:** Optional
490
+
491
+ **Request Body:** JSON object matching the model's field definitions.
492
+
493
+ ```json
494
+ {
495
+ "name": "Widget",
496
+ "price": 9.99,
497
+ "in_stock": true
498
+ }
499
+ ```
500
+
501
+ **Response `201`:**
502
+
503
+ ```json
504
+ {
505
+ "data": {
506
+ "id": "uuid",
507
+ "name": "Widget",
508
+ "price": 9.99,
509
+ "in_stock": 1,
510
+ "created_at": "2026-03-06T12:00:00.000Z",
511
+ "updated_at": "2026-03-06T12:00:00.000Z"
512
+ }
513
+ }
514
+ ```
515
+
516
+ **Errors:**
517
+
518
+ | Code | Reason |
519
+ | ----- | ------------------------------------------------------- |
520
+ | `400` | Validation error (missing required fields, wrong types) |
521
+ | `404` | Unknown table |
522
+
523
+ ---
524
+
525
+ #### `PUT /api/:table/:id`
526
+
527
+ Update a record (partial — only send fields to change).
528
+
529
+ **Request Body:**
530
+
531
+ ```json
532
+ {
533
+ "price": 14.99
534
+ }
535
+ ```
536
+
537
+ **Response `200`:**
538
+
539
+ ```json
540
+ {
541
+ "data": {
542
+ "id": "uuid",
543
+ "name": "Widget",
544
+ "price": 14.99
545
+ }
546
+ }
547
+ ```
548
+
549
+ **Errors:**
550
+
551
+ | Code | Reason |
552
+ | ----- | -------------------------------------- |
553
+ | `400` | Validation error or no fields provided |
554
+ | `404` | Record or table not found |
555
+
556
+ ---
557
+
558
+ #### `DELETE /api/:table/:id`
559
+
560
+ Delete a record.
561
+
562
+ **Response:** `204 No Content`
563
+
564
+ **Errors:**
565
+
566
+ | Code | Reason |
567
+ | ----- | ------------------------- |
568
+ | `404` | Record or table not found |
569
+
570
+ ---
571
+
572
+ ### 3.4 File Endpoints
573
+
574
+ #### `POST /files/upload`
575
+
576
+ Upload a file.
577
+
578
+ **Auth:** Optional (identity from JWT `Authorization` header)
579
+
580
+ **Request:** `multipart/form-data`
581
+
582
+ | Field | Type | Required | Description |
583
+ | ------------ | ------ | -------- | ----------------------------------- |
584
+ | `file` | file | Yes | The file to upload |
585
+ | `visibility` | string | No | `"public"` (default) or `"private"` |
586
+
587
+ **Response `201`:**
588
+
589
+ ```json
590
+ {
591
+ "data": {
592
+ "id": "uuid",
593
+ "original_name": "photo.jpg",
594
+ "stored_name": "uuid.jpg",
595
+ "mime_type": "image/jpeg",
596
+ "size_bytes": 204800,
597
+ "visibility": "public",
598
+ "uploader_id": "user-uuid",
599
+ "created_at": "2026-03-06T12:00:00.000Z"
600
+ }
601
+ }
602
+ ```
603
+
604
+ **Errors:**
605
+
606
+ | Code | Reason |
607
+ | ----- | ----------------------------------------- |
608
+ | `400` | No file provided, invalid visibility |
609
+ | `413` | File exceeds size limit or per-user quota |
610
+
611
+ ---
612
+
613
+ #### `GET /files`
614
+
615
+ List files. Admins see all files; other users see only their own.
616
+
617
+ **Response `200`:**
618
+
619
+ ```json
620
+ {
621
+ "data": [
622
+ /* array of file metadata objects */
623
+ ],
624
+ "meta": { "total": 5 }
625
+ }
626
+ ```
627
+
628
+ ---
629
+
630
+ #### `GET /files/:id`
631
+
632
+ Get metadata for a single file.
633
+
634
+ **Errors:**
635
+
636
+ | Code | Reason |
637
+ | ----- | --------------------------- |
638
+ | `403` | Not the owner and not admin |
639
+ | `404` | File not found |
640
+
641
+ ---
642
+
643
+ #### `GET /files/:id/download`
644
+
645
+ Download a file. Public files are accessible directly. Private files require a signed URL.
646
+
647
+ **Query Parameters (private files only):**
648
+
649
+ | Parameter | Description |
650
+ | --------- | ---------------------------------- |
651
+ | `token` | HMAC token from the signed URL |
652
+ | `expires` | Unix timestamp from the signed URL |
653
+
654
+ **Response:** File stream with appropriate `Content-Type` and `Content-Disposition` headers.
655
+
656
+ **Errors:**
657
+
658
+ | Code | Reason |
659
+ | ----- | ---------------------------------------------- |
660
+ | `403` | Private file accessed without valid signed URL |
661
+ | `404` | File not found |
662
+
663
+ ---
664
+
665
+ #### `GET /files/:id/signed-url`
666
+
667
+ Generate a time-limited signed URL for a private file.
668
+
669
+ **Auth:** Must be the file owner or admin.
670
+
671
+ **Response `200`:**
672
+
673
+ ```json
674
+ {
675
+ "data": {
676
+ "url": "http://localhost:3000/files/uuid/download?token=abc123&expires=1741300000",
677
+ "expires": 1741300000
678
+ }
679
+ }
680
+ ```
681
+
682
+ **Errors:**
683
+
684
+ | Code | Reason |
685
+ | ----- | --------------------------- |
686
+ | `400` | File is not private |
687
+ | `403` | Not the owner and not admin |
688
+ | `404` | File not found |
689
+
690
+ ---
691
+
692
+ #### `DELETE /files/:id`
693
+
694
+ Delete a file from disk and database.
695
+
696
+ **Auth:** Must be the file owner or admin.
697
+
698
+ **Response:** `204 No Content`
699
+
700
+ ---
701
+
702
+ ### 3.5 AI Endpoints
703
+
704
+ #### `GET /api/ai/providers`
705
+
706
+ List all enabled AI providers (public).
707
+
708
+ **Response `200`:**
709
+
710
+ ```json
711
+ {
712
+ "data": [
713
+ {
714
+ "id": "openai",
715
+ "label": "OpenAI",
716
+ "models": ["gpt-4o", "gpt-4o-mini"],
717
+ "defaultModel": "gpt-4o"
718
+ }
719
+ ]
720
+ }
721
+ ```
722
+
723
+ ---
724
+
725
+ #### `GET /api/ai/models`
726
+
727
+ Flat list of all models across all enabled providers.
728
+
729
+ **Response `200`:**
730
+
731
+ ```json
732
+ {
733
+ "data": [
734
+ { "provider": "openai", "providerLabel": "OpenAI", "model": "gpt-4o" },
735
+ {
736
+ "provider": "anthropic",
737
+ "providerLabel": "Anthropic",
738
+ "model": "claude-sonnet-4-6"
739
+ }
740
+ ]
741
+ }
742
+ ```
743
+
744
+ ---
745
+
746
+ #### `GET /api/ai/models/:provider`
747
+
748
+ Models for a specific provider.
749
+
750
+ **Response `200`:**
751
+
752
+ ```json
753
+ { "data": ["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"] }
754
+ ```
755
+
756
+ ---
757
+
758
+ #### `POST /api/ai/generate`
759
+
760
+ Generate a single text completion.
761
+
762
+ **Request Body:**
763
+
764
+ ```json
765
+ {
766
+ "provider": "openai",
767
+ "model": "gpt-4o",
768
+ "prompt": "Explain quantum entanglement in one sentence.",
769
+ "system": "You are a physics professor.",
770
+ "maxTokens": 256,
771
+ "temperature": 0.7
772
+ }
773
+ ```
774
+
775
+ | Field | Type | Required | Description |
776
+ | ------------- | ------- | -------- | ------------------------------------------------------- |
777
+ | `provider` | string | Yes | Provider ID (`openai`, `anthropic`, `gemini`, `ollama`) |
778
+ | `prompt` | string | Yes | The user prompt |
779
+ | `model` | string | No | Defaults to provider's `defaultModel` |
780
+ | `system` | string | No | System instruction |
781
+ | `maxTokens` | integer | No | Maximum tokens to generate |
782
+ | `temperature` | number | No | Sampling temperature (0-2) |
783
+
784
+ **Response `200`:**
785
+
786
+ ```json
787
+ {
788
+ "data": {
789
+ "text": "Quantum entanglement is a phenomenon where two particles...",
790
+ "model": "gpt-4o",
791
+ "usage": {
792
+ "prompt_tokens": 42,
793
+ "completion_tokens": 28
794
+ }
795
+ }
796
+ }
797
+ ```
798
+
799
+ ---
800
+
801
+ #### `POST /api/ai/chat`
802
+
803
+ Multi-turn conversation.
804
+
805
+ **Request Body:**
806
+
807
+ ```json
808
+ {
809
+ "provider": "anthropic",
810
+ "model": "claude-sonnet-4-6",
811
+ "messages": [
812
+ { "role": "user", "content": "Hello!" },
813
+ { "role": "assistant", "content": "Hi there! How can I help?" },
814
+ { "role": "user", "content": "What is 2+2?" }
815
+ ],
816
+ "system": "You are a helpful assistant.",
817
+ "maxTokens": 512
818
+ }
819
+ ```
820
+
821
+ **Response `200`:**
822
+
823
+ ```json
824
+ {
825
+ "data": {
826
+ "message": { "role": "assistant", "content": "2+2 equals 4." },
827
+ "model": "claude-sonnet-4-6",
828
+ "usage": {
829
+ "prompt_tokens": 60,
830
+ "completion_tokens": 12
831
+ }
832
+ }
833
+ }
834
+ ```
835
+
836
+ ---
837
+
838
+ ### 3.6 Agent Endpoints
839
+
840
+ #### `GET /api/agents`
841
+
842
+ List all defined agents (public).
843
+
844
+ **Response `200`:**
845
+
846
+ ```json
847
+ {
848
+ "data": [
849
+ {
850
+ "id": "uuid",
851
+ "name": "Research Assistant",
852
+ "description": "Searches the web and summarizes results",
853
+ "provider": "openai",
854
+ "model": "gpt-4o",
855
+ "tools": ["web_search", "calculate"],
856
+ "createdAt": "2026-03-06T12:00:00.000Z"
857
+ }
858
+ ]
859
+ }
860
+ ```
861
+
862
+ ---
863
+
864
+ #### `GET /api/agents/tools`
865
+
866
+ List all available tools.
867
+
868
+ **Response `200`:**
869
+
870
+ ```json
871
+ {
872
+ "data": [
873
+ {
874
+ "name": "calculate",
875
+ "description": "Safely evaluate a mathematical expression...",
876
+ "parameters": {
877
+ "expression": { "type": "string", "description": "A math expression" }
878
+ },
879
+ "required": ["expression"]
880
+ }
881
+ ]
882
+ }
883
+ ```
884
+
885
+ ---
886
+
887
+ #### `POST /api/agents/:id/run`
888
+
889
+ Run an agent with a task input.
890
+
891
+ **Request Body:**
892
+
893
+ ```json
894
+ {
895
+ "input": "What is 18% tip on a $47.50 bill?"
896
+ }
897
+ ```
898
+
899
+ **Response `200`:**
900
+
901
+ ```json
902
+ {
903
+ "data": {
904
+ "agentId": "uuid",
905
+ "agentName": "Math Helper",
906
+ "input": "What is 18% tip on a $47.50 bill?",
907
+ "output": "The 18% tip on a $47.50 bill is $8.55.",
908
+ "steps": [
909
+ {
910
+ "type": "thinking",
911
+ "content": "I need to calculate 18% of 47.50",
912
+ "iteration": 1
913
+ },
914
+ {
915
+ "type": "tool_call",
916
+ "tool": "calculate",
917
+ "args": { "expression": "47.50 * 0.18" },
918
+ "iteration": 1
919
+ },
920
+ {
921
+ "type": "tool_result",
922
+ "tool": "calculate",
923
+ "result": "{\"expression\":\"47.50 * 0.18\",\"result\":8.55}",
924
+ "iteration": 1
925
+ },
926
+ {
927
+ "type": "final_answer",
928
+ "content": "The 18% tip on a $47.50 bill is $8.55.",
929
+ "iteration": 2
930
+ }
931
+ ],
932
+ "usage": {
933
+ "prompt_tokens": 320,
934
+ "completion_tokens": 85
935
+ }
936
+ }
937
+ }
938
+ ```
939
+
940
+ **Errors:**
941
+
942
+ | Code | Reason |
943
+ | ----- | ------------------------ |
944
+ | `400` | Missing or empty `input` |
945
+ | `404` | Agent not found |
946
+ | `500` | LLM provider error |
947
+
948
+ ---
949
+
950
+ ### 3.7 Admin Endpoints
951
+
952
+ All admin endpoints require the `x-user-role: admin` header (automatically injected from JWT when role is `admin`).
953
+
954
+ #### `GET /admin/api/stats`
955
+
956
+ Dashboard statistics.
957
+
958
+ **Response `200`:**
959
+
960
+ ```json
961
+ {
962
+ "data": {
963
+ "models": [{ "model": "Product", "table": "products", "count": 42 }],
964
+ "users": { "count": 15 },
965
+ "files": { "count": 8, "storageMB": 12.5 },
966
+ "uptime": 3600,
967
+ "ai": {
968
+ "connectedLLMs": 2,
969
+ "enabledLLMs": 2,
970
+ "providers": [{ "id": "openai", "label": "OpenAI", "hasKey": true }],
971
+ "agentsCount": 3,
972
+ "usage": {
973
+ "totalRequests": 120,
974
+ "totalInputTokens": 45000,
975
+ "totalOutputTokens": 12000,
976
+ "byProvider": {
977
+ "openai": {
978
+ "requests": 80,
979
+ "inputTokens": 30000,
980
+ "outputTokens": 8000
981
+ }
982
+ }
983
+ }
984
+ }
985
+ }
986
+ }
987
+ ```
988
+
989
+ ---
990
+
991
+ #### `GET /admin/api/schema`
992
+
993
+ Get all model definitions.
994
+
995
+ **Response `200`:**
996
+
997
+ ```json
998
+ {
999
+ "data": {
1000
+ "Product": {
1001
+ "tableName": "products",
1002
+ "fields": [
1003
+ { "name": "name", "type": "string", "required": true },
1004
+ { "name": "price", "type": "number", "required": false }
1005
+ ]
1006
+ }
1007
+ }
1008
+ }
1009
+ ```
1010
+
1011
+ ---
1012
+
1013
+ #### `POST /admin/api/schema`
1014
+
1015
+ Create a new model. Routes become active immediately — no restart required.
1016
+
1017
+ **Request Body:**
1018
+
1019
+ ```json
1020
+ {
1021
+ "name": "Product",
1022
+ "fields": {
1023
+ "name": { "type": "string", "required": true },
1024
+ "price": "number",
1025
+ "in_stock": "boolean"
1026
+ }
1027
+ }
1028
+ ```
1029
+
1030
+ **Response `201`:**
1031
+
1032
+ ```json
1033
+ {
1034
+ "data": { "name": "Product", "tableName": "products", "fields": [] },
1035
+ "message": "Model \"Product\" is live. Routes /products are active now."
1036
+ }
1037
+ ```
1038
+
1039
+ **Errors:**
1040
+
1041
+ | Code | Reason |
1042
+ | ----- | ---------------------------------------------- |
1043
+ | `400` | Missing `name` or `fields`, reserved name used |
1044
+ | `409` | Model with that name already exists |
1045
+
1046
+ ---
1047
+
1048
+ #### `DELETE /admin/api/schema/:name`
1049
+
1050
+ Remove a model. Routes are deactivated immediately; the DB table is preserved.
1051
+
1052
+ **Response:** `204 No Content`
1053
+
1054
+ **Errors:**
1055
+
1056
+ | Code | Reason |
1057
+ | ----- | ---------------------------------------------- |
1058
+ | `400` | Attempt to delete a system model (e.g. `User`) |
1059
+ | `404` | Model not found |
1060
+
1061
+ ---
1062
+
1063
+ #### `GET /admin/api/logs`
1064
+
1065
+ Retrieve in-memory server logs.
1066
+
1067
+ **Query Parameters:**
1068
+
1069
+ | Parameter | Description |
1070
+ | --------- | ------------------------------------------------- |
1071
+ | `after` | Unix timestamp — only return logs after this time |
1072
+ | `level` | Filter by level: `info`, `warn`, `error`, `debug` |
1073
+
1074
+ **Response `200`:**
1075
+
1076
+ ```json
1077
+ {
1078
+ "logs": [
1079
+ {
1080
+ "ts": 1741258800000,
1081
+ "level": "info",
1082
+ "msg": "GET /api/products 200 12ms"
1083
+ }
1084
+ ]
1085
+ }
1086
+ ```
1087
+
1088
+ ---
1089
+
1090
+ #### `GET /admin/api/env`
1091
+
1092
+ Read current environment/config values.
1093
+
1094
+ **Response `200`:**
1095
+
1096
+ ```json
1097
+ {
1098
+ "data": {
1099
+ "JWT_SECRET": "***",
1100
+ "ROTIFEX_PORT": "3000",
1101
+ "ROTIFEX_CORS_ORIGIN": "*"
1102
+ }
1103
+ }
1104
+ ```
1105
+
1106
+ ---
1107
+
1108
+ #### `POST /admin/api/env`
1109
+
1110
+ Write environment variables to `.env`. Restart required for changes to take effect.
1111
+
1112
+ **Request Body:**
1113
+
1114
+ ```json
1115
+ {
1116
+ "vars": {
1117
+ "ROTIFEX_PORT": "4000",
1118
+ "ROTIFEX_CORS_ORIGIN": "https://myapp.com"
1119
+ }
1120
+ }
1121
+ ```
1122
+
1123
+ **Response `200`:**
1124
+
1125
+ ```json
1126
+ {
1127
+ "message": "Environment saved. Restart the server for changes to take effect."
1128
+ }
1129
+ ```
1130
+
1131
+ ---
1132
+
1133
+ #### `GET /admin/api/ai/providers`
1134
+
1135
+ Get all AI providers with masked API keys.
1136
+
1137
+ **Response `200`:**
1138
+
1139
+ ```json
1140
+ {
1141
+ "data": [
1142
+ {
1143
+ "id": "openai",
1144
+ "label": "OpenAI",
1145
+ "enabled": true,
1146
+ "apiKey": "***abcd",
1147
+ "hasKey": true,
1148
+ "models": ["gpt-4o", "gpt-4o-mini"],
1149
+ "defaultModel": "gpt-4o"
1150
+ }
1151
+ ]
1152
+ }
1153
+ ```
1154
+
1155
+ ---
1156
+
1157
+ #### `PUT /admin/api/ai/providers/:id`
1158
+
1159
+ Update a provider's configuration.
1160
+
1161
+ **Request Body:**
1162
+
1163
+ ```json
1164
+ {
1165
+ "enabled": true,
1166
+ "apiKey": "sk-...",
1167
+ "defaultModel": "gpt-4o-mini"
1168
+ }
1169
+ ```
1170
+
1171
+ **Response `200`:**
1172
+
1173
+ ```json
1174
+ {
1175
+ "data": {
1176
+ "id": "openai",
1177
+ "label": "OpenAI",
1178
+ "enabled": true,
1179
+ "hasKey": true
1180
+ },
1181
+ "message": "Provider \"openai\" updated."
1182
+ }
1183
+ ```
1184
+
1185
+ ---
1186
+
1187
+ #### `GET /admin/api/agents`
1188
+
1189
+ List all agents with full detail (including system prompt).
1190
+
1191
+ #### `GET /admin/api/agents/:id`
1192
+
1193
+ Get a single agent by ID.
1194
+
1195
+ #### `POST /admin/api/agents`
1196
+
1197
+ Create an agent.
1198
+
1199
+ **Request Body:**
1200
+
1201
+ ```json
1202
+ {
1203
+ "name": "Research Assistant",
1204
+ "description": "Searches and summarizes web content",
1205
+ "provider": "openai",
1206
+ "model": "gpt-4o",
1207
+ "systemPrompt": "You are a research assistant. Use tools to answer questions accurately.",
1208
+ "tools": ["web_search", "calculate"],
1209
+ "temperature": 0.7,
1210
+ "maxTokens": 2048,
1211
+ "maxIterations": 10
1212
+ }
1213
+ ```
1214
+
1215
+ **Response `201`:**
1216
+
1217
+ ```json
1218
+ {
1219
+ "data": { "id": "uuid", "name": "Research Assistant", ... },
1220
+ "message": "Agent \"Research Assistant\" created."
1221
+ }
1222
+ ```
1223
+
1224
+ #### `PUT /admin/api/agents/:id`
1225
+
1226
+ Update an agent (partial patch). Same body shape as POST.
1227
+
1228
+ **Response `200`:**
1229
+
1230
+ ```json
1231
+ {
1232
+ "data": { "id": "uuid", "name": "Research Assistant", ... },
1233
+ "message": "Agent \"Research Assistant\" updated."
1234
+ }
1235
+ ```
1236
+
1237
+ #### `DELETE /admin/api/agents/:id`
1238
+
1239
+ Delete an agent permanently.
1240
+
1241
+ **Response:** `204 No Content`
1242
+
1243
+ ---
1244
+
1245
+ ## 4. Authentication
1246
+
1247
+ ### Flow
1248
+
1249
+ ```
1250
+ Client Rotifex Server
1251
+ | |
1252
+ |-- POST /auth/register ----------> | Hash password, create user
1253
+ |<- { user } ---------------------- |
1254
+ | |
1255
+ |-- POST /auth/login -------------> | Verify password
1256
+ |<- { accessToken, refreshToken }-- |
1257
+ | |
1258
+ |-- GET /api/products |
1259
+ | Authorization: Bearer <token> > | Verify JWT, inject x-user-id/x-user-role
1260
+ |<- { data: [...] } --------------- |
1261
+ | |
1262
+ |-- POST /auth/refresh -----------> | Verify refresh token, issue new pair
1263
+ |<- { accessToken, refreshToken }-- |
1264
+ ```
1265
+
1266
+ ### Token Details
1267
+
1268
+ | Token | Algorithm | TTL | Secret Env Var |
1269
+ | ------------- | --------- | ------- | -------------------- |
1270
+ | Access Token | HS256 | 1 hour | `JWT_SECRET` |
1271
+ | Refresh Token | HS256 | 30 days | `JWT_REFRESH_SECRET` |
1272
+
1273
+ Secrets are auto-generated and saved to `.env` on first startup if not explicitly set.
1274
+
1275
+ ### Required Headers
1276
+
1277
+ | Header | Value | Set By |
1278
+ | --------------- | ---------------------- | ------------------------------ |
1279
+ | `Authorization` | `Bearer <accessToken>` | Client |
1280
+ | `x-user-id` | User UUID | JWT middleware (auto-injected) |
1281
+ | `x-user-role` | `user` or `admin` | JWT middleware (auto-injected) |
1282
+
1283
+ ### Permission Levels
1284
+
1285
+ | Role | Access |
1286
+ | ------- | --------------------------------------------- |
1287
+ | `user` | Public endpoints, own files, own data records |
1288
+ | `admin` | All endpoints including `/admin/api/*` |
1289
+
1290
+ > The JWT middleware skips all `/auth/*` routes. For `/auth/me`, the token is manually verified inside the handler.
1291
+
1292
+ ---
1293
+
1294
+ ## 5. Models / Data Structures
1295
+
1296
+ ### User
1297
+
1298
+ Built-in model managed by the auth system. Not configurable via the schema engine.
1299
+
1300
+ | Field | Type | Required | Notes |
1301
+ | --------------- | ----------------- | -------- | ----------------------------------- |
1302
+ | `id` | string (UUID) | Auto | Primary key |
1303
+ | `email` | string | Yes | Unique |
1304
+ | `display_name` | string | No | |
1305
+ | `role` | string | Yes | `"user"` or `"admin"` |
1306
+ | `password_hash` | string | Internal | bcrypt hash — never returned by API |
1307
+ | `created_at` | string (ISO 8601) | Auto | |
1308
+ | `updated_at` | string (ISO 8601) | Auto | |
1309
+
1310
+ ---
1311
+
1312
+ ### \_files (File Metadata)
1313
+
1314
+ Internal table managed by `StorageManager`. Not in `schema.json`.
1315
+
1316
+ | Field | Type | Notes |
1317
+ | --------------- | ----------------- | ---------------------------------------- |
1318
+ | `id` | string (UUID) | Primary key |
1319
+ | `original_name` | string | Original filename from upload |
1320
+ | `stored_name` | string | `<uuid>.<ext>` — actual filename on disk |
1321
+ | `mime_type` | string | e.g. `image/jpeg` |
1322
+ | `size_bytes` | integer | File size in bytes |
1323
+ | `visibility` | string | `"public"` or `"private"` |
1324
+ | `uploader_id` | string | UUID of the uploading user |
1325
+ | `created_at` | string (ISO 8601) | |
1326
+
1327
+ ---
1328
+
1329
+ ### Custom Models (schema.json)
1330
+
1331
+ All custom models automatically receive `id`, `created_at`, and `updated_at`.
1332
+
1333
+ **Example `schema.json`:**
1334
+
1335
+ ```json
1336
+ {
1337
+ "Product": {
1338
+ "fields": {
1339
+ "name": { "type": "string", "required": true },
1340
+ "price": "number",
1341
+ "in_stock": "boolean",
1342
+ "sku": { "type": "string", "unique": true }
1343
+ }
1344
+ },
1345
+ "Order": {
1346
+ "fields": {
1347
+ "product_id": { "type": "string", "required": true },
1348
+ "quantity": { "type": "integer", "required": true },
1349
+ "status": { "type": "string", "default": "pending" }
1350
+ }
1351
+ }
1352
+ }
1353
+ ```
1354
+
1355
+ **Resulting routes:**
1356
+
1357
+ - `Product` -> table `products` -> `/api/products`, `/api/products/:id`
1358
+ - `Order` -> table `orders` -> `/api/orders`, `/api/orders/:id`
1359
+
1360
+ ---
1361
+
1362
+ ### Agent
1363
+
1364
+ Stored in `agents.config.json`.
1365
+
1366
+ | Field | Type | Default | Description |
1367
+ | --------------- | ----------------- | ------- | -------------------------------------------- |
1368
+ | `id` | string (UUID) | Auto | |
1369
+ | `name` | string | — | Display name |
1370
+ | `description` | string | `""` | Human-readable description |
1371
+ | `provider` | string | — | `openai`, `anthropic`, `gemini`, or `ollama` |
1372
+ | `model` | string | — | Model ID |
1373
+ | `systemPrompt` | string | `""` | Agent's system instruction |
1374
+ | `tools` | string[] | `[]` | List of tool names to enable |
1375
+ | `temperature` | number | `0.7` | Sampling temperature |
1376
+ | `maxTokens` | integer | `2048` | Max tokens per LLM call |
1377
+ | `maxIterations` | integer | `10` | Max tool-call loop iterations |
1378
+ | `createdAt` | string (ISO 8601) | Auto | |
1379
+ | `updatedAt` | string (ISO 8601) | Auto | |
1380
+
1381
+ ---
1382
+
1383
+ ### AI Provider (ai.config.json)
1384
+
1385
+ | Field | Type | Description |
1386
+ | -------------- | -------- | ---------------------------------- |
1387
+ | `label` | string | Display name |
1388
+ | `apiKey` | string | API key (empty string if not set) |
1389
+ | `enabled` | boolean | Whether the provider is active |
1390
+ | `models` | string[] | Available model IDs |
1391
+ | `defaultModel` | string | Default model ID |
1392
+ | `baseUrl` | string | Only for Ollama — local server URL |
1393
+
1394
+ ---
1395
+
1396
+ ### Token Usage (ai.usage.json)
1397
+
1398
+ ```json
1399
+ {
1400
+ "totalRequests": 150,
1401
+ "totalInputTokens": 55000,
1402
+ "totalOutputTokens": 14000,
1403
+ "byProvider": {
1404
+ "openai": {
1405
+ "requests": 100,
1406
+ "inputTokens": 40000,
1407
+ "outputTokens": 10000
1408
+ },
1409
+ "anthropic": {
1410
+ "requests": 50,
1411
+ "inputTokens": 15000,
1412
+ "outputTokens": 4000
1413
+ }
1414
+ }
1415
+ }
1416
+ ```
1417
+
1418
+ ---
1419
+
1420
+ ## 6. AI / LLM Integration
1421
+
1422
+ ### Supported Providers
1423
+
1424
+ | Provider ID | Label | Requires API Key | Default Models |
1425
+ | ----------- | -------------- | ---------------- | ------------------------------------------------------------- |
1426
+ | `openai` | OpenAI | Yes | gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo |
1427
+ | `anthropic` | Anthropic | Yes | claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4-5-20251001 |
1428
+ | `gemini` | Google Gemini | Yes | gemini-2.0-flash, gemini-1.5-pro, gemini-1.5-flash |
1429
+ | `ollama` | Ollama (Local) | No | llama3.2, mistral, codellama, phi3 |
1430
+
1431
+ ### Configuration
1432
+
1433
+ Providers are configured via:
1434
+
1435
+ 1. **Admin panel -> AI Integration -> Providers tab:** Enable provider, paste API key, select default model.
1436
+ 2. **Direct file edit:** Edit `ai.config.json` in the project root.
1437
+
1438
+ Ollama requires a running local Ollama server. Default base URL: `http://localhost:11434`.
1439
+
1440
+ ### Token Usage Tracking
1441
+
1442
+ Every call to `POST /api/ai/generate`, `POST /api/ai/chat`, and `POST /api/agents/:id/run` records:
1443
+
1444
+ - Input token count
1445
+ - Output token count
1446
+ - Per-provider breakdown
1447
+
1448
+ Data is persisted to `ai.usage.json` and survives server restarts. Totals are visible in the admin dashboard.
1449
+
1450
+ ### AI Playground (Admin Panel)
1451
+
1452
+ - **Generate mode:** Single prompt with provider, model, system prompt, temperature, max tokens controls. Shows token count after each request.
1453
+ - **Chat mode:** Multi-turn conversation with scrollable history and persistent context.
1454
+ - **Session totals:** Cumulative token counts for the current browser session with a reset button.
1455
+
1456
+ ### Agent System
1457
+
1458
+ #### ReAct Loop
1459
+
1460
+ ```
1461
+ User Input
1462
+ |
1463
+ v
1464
+ LLM (receives task + tool definitions)
1465
+ |
1466
+ +-- Tool call requested?
1467
+ | |
1468
+ | +-- Execute tool
1469
+ | +-- Append result to conversation
1470
+ | +-- Loop back to LLM
1471
+ |
1472
+ +-- Final answer -> return output + all steps to caller
1473
+ ```
1474
+
1475
+ #### Agent Step Types
1476
+
1477
+ | Type | Description |
1478
+ | -------------- | ------------------------------------------- |
1479
+ | `thinking` | LLM reasoning text before invoking a tool |
1480
+ | `tool_call` | Name and arguments of the tool being called |
1481
+ | `tool_result` | Raw output from the tool execution |
1482
+ | `final_answer` | The LLM's conclusive response |
1483
+
1484
+ ---
1485
+
1486
+ ## 7. File Storage / Media Handling
1487
+
1488
+ ### Upload Process
1489
+
1490
+ 1. Client sends `POST /files/upload` as `multipart/form-data` with a `file` field and optional `visibility` field.
1491
+ 2. Server reads and buffers the stream, validates MIME type against the allowed list.
1492
+ 3. Checks the file size against `maxFileSizeMB` (default 10 MB).
1493
+ 4. Checks the uploader's total storage against `maxStoragePerUserMB` (default 100 MB).
1494
+ 5. Writes the file to disk as `<uuid><original-extension>` in the appropriate directory.
1495
+ 6. Inserts metadata into the `_files` SQLite table.
1496
+ 7. Returns the full file metadata record.
1497
+
1498
+ ### Access URLs
1499
+
1500
+ | Visibility | Download URL | Auth Required |
1501
+ | ---------- | ---------------------------------------------------- | ------------------------ |
1502
+ | `public` | `/files/:id/download` | No |
1503
+ | `private` | `/files/:id/download?token=<hmac>&expires=<unix-ts>` | Signed URL (HMAC-SHA256) |
1504
+
1505
+ Signed URLs are generated via `GET /files/:id/signed-url`. The default TTL is 1 hour, configurable via `signedUrlTTLSeconds` in config.
1506
+
1507
+ ### Storage Structure
1508
+
1509
+ ```
1510
+ <project-root>/
1511
+ storage/
1512
+ public/ <- Publicly downloadable files
1513
+ <uuid>.jpg
1514
+ <uuid>.png
1515
+ private/ <- Signed-URL-only files
1516
+ <uuid>.pdf
1517
+ <uuid>.docx
1518
+ ```
1519
+
1520
+ ### Size and Quota Limits
1521
+
1522
+ | Setting | Environment Variable | Default |
1523
+ | -------------------- | ----------------------------------- | ------- |
1524
+ | Max file size | `ROTIFEX_STORAGE_MAX_FILE_SIZE_MB` | 10 MB |
1525
+ | Max storage per user | Config only (`maxStoragePerUserMB`) | 100 MB |
1526
+
1527
+ ---
1528
+
1529
+ ## 8. Admin Panel Features
1530
+
1531
+ ### Dashboard
1532
+
1533
+ - **Stat cards:** Schemas, Total Records, Users, Files, Storage Used, Connected LLMs, Agents Created, Server Uptime, Server Status
1534
+ - **Schema Overview table:** model name, table name, record count per model
1535
+ - **Connected LLMs table:** provider name, request count, tokens in, tokens out, key status
1536
+
1537
+ ### Database Schemas
1538
+
1539
+ - View all defined models with their field names, types, and constraints
1540
+ - Create a new model via a field builder UI (add field name + type pairs)
1541
+ - Attempting to create a model with an existing name returns a conflict error
1542
+ - Delete a model (routes deactivate immediately; underlying data table is preserved)
1543
+
1544
+ ### User Management
1545
+
1546
+ - List all registered users: email, display name, role, creation date
1547
+ - Admin actions on user accounts
1548
+
1549
+ ### File Browser
1550
+
1551
+ - Browse all files (admins see all; users see their own)
1552
+ - Preview images inline
1553
+ - Download any file
1554
+ - Delete files (removes from disk and database)
1555
+
1556
+ ### AI Integration
1557
+
1558
+ **Providers tab:** Enable/disable providers, enter API keys (masked after save), set default model per provider.
1559
+
1560
+ **Playground tab:**
1561
+
1562
+ - Generate mode: prompt + provider/model/system/temp/maxTokens controls, token display after response
1563
+ - Chat mode: multi-turn conversation with message history
1564
+
1565
+ **Agents tab:**
1566
+
1567
+ - List all agents with name, provider, model, tools
1568
+ - Create agent form: name, description, provider, model, system prompt, tool checkboxes, temperature, max tokens, max iterations
1569
+ - Edit existing agents
1570
+ - Delete agents
1571
+ - Run agents interactively: enter a task, see reasoning steps in real time, view final output
1572
+
1573
+ **API Docs tab:** Built-in reference for all AI and agent endpoints.
1574
+
1575
+ ### Server Logs
1576
+
1577
+ - In-memory ring buffer of structured log entries
1578
+ - Filter by level (`info`, `warn`, `error`, `debug`)
1579
+ - Timestamps and log messages displayed in a table
1580
+
1581
+ ### Settings
1582
+
1583
+ Editable via admin panel — writes to `.env`:
1584
+
1585
+ | Variable | Description |
1586
+ | ----------------------------------- | ---------------------------- |
1587
+ | `JWT_SECRET` | Access token signing secret |
1588
+ | `JWT_REFRESH_SECRET` | Refresh token signing secret |
1589
+ | `ROTIFEX_PORT` | Server port |
1590
+ | `ROTIFEX_HOST` | Server bind host |
1591
+ | `ROTIFEX_CORS_ORIGIN` | Allowed CORS origin(s) |
1592
+ | `ROTIFEX_RATE_LIMIT_MAX` | Max requests per time window |
1593
+ | `ROTIFEX_LOG_LEVEL` | Log verbosity |
1594
+ | `ROTIFEX_STORAGE_MAX_FILE_SIZE_MB` | Max upload size in MB |
1595
+ | `ROTIFEX_STORAGE_SIGNED_URL_SECRET` | HMAC secret for signed URLs |
1596
+
1597
+ ---
1598
+
1599
+ ## 9. Error Handling
1600
+
1601
+ ### Error Response Format
1602
+
1603
+ ```json
1604
+ {
1605
+ "error": "Error Type",
1606
+ "message": "Human-readable description of what went wrong.",
1607
+ "statusCode": 400
1608
+ }
1609
+ ```
1610
+
1611
+ Validation errors may return an array for `message`:
1612
+
1613
+ ```json
1614
+ {
1615
+ "error": "Validation Error",
1616
+ "message": [{ "path": ["name"], "message": "Required" }],
1617
+ "statusCode": 400
1618
+ }
1619
+ ```
1620
+
1621
+ ### Common HTTP Error Codes
1622
+
1623
+ | Code | Meaning | Common Causes |
1624
+ | ----- | --------------------- | ------------------------------------------------------------------------------------- |
1625
+ | `400` | Bad Request | Missing required fields, invalid types, reserved model names, no fields to update |
1626
+ | `401` | Unauthorized | Missing, expired, or invalid JWT access token |
1627
+ | `403` | Forbidden | Non-admin accessing `/admin/api/*`, file access without ownership or valid signed URL |
1628
+ | `404` | Not Found | Unknown table name, record ID not found, agent not found, file not found |
1629
+ | `409` | Conflict | Email already registered, model name already exists |
1630
+ | `413` | Payload Too Large | File exceeds per-request size limit or per-user storage quota |
1631
+ | `500` | Internal Server Error | Unexpected server error, LLM provider failure |
1632
+
1633
+ ---
1634
+
1635
+ ## 10. Environment Configuration
1636
+
1637
+ ### Configuration Priority (highest to lowest)
1638
+
1639
+ 1. Shell environment variables
1640
+ 2. CLI flags (`--port`, `--host`, `--verbose`)
1641
+ 3. `config.json` (optional user overrides)
1642
+ 4. `config.default.json` (shipped defaults)
1643
+ 5. `.env` file (auto-loaded at startup)
1644
+
1645
+ ### Environment Variables Reference
1646
+
1647
+ | Variable | Default | Description |
1648
+ | ----------------------------------- | --------- | -------------------------------------------- |
1649
+ | `ROTIFEX_PORT` | `3000` | TCP port |
1650
+ | `ROTIFEX_HOST` | `0.0.0.0` | Bind address |
1651
+ | `ROTIFEX_CORS_ORIGIN` | `*` | Allowed CORS origin |
1652
+ | `ROTIFEX_RATE_LIMIT_MAX` | `100` | Max requests per rate-limit window |
1653
+ | `ROTIFEX_LOG_LEVEL` | `info` | Log level (`info`, `debug`, `warn`, `error`) |
1654
+ | `ROTIFEX_STORAGE_MAX_FILE_SIZE_MB` | `10` | Max upload size in MB |
1655
+ | `ROTIFEX_STORAGE_SIGNED_URL_SECRET` | auto | HMAC secret for signed file URLs |
1656
+ | `JWT_SECRET` | auto | Access token signing secret |
1657
+ | `JWT_REFRESH_SECRET` | auto | Refresh token signing secret |
1658
+
1659
+ > `JWT_SECRET`, `JWT_REFRESH_SECRET`, and `ROTIFEX_STORAGE_SIGNED_URL_SECRET` are auto-generated on first startup if absent and saved to `.env`.
1660
+
1661
+ ### Example `.env`
1662
+
1663
+ ```env
1664
+ ROTIFEX_PORT=3000
1665
+ ROTIFEX_HOST=0.0.0.0
1666
+ ROTIFEX_CORS_ORIGIN=https://myapp.com
1667
+ ROTIFEX_RATE_LIMIT_MAX=200
1668
+ ROTIFEX_LOG_LEVEL=info
1669
+ ROTIFEX_STORAGE_MAX_FILE_SIZE_MB=25
1670
+ JWT_SECRET=replace-with-a-long-random-string
1671
+ JWT_REFRESH_SECRET=replace-with-another-long-random-string
1672
+ ROTIFEX_STORAGE_SIGNED_URL_SECRET=replace-with-yet-another-secret
1673
+ ```
1674
+
1675
+ ---
1676
+
1677
+ ## 11. Deployment
1678
+
1679
+ ### Requirements
1680
+
1681
+ - Node.js 18 or later
1682
+ - npm 9 or later
1683
+
1684
+ ### Setup
1685
+
1686
+ ```bash
1687
+ git clone <repo-url>
1688
+ cd rotifex
1689
+ npm install
1690
+ ```
1691
+
1692
+ ### Running in Development
1693
+
1694
+ ```bash
1695
+ # Default port 3000
1696
+ npx rotifex start
1697
+
1698
+ # Custom port
1699
+ npx rotifex start --port 4000
1700
+
1701
+ # Custom host
1702
+ npx rotifex start --host 127.0.0.1
1703
+
1704
+ # Verbose (debug) logging
1705
+ npx rotifex start --verbose
1706
+ ```
1707
+
1708
+ ### Build the Admin Dashboard
1709
+
1710
+ ```bash
1711
+ npm run build:admin
1712
+ ```
1713
+
1714
+ Output is written to `admin/dist/`. The server automatically serves it at `/` when the directory exists.
1715
+
1716
+ ### Production Setup
1717
+
1718
+ **1. Set secrets in environment:**
1719
+
1720
+ ```bash
1721
+ export JWT_SECRET="$(openssl rand -hex 32)"
1722
+ export JWT_REFRESH_SECRET="$(openssl rand -hex 32)"
1723
+ export ROTIFEX_STORAGE_SIGNED_URL_SECRET="$(openssl rand -hex 32)"
1724
+ export ROTIFEX_CORS_ORIGIN="https://yourfrontend.com"
1725
+ ```
1726
+
1727
+ **2. Build the admin dashboard:**
1728
+
1729
+ ```bash
1730
+ npm run build:admin
1731
+ ```
1732
+
1733
+ **3. Use a process manager:**
1734
+
1735
+ ```bash
1736
+ npm install -g pm2
1737
+ pm2 start "npx rotifex start" --name rotifex
1738
+ pm2 save
1739
+ pm2 startup
1740
+ ```
1741
+
1742
+ **4. Reverse proxy (Nginx example):**
1743
+
1744
+ ```nginx
1745
+ server {
1746
+ listen 80;
1747
+ server_name api.yourapp.com;
1748
+
1749
+ location / {
1750
+ proxy_pass http://127.0.0.1:3000;
1751
+ proxy_set_header Host $host;
1752
+ proxy_set_header X-Real-IP $remote_addr;
1753
+ proxy_set_header X-Forwarded-Proto $scheme;
1754
+ }
1755
+ }
1756
+ ```
1757
+
1758
+ **5. Backup the database:**
1759
+
1760
+ ```bash
1761
+ # SQLite database file — copy it regularly
1762
+ cp rotifex.db rotifex.db.backup
1763
+ ```
1764
+
1765
+ ---
1766
+
1767
+ ## 12. Example Workflows
1768
+
1769
+ ### Registering a User and Logging In
1770
+
1771
+ ```bash
1772
+ # Register
1773
+ curl -X POST http://localhost:3000/auth/register \
1774
+ -H "Content-Type: application/json" \
1775
+ -d '{"email":"jane@example.com","password":"secure123","display_name":"Jane"}'
1776
+
1777
+ # Login — save the returned accessToken
1778
+ curl -X POST http://localhost:3000/auth/login \
1779
+ -H "Content-Type: application/json" \
1780
+ -d '{"email":"jane@example.com","password":"secure123"}'
1781
+
1782
+ # Store token
1783
+ export TOKEN="<accessToken from response>"
1784
+ ```
1785
+
1786
+ ---
1787
+
1788
+ ### Defining a Model and Using the CRUD API
1789
+
1790
+ ```bash
1791
+ # 1. Create model (admin role required)
1792
+ curl -X POST http://localhost:3000/admin/api/schema \
1793
+ -H "Content-Type: application/json" \
1794
+ -H "Authorization: Bearer $TOKEN" \
1795
+ -d '{
1796
+ "name": "Product",
1797
+ "fields": {
1798
+ "name": {"type": "string", "required": true},
1799
+ "price": "number",
1800
+ "in_stock": "boolean"
1801
+ }
1802
+ }'
1803
+
1804
+ # 2. Create a record
1805
+ curl -X POST http://localhost:3000/api/products \
1806
+ -H "Content-Type: application/json" \
1807
+ -d '{"name":"Widget","price":9.99,"in_stock":true}'
1808
+
1809
+ # 3. List with sort and filter
1810
+ curl "http://localhost:3000/api/products?sort=price&order=ASC&in_stock=1"
1811
+
1812
+ # 4. Get one
1813
+ curl http://localhost:3000/api/products/<id>
1814
+
1815
+ # 5. Update
1816
+ curl -X PUT http://localhost:3000/api/products/<id> \
1817
+ -H "Content-Type: application/json" \
1818
+ -d '{"price":14.99}'
1819
+
1820
+ # 6. Delete
1821
+ curl -X DELETE http://localhost:3000/api/products/<id>
1822
+ ```
1823
+
1824
+ ---
1825
+
1826
+ ### Uploading and Downloading Files
1827
+
1828
+ ```bash
1829
+ # Upload a public file
1830
+ curl -X POST http://localhost:3000/files/upload \
1831
+ -H "Authorization: Bearer $TOKEN" \
1832
+ -F "file=@/path/to/photo.jpg" \
1833
+ -F "visibility=public"
1834
+
1835
+ # Download public file (no auth needed)
1836
+ curl http://localhost:3000/files/<id>/download -o photo.jpg
1837
+
1838
+ # Upload a private file
1839
+ curl -X POST http://localhost:3000/files/upload \
1840
+ -H "Authorization: Bearer $TOKEN" \
1841
+ -F "file=@/path/to/document.pdf" \
1842
+ -F "visibility=private"
1843
+
1844
+ # Get a signed URL for the private file
1845
+ curl http://localhost:3000/files/<id>/signed-url \
1846
+ -H "Authorization: Bearer $TOKEN"
1847
+
1848
+ # Download using the signed URL
1849
+ curl "<signed-url>" -o document.pdf
1850
+ ```
1851
+
1852
+ ---
1853
+
1854
+ ### Calling an AI Model
1855
+
1856
+ ```bash
1857
+ # Generate a completion
1858
+ curl -X POST http://localhost:3000/api/ai/generate \
1859
+ -H "Content-Type: application/json" \
1860
+ -d '{
1861
+ "provider": "openai",
1862
+ "model": "gpt-4o",
1863
+ "prompt": "Write a one-sentence tagline for a productivity app.",
1864
+ "maxTokens": 60
1865
+ }'
1866
+
1867
+ # Multi-turn chat
1868
+ curl -X POST http://localhost:3000/api/ai/chat \
1869
+ -H "Content-Type: application/json" \
1870
+ -d '{
1871
+ "provider": "anthropic",
1872
+ "model": "claude-sonnet-4-6",
1873
+ "messages": [
1874
+ {"role": "user", "content": "What is the capital of France?"}
1875
+ ]
1876
+ }'
1877
+ ```
1878
+
1879
+ ---
1880
+
1881
+ ### Creating and Running an AI Agent
1882
+
1883
+ ```bash
1884
+ # 1. Create an agent (admin required)
1885
+ curl -X POST http://localhost:3000/admin/api/agents \
1886
+ -H "Content-Type: application/json" \
1887
+ -H "Authorization: Bearer $TOKEN" \
1888
+ -d '{
1889
+ "name": "Math Helper",
1890
+ "provider": "openai",
1891
+ "model": "gpt-4o",
1892
+ "systemPrompt": "You are a precise math assistant. Use the calculator tool for all computations.",
1893
+ "tools": ["calculate"],
1894
+ "temperature": 0.2,
1895
+ "maxIterations": 5
1896
+ }'
1897
+
1898
+ # 2. Run the agent
1899
+ curl -X POST http://localhost:3000/api/agents/<id>/run \
1900
+ -H "Content-Type: application/json" \
1901
+ -d '{"input": "What is (144 / 12) * 7.5 plus 33?"}'
1902
+ ```
1903
+
1904
+ ---
1905
+
1906
+ ### Refreshing an Expired Token
1907
+
1908
+ ```bash
1909
+ curl -X POST http://localhost:3000/auth/refresh \
1910
+ -H "Content-Type: application/json" \
1911
+ -d '{"refreshToken":"<your-refresh-token>"}'
1912
+ ```
1913
+
1914
+ ---
1915
+
1916
+ ## 13. Notes for Documentation Generators
1917
+
1918
+ ### Docusaurus
1919
+
1920
+ - Place this file in `docs/` as `intro.md`.
1921
+ - Split by `##` heading into separate files under `docs/api/`, `docs/guides/`, `docs/features/` for large doc sites.
1922
+ - Add `sidebar_label` and `sidebar_position` frontmatter to each split file.
1923
+ - All code blocks use fenced syntax compatible with Docusaurus's Prism highlighter.
1924
+ - Tables use standard GFM pipe syntax — no conversion needed.
1925
+
1926
+ ### Mintlify
1927
+
1928
+ - Each `##` section maps to a separate `.mdx` page.
1929
+ - API endpoint documentation can be supplemented by an `openapi.yaml` file generated from §3.
1930
+ - Use `<Note>`, `<Warning>`, and `<Tip>` components for callouts when converting to Mintlify MDX.
1931
+
1932
+ ### GitBook
1933
+
1934
+ - Import this file directly. GitBook renders all standard Markdown including tables, code blocks, and nested lists.
1935
+ - Use GitBook's `{% hint style="info" %}` blocks for the notes sections if converting to native GitBook format.
1936
+
1937
+ ### Swagger / OpenAPI Mapping
1938
+
1939
+ This document contains enough information to produce a complete `openapi.yaml`. Use this mapping:
1940
+
1941
+ | Section | OpenAPI Component |
1942
+ | -------------------- | ------------------------------------------------ |
1943
+ | §3.2 Auth endpoints | `paths` under `/auth/*` |
1944
+ | §3.3 CRUD endpoints | `paths` under `/api/{table}` |
1945
+ | §3.4 File endpoints | `paths` under `/files/*` |
1946
+ | §3.5 AI endpoints | `paths` under `/api/ai/*` |
1947
+ | §3.6 Agent endpoints | `paths` under `/api/agents/*` |
1948
+ | §3.7 Admin endpoints | `paths` under `/admin/api/*` |
1949
+ | §5 Data Structures | `components/schemas` |
1950
+ | §9 Error codes | `components/responses` |
1951
+ | §4 Auth headers | `components/securitySchemes` (BearerAuth, HS256) |
1952
+
1953
+ ### General Formatting Notes
1954
+
1955
+ - All code blocks specify a language (`bash`, `json`, `nginx`) for syntax highlighting.
1956
+ - Headings use a strict hierarchy (`#` -> `##` -> `###` -> `####`) for correct TOC generation.
1957
+ - Internal anchor links use lowercase slugs compatible with GitHub, Docusaurus, Mintlify, and GitBook.
1958
+ - No HTML tags are used — the file is pure Markdown for maximum portability.