Files
duck-prod-manager/notes/api_services.md
bartool 93778065ce refaktoryzacja service api.
wydzielenie mockow.
dodanie parsowania wyszukiwania
2026-05-02 05:49:24 +02:00

601 lines
11 KiB
Markdown

# API Services I Mocki
Data: 2026-04-29
## Cel dokumentu
Ten dokument opisuje warstwe API we frontendzie.
Warstwa API znajduje sie w:
```txt
frontend/src/services/
```
Jej zadaniem jest odseparowanie store od szczegolow komunikacji HTTP.
Store nie powinien wiedziec:
- jaki dokladnie jest URL endpointu,
- czy komunikacja idzie przez Axios,
- czy dane pochodza z prawdziwego backendu,
- czy dane pochodza z mockow JSON.
Store powinien wywolac funkcje domenowa, np.:
```js
fetchProducts(params)
```
a service powinien zajac sie reszta.
## Aktualne pliki services
```txt
frontend/src/services/apiMode.js
frontend/src/services/productsApi.js
frontend/src/services/productsHttpApi.js
frontend/src/services/productTimelineApi.js
frontend/src/services/productTimelineHttpApi.js
frontend/src/services/productSpecificationApi.js
frontend/src/services/productSpecificationHttpApi.js
frontend/src/services/dictionariesApi.js
frontend/src/services/dictionariesHttpApi.js
```
Pliki `*Api.js` sa fasadami. Wybieraja implementacje na podstawie `USE_MOCK_API`.
Pliki `*HttpApi.js` sa implementacja prawdziwego API przez Axios.
Implementacje mockow sa poza katalogiem `services`:
```txt
frontend/src/mocks/api/productsMockApi.js
frontend/src/mocks/api/productTimelineMockApi.js
frontend/src/mocks/api/productSpecificationMockApi.js
frontend/src/mocks/api/dictionariesMockApi.js
```
## Przeplyw danych
Docelowy przeplyw wyglada tak:
```txt
komponent Vue
-> store Pinia
-> service API
-> mock JSON albo Axios
-> backend Directus
```
Przyklad:
```txt
IndexPage.vue
-> productsStore.fetchFirstPage()
-> fetchProducts(params)
-> productsMockApi albo productsHttpApi
-> products.json albo GET /mayo-api/products
```
## Dlaczego services sa osobna warstwa
Bez services store musialby robic cos takiego:
```js
api.get('/mayo-api/products', { params })
```
Wtedy store zna:
- Axios,
- endpoint,
- strukture odpowiedzi HTTP.
To miesza odpowiedzialnosci.
Po wydzieleniu services store robi:
```js
const { items, pageInfo } = await fetchProducts(params)
```
To jest lepsze, bo:
- store skupia sie na stanie aplikacji,
- services skupiaja sie na komunikacji,
- endpoint mozna zmienic w jednym miejscu,
- latwiej dodac mocki,
- latwiej testowac logike store.
Po ostatnim porzadkowaniu services sa fasadami. Przyklad:
```js
export const fetchProducts = USE_MOCK_API ? fetchProductsFromMock : fetchProductsFromHttp
```
Dzieki temu `productsStore` zawsze importuje to samo:
```js
import { fetchProducts } from 'src/services/productsApi'
```
ale realna implementacja moze byc mockowa albo HTTP.
## `apiMode.js`
Plik:
```txt
frontend/src/services/apiMode.js
```
Zawiera przelacznik miedzy mock API i prawdziwym API:
```js
export const USE_MOCK_API = import.meta.env.VITE_USE_MOCK_API !== 'false'
```
To oznacza:
- domyslnie mocki sa wlaczone,
- jesli ustawisz `VITE_USE_MOCK_API=false`, aplikacja uzyje prawdziwego backendu.
Uruchomienie z prawdziwym backendem:
```bash
VITE_USE_MOCK_API=false npm run dev
```
Uruchomienie z mockami:
```bash
npm run dev
```
Mocki maja sztuczne opoznienie:
```js
waitForMockApi()
```
Dzieki temu podczas pracy nad UI widac stany ladowania podobne do prawdziwego API.
## Pliki mockow
Mocki sa zwyklymi plikami JSON:
```txt
frontend/src/mocks/products.json
frontend/src/mocks/timelines.json
frontend/src/mocks/specifications.json
frontend/src/mocks/dictionaries.json
```
Sa uzywane tylko przez implementacje mock API w `frontend/src/mocks/api/`.
Komponenty, store i fasady `services/*Api.js` nie importuja plikow JSON bezposrednio.
To jest wazne, bo komponent nie powinien wiedziec, skad przyszly dane. Dla komponentu dane z mocka i z backendu powinny wygladac identycznie.
## `productsApi.js` i `productsHttpApi.js`
Plik:
```txt
frontend/src/services/productsApi.js
frontend/src/services/productsHttpApi.js
```
Eksportuje:
```js
fetchProducts(params)
```
Odpowiedzialnosc:
- pobranie listy produktow,
- obsluga filtrow,
- obsluga `limit` i `offset`,
- zwrocenie danych w formacie oczekiwanym przez `productsStore`.
`productsApi.js` jest fasada.
`productsHttpApi.js` zna Axios i endpoint:
```http
GET /mayo-api/products
```
Zwraca:
```js
{
items: [],
pageInfo: {
limit: 30,
offset: 0,
hasMore: true,
total: 184,
},
}
```
W trybie mock implementacja jest w:
```txt
frontend/src/mocks/api/productsMockApi.js
```
W trybie mock:
- czyta `frontend/src/mocks/products.json`,
- filtruje po `orderSearch`, `finish`, `year`, `productionList`,
- ucina wynik wedlug `limit` i `offset`,
- zwraca `items` i `pageInfo`.
W trybie prawdziwego API implementacja jest w `productsHttpApi.js`:
```http
GET /mayo-api/products
```
z parametrami:
```js
{
limit,
offset,
search,
orderSearch,
model,
client,
finish,
productionList,
year,
}
```
`orderSearch` powstaje w `productsStore` przez parser:
```txt
frontend/src/utils/orderSearchParser.js
```
Do HTTP `orderSearch` jest serializowane jako JSON string, bo endpoint listy jest typu `GET`.
## `productTimelineApi.js` i `productTimelineHttpApi.js`
Plik:
```txt
frontend/src/services/productTimelineApi.js
frontend/src/services/productTimelineHttpApi.js
```
Eksportuje:
```js
fetchProductTimeline(productId)
createProductTimelineEvent(productId, payload)
```
### `fetchProductTimeline`
Pobiera pelny timeline produktu.
W trybie mock implementacja jest w:
```txt
frontend/src/mocks/api/productTimelineMockApi.js
```
W trybie mock:
- czyta `frontend/src/mocks/timelines.json`,
- zwraca timeline dla `productId`,
- jesli brak danych, zwraca pusty timeline.
W trybie prawdziwego API implementacja jest w `productTimelineHttpApi.js`:
```http
GET /mayo-api/products/:id/timeline
```
### `createProductTimelineEvent`
Dodaje event do timeline.
W trybie mock:
- tworzy sztuczny event,
- dodaje go do pamieci mockow,
- przelicza `timelinePreview`,
- zwraca nowy event i nowy preview.
W trybie prawdziwego API:
```http
POST /mayo-api/products/:id/timeline/events
```
Zwracany format:
```js
{
event: {},
timelinePreview: {
body: [],
neck: [],
},
}
```
To pozwala store od razu zaktualizowac:
- pelny timeline,
- preview na glownej karcie produktu.
## `productSpecificationApi.js` i `productSpecificationHttpApi.js`
Plik:
```txt
frontend/src/services/productSpecificationApi.js
frontend/src/services/productSpecificationHttpApi.js
```
Eksportuje:
```js
fetchProductSpecification(productId)
refreshProductSpecification(productId)
```
### `fetchProductSpecification`
Pobiera specyfikacje produktu.
W trybie mock implementacja jest w:
```txt
frontend/src/mocks/api/productSpecificationMockApi.js
```
W trybie mock:
- czyta `frontend/src/mocks/specifications.json`.
W trybie prawdziwego API implementacja jest w `productSpecificationHttpApi.js`:
```http
GET /mayo-api/products/:id/specification
```
### `refreshProductSpecification`
Odswieza specyfikacje produktu.
W trybie mock:
- zwraca dane z mocka,
- aktualizuje `lastFetchedAt` na aktualny czas.
W trybie prawdziwego API:
```http
POST /mayo-api/products/:id/specification/refresh
```
### Normalizacja odpowiedzi
Ten plik zawiera funkcje:
```js
normalizeSpecificationResponse(data)
```
Jej zadaniem jest zamiana odpowiedzi backendu na prosty format dla store:
```js
{
productId,
orderId,
sourceUrl,
lastFetchedAt,
sections,
diff,
}
```
Service akceptuje dwa ksztalty:
```js
data.specification.sections
```
albo:
```js
data.sections
```
Dzieki temu store nie musi znac szczegolow odpowiedzi HTTP.
## `dictionariesApi.js` i `dictionariesHttpApi.js`
Plik:
```txt
frontend/src/services/dictionariesApi.js
frontend/src/services/dictionariesHttpApi.js
```
Eksportuje:
```js
fetchDictionaries()
```
Odpowiedzialnosc:
- pobranie slownikow do filtrow i formularzy,
- normalizacja pustych pol do pustych tablic.
W trybie mock implementacja jest w:
```txt
frontend/src/mocks/api/dictionariesMockApi.js
```
W trybie mock:
- czyta `frontend/src/mocks/dictionaries.json`.
W trybie prawdziwego API implementacja jest w `dictionariesHttpApi.js`:
```http
GET /mayo-api/dictionaries
```
Zwraca:
```js
{
models: [],
clients: [],
finishes: [],
productionLists: [],
operations: [],
colors: [],
}
```
## Jak dodac nowy endpoint
Przyklad: chcesz dodac API do zdjec produktu.
Nie dodawaj Axiosa bezposrednio w komponencie ani w store.
Zrob nowy service:
```txt
frontend/src/services/productPhotosApi.js
frontend/src/services/productPhotosHttpApi.js
frontend/src/mocks/api/productPhotosMockApi.js
```
Fasada:
```js
import { fetchProductPhotos as fetchProductPhotosFromHttp } from 'src/services/productPhotosHttpApi'
import { fetchProductPhotos as fetchProductPhotosFromMock } from 'src/mocks/api/productPhotosMockApi'
import { USE_MOCK_API } from 'src/services/apiMode'
export const fetchProductPhotos = USE_MOCK_API
? fetchProductPhotosFromMock
: fetchProductPhotosFromHttp
```
Implementacja HTTP:
```js
import { api } from 'src/boot/axios'
export async function fetchProductPhotos(productId) {
const response = await api.get(`/mayo-api/products/${productId}/photos`)
return response.data
}
```
Dopiero potem store importuje:
```js
import { fetchProductPhotos } from 'src/services/productPhotosApi'
```
## Zasady pracy z mockami
Mocki powinny miec taki sam ksztalt jak prawdziwe API.
To jest najwazniejsza zasada.
Jesli prawdziwy endpoint ma zwrocic:
```js
{
items: [],
pageInfo: {}
}
```
to mock tez powinien zwrocic:
```js
{
items: [],
pageInfo: {}
}
```
Nie nalezy robic osobnych struktur danych tylko dla widoku, jesli nie beda zgodne z przyszlym API.
## Co zostalo juz zrobione
Utworzono:
```txt
frontend/src/services/apiMode.js
frontend/src/services/productsApi.js
frontend/src/services/productsHttpApi.js
frontend/src/services/productTimelineApi.js
frontend/src/services/productTimelineHttpApi.js
frontend/src/services/productSpecificationApi.js
frontend/src/services/productSpecificationHttpApi.js
frontend/src/services/dictionariesApi.js
frontend/src/services/dictionariesHttpApi.js
```
Utworzono dane mockow:
```txt
frontend/src/mocks/products.json
frontend/src/mocks/timelines.json
frontend/src/mocks/specifications.json
frontend/src/mocks/dictionaries.json
```
Utworzono implementacje mock API:
```txt
frontend/src/mocks/api/productsMockApi.js
frontend/src/mocks/api/productTimelineMockApi.js
frontend/src/mocks/api/productSpecificationMockApi.js
frontend/src/mocks/api/dictionariesMockApi.js
```
Zaktualizowano store tak, aby uzywaly services zamiast Axiosa bezposrednio.
Zaktualizowano widok glownej strony tak, aby pobieral dane ze store zamiast trzymac lokalne mocki w komponencie.
## Najwazniejsza regula
Komponenty nie powinny importowac:
```js
api
```
ani:
```js
src/mocks/*.json
```
Komponent powinien uzywac store albo dostawac dane przez props.
Store powinien uzywac services.
Services powinny uzywac Axiosa albo mockow.