[Intum Pomoc](https://intum.pl/pomoc.md) / [API](https://intum.pl/pomoc/noe/api.md)

# [Bazy danych Noe](https://intum.pl/pomoc/noe/api/bazy-danych.md)

Tworzenie i konfiguracja elastycznych baz JSON oraz operacje na rekordach - przez API i przez
publiczne metody (dla frontendów aplikacji Noe, bez autoryzacji).

**Autoryzacja:** `Authorization: Bearer TOKEN` (poza metodami publicznymi)
**Content-Type:** `application/json; charset=utf-8`

## API Endpoints

| Metoda | Ścieżka | Opis |
|--------|---------|------|
| GET | `/noe/dbs.json` | Lista baz |
| GET | `/noe/dbs/:id.json` | Szczegóły bazy (`:id` = id lub code) |
| POST | `/noe/dbs.json` | Utworzenie bazy |
| PATCH | `/noe/dbs/:id.json` | Aktualizacja bazy |
| DELETE | `/noe/dbs/:id.json` | Usunięcie bazy (z rekordami!) |
| PATCH | `/noe/dbs/:id/replace.json` | Częściowa edycja pól `fields`/`schema`/`public_methods` |
| GET | `/noe/dbs/:db_id/records.json` | Lista rekordów |
| POST | `/noe/dbs/:db_id/records.json` | Utworzenie rekordu (z `table_name` dla baz wielotabelowych) |
| PATCH | `/noe/dbs/:db_id/records/:id.json` | Aktualizacja rekordu |
| DELETE | `/noe/dbs/:db_id/records/:id.json` | Usunięcie rekordu |
| GET | `/noe/dbs/:db_id/search.json` | Wyszukiwanie rekordów (`table_name`, `q`, `search_in`, `filter[]`, `fields`, `sort`) |
| GET/POST | `/noe/db/:db_code/:method_name` | **Metody publiczne** (bez autoryzacji, wg `public_methods`) |

## Utworzenie bazy

```json
POST /noe/dbs.json
{
  "db": {
    "name": "Todo",
    "code": "todo",
    "app_id": "uuid-aplikacji (opcjonalnie)",
    "schema": {
      "fields": {
        "title": { "type": "string", "required": true },
        "done": { "type": "integer" }
      },
      "indexes": {
        "idx_str_1": "title",
        "idx_int_1": "done"
      }
    }
  }
}
```

| Pole | Typ | Wymagane | Opis |
|------|-----|----------|------|
| `name` | string | tak | Nazwa bazy |
| `code` | string | nie | Unikalny slug do URL-i (np. "todo" -> `/noe/db/todo/...`) |
| `app_id` | uuid | nie | Powiązana aplikacja Noe (widoczne w "Powiązaniach") |
| `schema` | object | nie | Pola + mapowanie indeksów |
| `public_methods` | object | nie | Operacje wystawione bez autoryzacji (niżej) |

### Typy pól w schema

`string`, `text`, `integer`, `number`, `boolean`, `datetime`, `date`, `time`

### Indeksy (szybkie wyszukiwanie/filtrowanie)

| Indeks | Typ |
|--------|-----|
| `idx_str_1..3` | string (max 255 znaków) |
| `idx_int_1..2` | bigint |
| `idx_time_1..2` | datetime |

Dwa sposoby definiowania indeksów:

- `"indexes": { "idx_str_1": "title" }` - jawne mapowanie pole -> kolumna indeksu (starszy format)
- `"indexed_fields": ["title", "done"]` - lista pól, mapowanie na `idx_*` liczone automatycznie
  z typu pola (string/text -> `idx_str_*`, integer/number -> `idx_int_*`, date/time/datetime -> `idx_time_*`);
  pola, które nie mieszczą się w limicie kolumn, po prostu nie są indeksowane

Pola nieindeksowane też są przeszukiwalne (po `data->>'pole'` w JSONB), tylko wolniej - indeksuj to,
po czym naprawdę filtrujesz i sortujesz.

## Baza wielotabelowa (`tables`)

Jedna baza Noe może trzymać kilka "tabel" - każda z własnymi polami i własnymi indeksami.
Rekordy rozdziela kolumna `table_name`.

```json
POST /noe/dbs.json
{
  "db": {
    "name": "Sklep",
    "code": "sklep",
    "schema": {
      "tables": {
        "users": {
          "fields": {
            "name": { "type": "string", "required": true },
            "email": "string",
            "age": "number"
          },
          "indexed_fields": ["name", "email"]
        },
        "orders": {
          "fields": {
            "order_number": "string",
            "user_id": "number",
            "total_amount": "number"
          },
          "indexed_fields": ["order_number", "user_id"]
        }
      }
    }
  }
}
```

Zasady:

- `"fields"` przyjmuje skrót `"email": "string"` albo pełny zapis `{ "type": "string", "required": true }`
- indeksy są liczone **per tabela** - `name` w `users` i `order_number` w `orders` oba trafiają
  do `idx_str_1` (to ta sama kolumna, ale rekordy różnych tabel nigdy się nie mieszają, bo zapytania
  są zawsze zawężone do `table_name`). Każda tabela ma więc pełny limit: 3 stringi, 2 inty, 2 daty
- `required` obowiązuje tylko w obrębie swojej tabeli - pole wymagane w `users` nie blokuje zapisu w `orders`
- typy pól są walidowane globalnie (ta sama lista typów co w bazie jednotabelowej)
- format jednotabelowy (`fields` + `indexes`/`indexed_fields` na górnym poziomie) działa bez zmian;
  rekordy takiej bazy mają `table_name` = `null`
- nie mieszaj formatów - albo `tables`, albo `fields` na górnym poziomie

## Rekordy (prywatne - wymaga autoryzacji)

```json
POST /noe/dbs/todo/records.json
{ "record": { "data": { "title": "Zadanie", "done": 0 } } }
```

W bazie wielotabelowej podaj `table_name` - decyduje, wg której tabeli rekord jest walidowany
i indeksowany:

```json
POST /noe/dbs/sklep/records.json
{ "record": { "table_name": "orders", "data": { "order_number": "Z/2026/01", "user_id": 7 } } }
```

Aktualizacja: `PATCH /noe/dbs/todo/records/:id.json` z `{ "record": { "data": { "done": 1 } } }`
(w bazie wielotabelowej przekaż też `table_name`, inaczej rekord wróci do tabeli `null`).
Wyszukiwanie: `GET /noe/dbs/todo/search.json?q=fraza&sort=-created_at&filter[done]=0&limit=50`.

## Wyszukiwanie (`/noe/dbs/:db_id/search.json`)

| Parametr | Opis |
|----------|------|
| `table_name` | tabela w bazie wielotabelowej - **wymagany**, gdy schema ma `tables` |
| `q` | szukana fraza; `*` i `%` działają jak wildcard (`ab*` -> `ab%`), bez nich szuka fragmentu |
| `search_in` | pola do przeszukania (lista po przecinku); domyślnie pola indeksowane, a przy braku schematu całe `data` |
| `filter[pole]` | dokładne dopasowanie z konwersją typu ze schematu; wartość z `*`/`%` przechodzi na `ILIKE` |
| `sort` | pole sortowania, prefiks `-` = DESC (`id`, `created_at`, `updated_at`, pole indeksowane lub pole z `data`) |
| `fields` | które pola zwrócić (poza `id`, `created_at`, `updated_at`, które są zawsze) |
| `limit` / `offset` | paginacja (`limit` 1-100, domyślnie 20) |

```
GET /noe/dbs/sklep/search.json?table_name=users&q=ali*&search_in=name&fields=name,email&sort=-created_at&limit=20
```

Odpowiedź to płaska tablica rekordów (bez `total`): `[{ "id": ..., "created_at": ..., "updated_at": ..., "name": "Alice", ... }]`.

Brakujące lub nieznane `table_name` w bazie wielotabelowej daje `400` z listą dostępnych tabel:

```json
{ "error": "table_name is required for multi-table database. Available: users, orders",
  "available_tables": ["users", "orders"] }
```

## Metody publiczne (bez autoryzacji)

`public_methods` na bazie wystawia operacje pod `POST /noe/db/:db_code/:method_name` - główny
sposób, w jaki frontendy aplikacji Noe czytają/zapisują dane (bez tokenów w kodzie appki).

| Operacja | Parametry |
|----------|-----------|
| `list` | `limit`, `offset` |
| `get` | `id` |
| `create` | `{ "data": {...} }` |
| `update` | `{ "id": ..., "data": {...} }` |
| `delete` | `{ "id": ... }` |
| `search` | `q`, `limit`, `offset`, `sort`, `table_name` |

Konfiguracja per operacja (`true` = bez ograniczeń, albo obiekt):

| Opcja | Opis |
|-------|------|
| `allowed_fields` | tylko te pola przejdą z requestu (reszta ignorowana) |
| `required_fields` | wymagane pola - błąd gdy brakuje |
| `default_values` | wartości dopisywane automatycznie (nadpisują brakujące) |
| `auto_fields` | pola wypełniane przez serwer - nie do podrobienia przez klienta |
| `allowed_params` | dozwolone parametry (dla `search`) |
| `scope_by` | filtrowanie wyników po polu (każdy widzi swoje rekordy) |

**Zmienne w `auto_fields`:** `{ip}` (adres IP), `{cookie_key}` (token per przeglądarka, cookie 1 mies.),
`{user_id}` / `{user_name}` (zalogowany użytkownik; puste bez sesji).

Przykład - publiczna lista + kontrolowany zapis:

```json
{
  "db": {
    "public_methods": {
      "list": { "scope_by": "user_ip" },
      "create": {
        "allowed_fields": ["title", "done"],
        "required_fields": ["title"],
        "default_values": { "done": 0 },
        "auto_fields": { "user_ip": "{ip}", "author": "{user_name}" }
      }
    }
  }
}
```

Uwagi:

- `auto_fields` ZAWSZE nadpisuje pole wartością serwera - jeśli rekordy zapisuje też backend
  (np. flow przez konektor), nie używaj auto_fields na polach, które backend ustawia sam
- odpowiedzi list/get/create: `{ "id": ..., "data": {...}, "created_at": ..., "updated_at": ... }`
  (list: tablica, najnowsze pierwsze); czas utworzenia rekordu = `created_at`
- `scope_by` dobiera zmienną z `auto_fields` tego pola (IP albo cookie_key)
- **bazy wielotabelowe:** `table_name` przechodzi tylko przez publiczne `search`. `list`/`get`/`create`/
  `update`/`delete` nie znają tabel - `create` zapisze rekord z `table_name` = `null` (bez walidacji
  i indeksów tabeli), a `list`/`get` widzą rekordy ze wszystkich tabel wymieszane. Frontend appki,
  który ma czytać/pisać konkretną tabelę, korzystaj z prywatnego API rekordów (z tokenem),
  z publicznego `search` z `table_name`, albo trzymaj tabelę jako osobną bazę jednotabelową
- `filter[pole]` i `fields` obsługuje tylko prywatny `/noe/dbs/:db_id/search.json` - w publicznym
  `search` zostają `q`, `sort`, `table_name`, `limit`, `offset`

## Replace API (częściowa edycja)

```json
PATCH /noe/dbs/:id/replace.json
{ "field": "public_methods", "old_string": "\"list\": true", "new_string": "\"list\": { \"scope_by\": \"user_ip\" }" }
```

Dozwolone pola: `fields`, `schema`, `public_methods`. Opcja `replace_all: true` dla wielu wystąpień.

## Powiązane

- [noe_api](https://intum.intum.pl/kb/intum-kb/noe/api/aplikacje) - API aplikacji Noe (akcje, silniki, źródła)
- [noe_app_example_weather](https://intum.intum.pl/kb/intum-kb/noe/api/przyklad-aplikacja-pogoda) - kompletny przykład: appka + baza z publicznymi metodami + flow + cron
- [common_api](https://app.intum.pl/noe/prompt/common_api.md) - wspólne zasady API