From b312dc56e3a9b91400e51ed453738b2841f771e0 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Sun, 22 Feb 2026 23:16:37 +0100 Subject: [PATCH] feat: Add API documentation and permissions configuration --- .claude/settings.local.json | 10 + .vscode/ftp-kr.sync.cache.json | 224 +++++++++++--- DOCS/API.md | 517 +++++++++++++++++++++++++++++++++ 3 files changed, 704 insertions(+), 47 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 DOCS/API.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..b55705c --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(python3:*)", + "Bash(py:*)", + "Bash(npm ls:*)", + "WebFetch(domain:github.com)" + ] + } +} diff --git a/.vscode/ftp-kr.sync.cache.json b/.vscode/ftp-kr.sync.cache.json index 1db7d95..6de735d 100644 --- a/.vscode/ftp-kr.sync.cache.json +++ b/.vscode/ftp-kr.sync.cache.json @@ -7,38 +7,57 @@ "size": 1508, "lmtime": 1771460522111, "modified": false + }, + "migrate.php": { + "type": "-", + "size": 1357, + "lmtime": 1771692294431, + "modified": false } }, "bootstrap": { "app.php": { "type": "-", - "size": 869, - "lmtime": 1771459558621, + "size": 877, + "lmtime": 1771692620052, "modified": false } }, "composer.json": { "type": "-", - "size": 330, - "lmtime": 1771460587937, + "size": 368, + "lmtime": 1771691973809, "modified": false }, "config": { "app.php": { "type": "-", - "size": 622, - "lmtime": 1771460431365, + "size": 690, + "lmtime": 1771692227139, "modified": false }, "auth.php": { "type": "-", "size": 289, - "lmtime": 1771459566674, + "lmtime": 1771691433920, + "modified": false + }, + "database.php": { + "type": "-", + "size": 425, + "lmtime": 1771691899980, "modified": false } }, "database": { - "migrations": {}, + "migrations": { + "20260221_000001_create_users_table.sql": { + "type": "-", + "size": 333, + "lmtime": 1771691952235, + "modified": false + } + }, "seeders": {} }, "DOCS": { @@ -48,6 +67,12 @@ "lmtime": 1771460831573, "modified": false }, + "FRONTEND_STANDARDS.md": { + "type": "-", + "size": 766, + "lmtime": 1771691123295, + "modified": false + }, "PLAN_PROJEKTU.md": { "type": "-", "size": 5181, @@ -59,12 +84,30 @@ "size": 132, "lmtime": 1771460631339, "modified": false + }, + "MIGRATIONS.md": { + "type": "-", + "size": 599, + "lmtime": 1771692333726, + "modified": false } }, + ".env": { + "type": "-", + "size": 325, + "lmtime": 1771692631760, + "modified": false + }, ".env.example": { "type": "-", - "size": 222, - "lmtime": 1771459546785, + "size": 229, + "lmtime": 1771692627769, + "modified": false + }, + ".gitignore": { + "type": "-", + "size": 82, + "lmtime": 1771460725903, "modified": false }, ".htaccess": { @@ -1235,8 +1278,8 @@ }, "package.json": { "type": "-", - "size": 671, - "lmtime": 1771460684115, + "size": 1005, + "lmtime": 1771691140832, "modified": false }, "package-lock.json": { @@ -1250,8 +1293,8 @@ "css": { "app.css": { "type": "-", - "size": 2295, - "lmtime": 1771460692659, + "size": 5370, + "lmtime": 1771692521342, "modified": false }, "app.css.map": { @@ -1262,8 +1305,8 @@ }, "login.css": { "type": "-", - "size": 2987, - "lmtime": 1771460693108, + "size": 4504, + "lmtime": 1771692521824, "modified": false }, "login.css.map": { @@ -1271,10 +1314,27 @@ "size": 958, "lmtime": 1771460670209, "modified": false + }, + "modules": { + "jquery-alerts.css": { + "type": "-", + "size": 768, + "lmtime": 1771691151859, + "modified": false + } } }, "img": {}, - "js": {} + "js": { + "modules": { + "jquery-alerts.js": { + "type": "-", + "size": 1573, + "lmtime": 1771691107503, + "modified": false + } + } + } }, "index.php": { "type": "-", @@ -1299,17 +1359,39 @@ "lang": { "pl.php": { "type": "-", - "size": 1393, - "lmtime": 1771460496306, + "size": 3819, + "lmtime": 1771692642685, "modified": false } }, + "scss": { + "app.scss": { + "type": "-", + "size": 3651, + "lmtime": 1771692517226, + "modified": false + }, + "login.scss": { + "type": "-", + "size": 2507, + "lmtime": 1771691089256, + "modified": false + }, + "shared": { + "_ui-components.scss": { + "type": "-", + "size": 3044, + "lmtime": 1771692265024, + "modified": false + } + } + }, "views": { "auth": { "login.php": { "type": "-", - "size": 1521, - "lmtime": 1771460485980, + "size": 1649, + "lmtime": 1771691098142, "modified": false } }, @@ -1324,8 +1406,8 @@ "layouts": { "app.php": { "type": "-", - "size": 1014, - "lmtime": 1771460470614, + "size": 1984, + "lmtime": 1771692509348, "modified": false }, "auth.php": { @@ -1334,28 +1416,46 @@ "lmtime": 1771460477300, "modified": false } + }, + "users": { + "index.php": { + "type": "-", + "size": 2379, + "lmtime": 1771691646298, + "modified": false + } + }, + "settings": { + "database.php": { + "type": "-", + "size": 3470, + "lmtime": 1771692300314, + "modified": false + } } }, - "scss": { - "app.scss": { - "type": "-", - "size": 2831, - "lmtime": 1771460151261, - "modified": false - }, - "login.scss": { - "type": "-", - "size": 3671, - "lmtime": 1771460219578, - "modified": false + "modules": { + "jquery-alerts": { + "jquery-alerts.js": { + "type": "-", + "size": 1573, + "lmtime": 1771691107503, + "modified": false + }, + "jquery-alerts.scss": { + "type": "-", + "size": 945, + "lmtime": 1771691116267, + "modified": false + } } } }, "routes": { "web.php": { "type": "-", - "size": 1579, - "lmtime": 1771460461885, + "size": 2243, + "lmtime": 1771692238609, "modified": false } }, @@ -1363,10 +1463,24 @@ "Core": { "Application.php": { "type": "-", - "size": 4572, - "lmtime": 1771460438825, + "size": 5346, + "lmtime": 1771692614516, "modified": false }, + "Database": { + "ConnectionFactory.php": { + "type": "-", + "size": 1175, + "lmtime": 1771691906777, + "modified": false + }, + "Migrator.php": { + "type": "-", + "size": 4739, + "lmtime": 1771692444377, + "modified": false + } + }, "Http": { "Request.php": { "type": "-", @@ -1456,8 +1570,30 @@ }, "AuthService.php": { "type": "-", - "size": 1584, - "lmtime": 1771459658223, + "size": 1599, + "lmtime": 1771692611409, + "modified": false + } + }, + "Users": { + "UserRepository.php": { + "type": "-", + "size": 2878, + "lmtime": 1771691981226, + "modified": false + }, + "UsersController.php": { + "type": "-", + "size": 3359, + "lmtime": 1771691595738, + "modified": false + } + }, + "Settings": { + "SettingsController.php": { + "type": "-", + "size": 2609, + "lmtime": 1771692449213, "modified": false } } @@ -1511,12 +1647,6 @@ } }, "tmp": {} - }, - ".gitignore": { - "type": "-", - "size": 82, - "lmtime": 1771460725903, - "modified": false } } }, diff --git a/DOCS/API.md b/DOCS/API.md new file mode 100644 index 0000000..1b57aa9 --- /dev/null +++ b/DOCS/API.md @@ -0,0 +1,517 @@ +# shopPRO REST API + +REST API do integracji z ordersPRO i innymi systemami zewnetrznymi. + +## Autentykacja + +Kazde zapytanie wymaga headera `X-Api-Key` z kluczem API. + +``` +X-Api-Key: {klucz_api} +``` + +Klucz przechowywany jest w `pp_settings` jako parametr `api_key`. API jest stateless (bez sesji). + +## Format odpowiedzi + +### Sukces (HTTP 200) +```json +{ + "status": "ok", + "data": { ... } +} +``` + +### Blad +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Invalid or missing API key" +} +``` + +Kody bledow: +| Kod | HTTP | Opis | +|-----|------|------| +| `UNAUTHORIZED` | 401 | Brak lub nieprawidlowy klucz API | +| `BAD_REQUEST` | 400 | Brakujace lub niepoprawne parametry | +| `NOT_FOUND` | 404 | Nie znaleziono zasobu/endpointu/akcji | +| `METHOD_NOT_ALLOWED` | 405 | Nieprawidlowa metoda HTTP | +| `INTERNAL_ERROR` | 500 | Blad wewnetrzny serwera | + +## Endpointy + +### Zamowienia + +#### Lista zamowien +``` +GET api.php?endpoint=orders&action=list +``` + +Parametry filtrowania (opcjonalne): +| Parametr | Typ | Opis | +|----------|-----|------| +| `status` | int | Filtruj po statusie zamowienia | +| `paid` | int (0/1) | Filtruj po statusie platnosci | +| `date_from` | date (YYYY-MM-DD) | Zamowienia od daty | +| `date_to` | date (YYYY-MM-DD) | Zamowienia do daty | +| `updated_since` | datetime (YYYY-MM-DD HH:MM:SS) | Zamowienia zmodyfikowane od podanej daty (klucz do pollingu) | +| `number` | string | Szukaj po numerze zamowienia | +| `client` | string | Szukaj po imieniu, nazwisku lub emailu klienta | +| `page` | int | Numer strony (domyslnie 1) | +| `per_page` | int | Wynikow na strone (domyslnie 50, max 100) | + +Odpowiedz: +```json +{ + "status": "ok", + "data": { + "items": [ + { + "id": 42, + "number": "2026/02/001", + "date_order": "2026-02-19 10:30:00", + "updated_at": "2026-02-19 12:00:00", + "status": 4, + "paid": 1, + "client_name": "Jan", + "client_surname": "Kowalski", + "client_email": "jan@example.com", + "client_phone": "111222333", + "client_street": "Testowa 1", + "client_postal_code": "00-000", + "client_city": "Warszawa", + "firm_name": null, + "firm_nip": null, + "transport": "Kurier DPD", + "transport_cost": 15.00, + "payment_method": "Przelew bankowy", + "summary": 150.00 + } + ], + "total": 1, + "page": 1, + "per_page": 50 + } +} +``` + +#### Szczegoly zamowienia +``` +GET api.php?endpoint=orders&action=get&id={order_id} +``` + +Zwraca pelne dane zamowienia z produktami i historia statusow. + +#### Zmiana statusu zamowienia +``` +PUT api.php?endpoint=orders&action=change_status&id={order_id} +Content-Type: application/json + +{ + "status_id": 5, + "send_email": true +} +``` + +Odpowiedz: +```json +{ + "status": "ok", + "data": { + "order_id": 42, + "status_id": 5, + "changed": true + } +} +``` + +#### Oznacz jako oplacone +``` +PUT api.php?endpoint=orders&action=set_paid&id={order_id} +``` + +Opcjonalnie w body: `{"send_email": true}` + +#### Oznacz jako nieoplacone +``` +PUT api.php?endpoint=orders&action=set_unpaid&id={order_id} +``` + +### Produkty + +#### Lista produktow +``` +GET api.php?endpoint=products&action=list +``` + +Parametry filtrowania (opcjonalne): +| Parametr | Typ | Opis | +|----------|-----|------| +| `search` | string | Szukaj po nazwie, EAN lub SKU | +| `status` | int (0/1) | Filtruj po statusie (1 = aktywny, 0 = nieaktywny) | +| `promoted` | int (0/1) | Filtruj po promocji | +| `attribute_{id}` | int | Filtruj po atrybucie — `attribute_1=3` oznacza atrybut 1 = wartosc 3 (wiele filtrow AND) | +| `sort` | string | Sortuj po: id, name, price_brutto, status, promoted, quantity (domyslnie id) | +| `sort_dir` | string | Kierunek: ASC lub DESC (domyslnie DESC) | +| `page` | int | Numer strony (domyslnie 1) | +| `per_page` | int | Wynikow na strone (domyslnie 50, max 100) | + +Odpowiedz: +```json +{ + "status": "ok", + "data": { + "items": [ + { + "id": 1, + "sku": "PROD-001", + "ean": "5901234123457", + "name": "Produkt testowy", + "price_brutto": 99.99, + "price_brutto_promo": null, + "price_netto": 81.29, + "price_netto_promo": null, + "quantity": 10, + "status": 1, + "promoted": 0, + "vat": 23, + "weight": 0.5, + "main_image": "product1.jpg", + "date_add": "2026-01-15 10:00:00", + "date_modify": "2026-02-19 12:00:00" + } + ], + "total": 1, + "page": 1, + "per_page": 50 + } +} +``` + +#### Szczegoly produktu +``` +GET api.php?endpoint=products&action=get&id={product_id} +``` + +Zwraca pelne dane produktu z jezykami, zdjeciami, kategoriami i atrybutami. + +Odpowiedz: +```json +{ + "status": "ok", + "data": { + "id": 1, + "sku": "PROD-001", + "ean": "5901234123457", + "price_brutto": 99.99, + "price_brutto_promo": null, + "price_netto": 81.29, + "price_netto_promo": null, + "quantity": 10, + "status": 1, + "promoted": 0, + "vat": 23, + "weight": 0.5, + "stock_0_buy": 0, + "custom_label_0": null, + "set_id": null, + "product_unit_id": 1, + "producer_id": 3, + "date_add": "2026-01-15 10:00:00", + "date_modify": "2026-02-19 12:00:00", + "languages": { + "pl": { + "name": "Produkt testowy", + "short_description": "Krotki opis", + "description": "

Pelny opis produktu

", + "meta_description": null, + "meta_keywords": null, + "meta_title": null, + "seo_link": "produkt-testowy", + "copy_from": null, + "warehouse_message_zero": null, + "warehouse_message_nonzero": null, + "tab_name_1": null, + "tab_description_1": null, + "tab_name_2": null, + "tab_description_2": null, + "canonical": null + } + }, + "images": [ + {"id": 1, "src": "product1.jpg", "alt": "Zdjecie produktu"} + ], + "categories": [1, 5], + "attributes": [ + { + "attribute_id": 1, + "attribute_type": 1, + "attribute_names": {"pl": "Kolor", "en": "Color"}, + "value_id": 3, + "value_names": {"pl": "Czerwony", "en": "Red"} + } + ], + "variants": [ + { + "id": 101, + "permutation_hash": "1-3|2-5", + "sku": "PROD-001-RED-L", + "ean": null, + "price_brutto": 109.99, + "price_brutto_promo": null, + "price_netto": 89.42, + "price_netto_promo": null, + "quantity": 5, + "stock_0_buy": 0, + "weight": 0.5, + "status": 1, + "attributes": [ + {"attribute_id": 1, "attribute_names": {"pl": "Kolor"}, "value_id": 3, "value_names": {"pl": "Czerwony"}}, + {"attribute_id": 2, "attribute_names": {"pl": "Rozmiar"}, "value_id": 5, "value_names": {"pl": "L"}} + ] + } + ] + } +} +``` + +#### Tworzenie produktu +``` +POST api.php?endpoint=products&action=create +Content-Type: application/json + +{ + "price_brutto": 99.99, + "vat": 23, + "quantity": 10, + "status": 1, + "sku": "PROD-001", + "ean": "5901234123457", + "weight": 0.5, + "languages": { + "pl": { + "name": "Nowy produkt", + "description": "

Opis produktu

" + } + }, + "categories": [1, 5], + "products_related": [10, 20] +} +``` + +Wymagane: `languages` (min. 1 jezyk z `name`) oraz `price_brutto`. + +Odpowiedz (HTTP 201): +```json +{ + "status": "ok", + "data": { + "id": 42 + } +} +``` + +#### Aktualizacja produktu +``` +PUT api.php?endpoint=products&action=update&id={product_id} +Content-Type: application/json + +{ + "price_brutto": 129.99, + "status": 1, + "languages": { + "pl": { + "name": "Zaktualizowana nazwa" + } + } +} +``` + +Partial update — wystarczy przeslac tylko zmienione pola. Pola nieprzeslane zachowuja aktualna wartosc. + +Odpowiedz: pelne dane produktu (jak w `get`). + +### Warianty produktow + +#### Lista wariantow produktu +``` +GET api.php?endpoint=products&action=variants&id={product_id} +``` + +Zwraca warianty produktu nadrzednego wraz z dostepnymi atrybutami. + +Odpowiedz: +```json +{ + "status": "ok", + "data": { + "product_id": 1, + "available_attributes": [ + { + "id": 1, + "type": 1, + "status": 1, + "names": {"pl": "Kolor", "en": "Color"}, + "values": [ + {"id": 3, "names": {"pl": "Czerwony", "en": "Red"}, "is_default": 0, "impact_on_the_price": null}, + {"id": 4, "names": {"pl": "Niebieski", "en": "Blue"}, "is_default": 0, "impact_on_the_price": 10.0} + ] + } + ], + "variants": [ + { + "id": 101, + "permutation_hash": "1-3", + "sku": "PROD-001-RED", + "ean": null, + "price_brutto": 109.99, + "price_brutto_promo": null, + "price_netto": 89.42, + "price_netto_promo": null, + "quantity": 5, + "stock_0_buy": 0, + "weight": 0.5, + "status": 1, + "attributes": [ + {"attribute_id": 1, "attribute_names": {"pl": "Kolor"}, "value_id": 3, "value_names": {"pl": "Czerwony"}} + ] + } + ] + } +} +``` + +#### Tworzenie wariantu +``` +POST api.php?endpoint=products&action=create_variant&id={product_id} +Content-Type: application/json + +{ + "attributes": {"1": 3, "2": 5}, + "sku": "PROD-001-RED-L", + "ean": "5901234123458", + "price_brutto": 109.99, + "quantity": 5, + "weight": 0.5 +} +``` + +Wymagane: `attributes` (mapa attribute_id -> value_id, min. 1). Kombinacja atrybutow musi byc unikalna. + +Odpowiedz (HTTP 201): pelne dane wariantu. + +#### Aktualizacja wariantu +``` +PUT api.php?endpoint=products&action=update_variant&id={variant_id} +Content-Type: application/json + +{ + "sku": "PROD-001-RED-XL", + "price_brutto": 119.99, + "quantity": 3 +} +``` + +Partial update — mozna zmienic: sku, ean, price_brutto, price_netto, price_brutto_promo, price_netto_promo, quantity, stock_0_buy, weight, status. + +Odpowiedz: pelne dane wariantu. + +#### Usuwanie wariantu +``` +DELETE api.php?endpoint=products&action=delete_variant&id={variant_id} +``` + +Odpowiedz: +```json +{ + "status": "ok", + "data": {"id": 101, "deleted": true} +} +``` + +### Slowniki + +#### Lista statusow zamowien +``` +GET api.php?endpoint=dictionaries&action=statuses +``` + +Odpowiedz: +```json +{ + "status": "ok", + "data": [ + {"id": 0, "name": "Nowe"}, + {"id": 1, "name": "Oplacone"}, + {"id": 4, "name": "W realizacji"}, + {"id": 6, "name": "Wyslane"} + ] +} +``` + +#### Lista metod transportu +``` +GET api.php?endpoint=dictionaries&action=transports +``` + +#### Lista metod platnosci +``` +GET api.php?endpoint=dictionaries&action=payment_methods +``` + +#### Lista atrybutow +``` +GET api.php?endpoint=dictionaries&action=attributes +``` + +Zwraca aktywne atrybuty z wartosciami i wielojezycznymi nazwami. + +Odpowiedz: +```json +{ + "status": "ok", + "data": [ + { + "id": 1, + "type": 1, + "status": 1, + "names": {"pl": "Kolor", "en": "Color"}, + "values": [ + {"id": 3, "names": {"pl": "Czerwony"}, "is_default": 0, "impact_on_the_price": null}, + {"id": 4, "names": {"pl": "Niebieski"}, "is_default": 1, "impact_on_the_price": 10.0} + ] + } + ] +} +``` + +## Polling + +Aby pobierac tylko nowe/zmienione zamowienia, uzyj parametru `updated_since`: + +``` +GET api.php?endpoint=orders&action=list&updated_since=2026-02-19 12:00:00 +``` + +Kolumna `updated_at` w `pp_shop_orders` jest aktualizowana automatycznie przy kazdej modyfikacji zamowienia (zmiana statusu, platnosci, edycja danych, tworzenie zamowienia). + +## Konfiguracja + +Klucz API ustawia sie w panelu admina w ustawieniach sklepu lub bezposrednio w bazie: + +```sql +INSERT INTO pp_settings (param, value) VALUES ('api_key', 'twoj-klucz-api'); +-- lub +UPDATE pp_settings SET value = 'twoj-klucz-api' WHERE param = 'api_key'; +``` + +## Architektura + +- Entry point: `api.php` +- Router: `\api\ApiRouter` (`autoload/api/ApiRouter.php`) +- Kontrolery: `autoload/api/Controllers/` + - `OrdersApiController` — zamowienia (5 akcji) + - `ProductsApiController` — produkty (8 akcji: list, get, create, update, variants, create_variant, update_variant, delete_variant) + - `DictionariesApiController` — slowniki (4 akcje: statuses, transports, payment_methods, attributes)