mirror of
https://github.com/wowlikon/LiB.git
synced 2026-02-04 04:31:09 +00:00
241 lines
6.0 KiB
JavaScript
241 lines
6.0 KiB
JavaScript
const Utils = {
|
|
escapeHtml: (text) => {
|
|
if (!text) return "";
|
|
return text.replace(
|
|
/[&<>"']/g,
|
|
(m) =>
|
|
({
|
|
"&": "&",
|
|
"<": "<",
|
|
">": ">",
|
|
'"': """,
|
|
"'": "'",
|
|
})[m],
|
|
);
|
|
},
|
|
|
|
showToast: (message, type = "info") => {
|
|
const container = document.getElementById("toast-container");
|
|
if (!container) return;
|
|
|
|
const el = document.createElement("div");
|
|
const colors =
|
|
type === "error"
|
|
? "bg-red-500"
|
|
: type === "success"
|
|
? "bg-green-500"
|
|
: "bg-blue-500";
|
|
el.className = `${colors} text-white px-6 py-3 rounded shadow-lg transform transition-all duration-300 translate-y-10 opacity-0 mb-3`;
|
|
el.textContent = message;
|
|
|
|
container.appendChild(el);
|
|
|
|
requestAnimationFrame(() => {
|
|
el.classList.remove("translate-y-10", "opacity-0");
|
|
});
|
|
|
|
setTimeout(() => {
|
|
el.classList.add("translate-y-10", "opacity-0");
|
|
setTimeout(() => el.remove(), 300);
|
|
}, 3000);
|
|
},
|
|
|
|
getGravatarUrl: async (email) => {
|
|
if (!email) return "";
|
|
const msgBuffer = new TextEncoder().encode(email.trim().toLowerCase());
|
|
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
const hashHex = hashArray
|
|
.map((b) => b.toString(16).padStart(2, "0"))
|
|
.join("");
|
|
return `https://www.gravatar.com/avatar/${hashHex}?d=identicon&s=200`;
|
|
},
|
|
};
|
|
|
|
const Api = {
|
|
getBaseUrl() {
|
|
return window.location.origin;
|
|
},
|
|
|
|
async request(endpoint, options = {}) {
|
|
const fullUrl = this.getBaseUrl() + endpoint;
|
|
const token = localStorage.getItem("access_token");
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
...options.headers,
|
|
};
|
|
|
|
if (token) {
|
|
headers["Authorization"] = `Bearer ${token}`;
|
|
}
|
|
|
|
const config = { ...options, headers };
|
|
|
|
try {
|
|
const response = await fetch(fullUrl, config);
|
|
|
|
if (response.status === 401) {
|
|
const refreshed = await Auth.tryRefresh();
|
|
if (refreshed) {
|
|
headers["Authorization"] =
|
|
`Bearer ${localStorage.getItem("access_token")}`;
|
|
const retryResponse = await fetch(fullUrl, { ...options, headers });
|
|
if (retryResponse.ok) {
|
|
return retryResponse.json();
|
|
}
|
|
}
|
|
Auth.logout();
|
|
return null;
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.detail || `Error ${response.status}`);
|
|
}
|
|
return response.json();
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
get(endpoint) {
|
|
return this.request(endpoint, { method: "GET" });
|
|
},
|
|
|
|
post(endpoint, body) {
|
|
return this.request(endpoint, {
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
});
|
|
},
|
|
|
|
put(endpoint, body) {
|
|
return this.request(endpoint, {
|
|
method: "PUT",
|
|
body: JSON.stringify(body),
|
|
});
|
|
},
|
|
|
|
delete(endpoint) {
|
|
return this.request(endpoint, { method: "DELETE" });
|
|
},
|
|
|
|
postForm(endpoint, formData) {
|
|
return this.request(endpoint, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
body: formData.toString(),
|
|
});
|
|
},
|
|
};
|
|
|
|
const Auth = {
|
|
logout: () => {
|
|
localStorage.removeItem("access_token");
|
|
localStorage.removeItem("refresh_token");
|
|
localStorage.removeItem("user");
|
|
window.location.href = "/";
|
|
},
|
|
|
|
tryRefresh: async () => {
|
|
const refreshToken = localStorage.getItem("refresh_token");
|
|
if (!refreshToken) return false;
|
|
|
|
try {
|
|
const response = await fetch("/api/auth/refresh", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
localStorage.setItem("access_token", data.access_token);
|
|
localStorage.setItem("refresh_token", data.refresh_token);
|
|
return true;
|
|
}
|
|
} catch (e) {
|
|
console.error("Refresh failed:", e);
|
|
}
|
|
return false;
|
|
},
|
|
|
|
init: async () => {
|
|
const token = localStorage.getItem("access_token");
|
|
const refreshToken = localStorage.getItem("refresh_token");
|
|
|
|
if (!token && !refreshToken) {
|
|
localStorage.removeItem("user");
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
let response = await fetch("/api/auth/me", {
|
|
headers: { Authorization: "Bearer " + token },
|
|
});
|
|
|
|
if (response.status === 401 && refreshToken) {
|
|
const refreshed = await Auth.tryRefresh();
|
|
if (refreshed) {
|
|
response = await fetch("/api/auth/me", {
|
|
headers: {
|
|
Authorization: "Bearer " + localStorage.getItem("access_token"),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
if (response.ok) {
|
|
const user = await response.json();
|
|
localStorage.setItem("user", JSON.stringify(user));
|
|
document.dispatchEvent(new CustomEvent("auth:login", { detail: user }));
|
|
return user;
|
|
}
|
|
} catch (e) {
|
|
console.error("Auth check failed", e);
|
|
}
|
|
|
|
localStorage.removeItem("access_token");
|
|
localStorage.removeItem("refresh_token");
|
|
localStorage.removeItem("user");
|
|
return null;
|
|
},
|
|
};
|
|
|
|
window.getUser = function () {
|
|
const userJson = localStorage.getItem("user");
|
|
if (!userJson) return null;
|
|
try {
|
|
return JSON.parse(userJson);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
window.hasRole = function (roleName) {
|
|
const user = window.getUser();
|
|
if (!user || !user.roles) {
|
|
return false;
|
|
}
|
|
return user.roles.includes(roleName);
|
|
};
|
|
|
|
window.isAdmin = function () {
|
|
return window.hasRole("admin");
|
|
};
|
|
|
|
window.isLibrarian = function () {
|
|
return window.hasRole("librarian") || window.hasRole("admin");
|
|
};
|
|
|
|
window.isAuthenticated = function () {
|
|
return !!window.getUser();
|
|
};
|
|
|
|
window.canManage = function () {
|
|
return (
|
|
(typeof window.isAdmin === "function" && window.isAdmin()) ||
|
|
(typeof window.isLibrarian === "function" && window.isLibrarian())
|
|
);
|
|
};
|