This commit is contained in:
2025-10-01 18:31:30 +02:00
commit 85b035fd27
80 changed files with 6930 additions and 0 deletions

202
frontend/src/api/axios.ts Normal file
View File

@@ -0,0 +1,202 @@
import axios from "axios";
const API_URL: string = `${import.meta.env.VITE_BACKEND_URL}/api`;
// Axios instance, můžeme používat místo globálního axios
const axios_instance = axios.create({
baseURL: API_URL,
withCredentials: true, // potřebné pro cookies
});
axios_instance.defaults.xsrfCookieName = "csrftoken";
axios_instance.defaults.xsrfHeaderName = "X-CSRFToken";
export default axios_instance;
// 🔐 Axios response interceptor: automatická obnova při 401
axios_instance.interceptors.request.use((config) => {
const getCookie = (name: string): string | null => {
let cookieValue: string | null = null;
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.startsWith(name + "=")) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
};
const csrfToken = getCookie("csrftoken");
if (csrfToken && config.method && ["post", "put", "patch", "delete"].includes(config.method)) {
if (!config.headers) config.headers = {};
config.headers["X-CSRFToken"] = csrfToken;
}
return config;
});
// Přidej globální response interceptor pro redirect na login při 401 s detail hláškou
axios_instance.interceptors.response.use(
(response) => response,
(error) => {
if (
error.response &&
error.response.status === 401 &&
error.response.data &&
error.response.data.detail === "Nebyly zadány přihlašovací údaje."
) {
window.location.href = "/login";
}
return Promise.reject(error);
}
);
// 🔄 Obnova access tokenu pomocí refresh cookie
export const refreshAccessToken = async (): Promise<{ access: string; refresh: string } | null> => {
try {
const res = await axios_instance.post(`/account/token/refresh/`);
return res.data as { access: string; refresh: string };
} catch (err) {
console.error("Token refresh failed", err);
await logout();
return null;
}
};
// ✅ Přihlášení
export const login = async (username: string, password: string): Promise<any> => {
await logout();
try {
const response = await axios_instance.post(`/account/token/`, { username, password });
return response.data;
} catch (err: any) {
if (err.response) {
// Server responded with a status code outside 2xx
console.log('Login error status:', err.response.status);
} else if (err.request) {
// Request was made but no response received
console.log('Login network error:', err.request);
} else {
// Something else happened
console.log('Login setup error:', err.message);
}
throw err;
}
};
// ❌ Odhlášení s CSRF tokenem
export const logout = async (): Promise<any> => {
try {
const getCookie = (name: string): string | null => {
let cookieValue: string | null = null;
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.startsWith(name + "=")) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
};
const csrfToken = getCookie("csrftoken");
const response = await axios_instance.post(
"/account/logout/",
{},
{
headers: {
"X-CSRFToken": csrfToken,
},
}
);
console.log(response.data);
return response.data; // např. { detail: "Logout successful" }
} catch (err) {
console.error("Logout failed", err);
throw err;
}
};
/**
* 📡 Obecný request pro API
*
* @param method - HTTP metoda (např. "get", "post", "put", "patch", "delete")
* @param endpoint - API endpoint (např. "/api/service-tickets/")
* @param data - data pro POST/PUT/DELETE requesty
* @param config - další konfigurace pro axios request
* @returns Promise<any> - vrací data z odpovědi
*/
export const apiRequest = async (
method: string,
endpoint: string,
data: Record<string, any> = {},
config: Record<string, any> = {}
): Promise<any> => {
const url = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
try {
const response = await axios_instance({
method,
url,
data: ["post", "put", "patch"].includes(method.toLowerCase()) ? data : undefined,
params: ["get", "delete"].includes(method.toLowerCase()) ? data : undefined,
...config,
});
return response.data;
} catch (err: any) {
if (err.response) {
// Server odpověděl s kódem mimo rozsah 2xx
console.error("API Error:", {
status: err.response.status,
data: err.response.data,
headers: err.response.headers,
});
} else if (err.request) {
// Request byl odeslán, ale nedošla odpověď
console.error("No response received:", err.request);
} else {
// Něco jiného se pokazilo při sestavování requestu
console.error("Request setup error:", err.message);
}
throw err;
}
};
// 👤 Funkce pro získání aktuálně přihlášeného uživatele
export async function getCurrentUser(): Promise<any> {
const response = await axios_instance.get(`${API_URL}/account/user/me/`);
return response.data; // vrací data uživatele
}
// 🔒 ✔️ Jednoduchá funkce, která kontroluje přihlášení - můžeš to upravit dle potřeby
export async function isAuthenticated(): Promise<boolean> {
try {
const user = await getCurrentUser();
return user != null;
} catch (err) {
return false; // pokud padne 401, není přihlášen
}
}
export { axios_instance, API_URL };

View File

@@ -0,0 +1,26 @@
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
/**
* Makes a general external API call using axios.
*
* @param url - The full URL of the external API endpoint.
* @param method - HTTP method (GET, POST, PUT, PATCH, DELETE, etc.).
* @param data - Request body data (for POST, PUT, PATCH). Optional.
* @param config - Additional Axios request config (headers, params, etc.). Optional.
* @returns Promise resolving to AxiosResponse<any>.
*
* @example externalApiCall("https://api.example.com/data", "post", { foo: "bar" }, { headers: { Authorization: "Bearer token" } })
*/
export async function externalApiCall(
url: string,
method: AxiosRequestConfig["method"],
data?: any,
config?: AxiosRequestConfig
): Promise<AxiosResponse<any>> {
return axios({
url,
method,
data,
...config,
});
}

View File

@@ -0,0 +1,41 @@
import { apiRequest } from "./axios";
/**
* Loads enum values from an OpenAPI schema for a given path, method, and field (e.g., category).
*
* @param path - API path, e.g., "/api/service-tickets/"
* @param method - HTTP method
* @param field - field name in parameters or request
* @param schemaUrl - URL of the JSON schema, default "/api/schema/?format=json"
* @returns Promise<Array<{ value: string; label: string }>>
*/
export async function fetchEnumFromSchemaJson(
path: string,
method: "get" | "post" | "patch" | "put",
field: string,
schemaUrl: string = "/schema/?format=json"
): Promise<Array<{ value: string; label: string }>> {
try {
const schema = await apiRequest("get", schemaUrl);
const methodDef = schema.paths?.[path]?.[method];
if (!methodDef) {
throw new Error(`Method ${method.toUpperCase()} for ${path} not found in schema.`);
}
// Search in "parameters" (e.g., GET query parameters)
const param = methodDef.parameters?.find((p: any) => p.name === field);
if (param?.schema?.enum) {
return param.schema.enum.map((val: string) => ({
value: val,
label: val,
}));
}
throw new Error(`Field '${field}' does not contain enum`);
} catch (error) {
console.error("Error loading enum values:", error);
throw error;
}
}