fix: zamijeni auth polling health endpointom
Some checks failed
ERP CI/CD Pipeline / test (push) Has been cancelled
ERP CI/CD Pipeline / Deploy (server git pull + compose) (push) Has been cancelled

Dodaje backend /api/health/ endpoint i prebacuje frontend heartbeat sa /api/token/ OPTIONS na /api/health/ GET kako bi se izbjeglo periodicko gadanje auth rute.

Dodaje TTL cache za clientStore (lastFetchedAt + force opcija) kako bi se smanjili redundantni crm/clients pozivi pri navigaciji.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
mariomitte
2026-07-19 16:27:20 +02:00
parent bbdd02c9e9
commit 8485431180
3 changed files with 26 additions and 5 deletions

View File

@@ -2,14 +2,19 @@
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.http import JsonResponse
from django.urls import path, include from django.urls import path, include
from rest_framework_simplejwt.views import ( from rest_framework_simplejwt.views import (
TokenObtainPairView, TokenObtainPairView,
TokenRefreshView, TokenRefreshView,
) )
def api_health(_request):
return JsonResponse({'status': 'ok'})
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('api/health/', api_health, name='api_health'),
# Uključujemo rute iz modula (svaki modul ima svoj urls.py) # Uključujemo rute iz modula (svaki modul ima svoj urls.py)
path('api/crm/', include('modules.crm.urls')), path('api/crm/', include('modules.crm.urls')),

View File

@@ -8,12 +8,25 @@ export const $clients = atom([]);
export const $clientsLoading = atom(false); export const $clientsLoading = atom(false);
export const $clientsError = atom(null); export const $clientsError = atom(null);
export async function fetchClients(signal) { let _lastClientsFetchAt = 0;
const CLIENTS_FETCH_TTL_MS = 30_000; // 30 sekundi
export async function fetchClients(arg) {
const options = (arg && typeof arg === 'object' && !('aborted' in arg))
? arg
: { signal: arg };
const { signal, force = false } = options;
if (!force && $clients.get().length > 0 && Date.now() - _lastClientsFetchAt < CLIENTS_FETCH_TTL_MS) {
return;
}
$clientsLoading.set(true); $clientsLoading.set(true);
$clientsError.set(null); $clientsError.set(null);
try { try {
const data = await api.get('crm/clients/', { signal }); const data = await api.get('crm/clients/', { signal });
$clients.set(Array.isArray(data) ? data : (data.results ?? [])); $clients.set(Array.isArray(data) ? data : (data.results ?? []));
_lastClientsFetchAt = Date.now();
} catch (err) { } catch (err) {
if (err?.name !== 'AbortError') $clientsError.set(err.message ?? 'Greška'); if (err?.name !== 'AbortError') $clientsError.set(err.message ?? 'Greška');
} finally { } finally {
@@ -24,16 +37,19 @@ export async function fetchClients(signal) {
export async function createClient(payload) { export async function createClient(payload) {
const data = await api.post('crm/clients/', payload); const data = await api.post('crm/clients/', payload);
$clients.set([...$clients.get(), data]); $clients.set([...$clients.get(), data]);
_lastClientsFetchAt = Date.now();
return data; return data;
} }
export async function updateClient(id, payload) { export async function updateClient(id, payload) {
const data = await api.patch(`crm/clients/${id}/`, payload); const data = await api.patch(`crm/clients/${id}/`, payload);
$clients.set($clients.get().map((c) => (c.id === id ? data : c))); $clients.set($clients.get().map((c) => (c.id === id ? data : c)));
_lastClientsFetchAt = Date.now();
return data; return data;
} }
export async function deleteClient(id) { export async function deleteClient(id) {
await api.delete(`crm/clients/${id}/`); await api.delete(`crm/clients/${id}/`);
$clients.set($clients.get().filter((c) => c.id !== id)); $clients.set($clients.get().filter((c) => c.id !== id));
_lastClientsFetchAt = Date.now();
} }

View File

@@ -4,14 +4,14 @@ export const $isOffline = atom(false);
const API_BASE = import.meta.env.PUBLIC_API_URL || 'http://localhost:8001/api/'; const API_BASE = import.meta.env.PUBLIC_API_URL || 'http://localhost:8001/api/';
// Heartbeat endpoint: OPTIONS /api/token/ // Heartbeat endpoint: GET /api/health/
// DRF odgovara bez 405 greške i dovoljno je za provjeru dostupnosti backenda. // Time izbjegavamo periodično gađanje auth/token endpointa.
const HEARTBEAT_URL = new URL('token/', API_BASE).toString(); const HEARTBEAT_URL = new URL('health/', API_BASE).toString();
async function provjeriStvarnuVezu() { async function provjeriStvarnuVezu() {
try { try {
const response = await fetch(HEARTBEAT_URL, { const response = await fetch(HEARTBEAT_URL, {
method: 'OPTIONS', method: 'GET',
cache: 'no-store', cache: 'no-store',
}); });