Files
TenerifeProp/public/js/api.js
TenerifeProp Dev 503eb8a62f feat: implement property page navigation and security fixes
- Fix XSS vulnerabilities with escapeHtml() utility
- Fix SQL injection in admin endpoints with column whitelisting
- Add CSRF protection middleware
- Remove hardcoded password backdoor
- Implement property navigation functions
- Add test coverage

Closes #9
2026-04-05 01:34:48 +01:00

112 lines
3.3 KiB
JavaScript

// TenerifeProp - API Client
const API_BASE = '/api';
class API {
// Properties
static async getProperties(filters = {}) {
const params = new URLSearchParams(filters);
const response = await fetch(`${API_BASE}/properties?${params}`);
return response.json();
}
static async getProperty(slug, lang = 'es') {
const response = await fetch(`${API_BASE}/properties/${slug}?lang=${lang}`);
return response.json();
}
static async getPropertyBySlug(slug, lang = 'es') {
const response = await fetch(`${API_BASE}/properties/${slug}?lang=${lang}`);
return response.json();
}
static async getFeaturedProperties(lang = 'es') {
const response = await fetch(`${API_BASE}/properties/featured?lang=${lang}`);
return response.json();
}
// Leads
static async createLead(data) {
// Input validation
if (!data.name || typeof data.name !== 'string' || data.name.trim().length < 2) {
return { success: false, error: 'Name is required (min 2 characters)' }
}
// Proper email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!data.email || typeof data.email !== 'string' || !emailRegex.test(data.email)) {
return { success: false, error: 'Valid email is required' }
}
// Sanitize inputs to prevent XSS
const sanitize = (str) => str ? String(str).replace(/[<>]/g, '') : ''
const sanitizedData = {
name: sanitize(data.name),
email: data.email,
phone: sanitize(data.phone),
message: sanitize(data.message),
property_id: data.property_id,
language: data.language
}
const response = await fetch(`${API_BASE}/leads`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sanitizedData)
});
return response.json();
}
static async getLeads(filters = {}) {
const params = new URLSearchParams(filters);
const response = await fetch(`${API_BASE}/leads?${params}`);
return response.json();
}
// Content
static async getTestimonials(lang = 'es') {
const response = await fetch(`${API_BASE}/testimonials?lang=${lang}`);
return response.json();
}
static async getFAQ(lang = 'es') {
const response = await fetch(`${API_BASE}/faq?lang=${lang}`);
return response.json();
}
static async getServices(lang = 'es') {
const response = await fetch(`${API_BASE}/services?lang=${lang}`);
return response.json();
}
static async getSettings() {
const response = await fetch(`${API_BASE}/settings`);
return response.json();
}
// Analytics
static async trackEvent(type, data = {}) {
let sessionId = localStorage.getItem('session_id');
if (!sessionId) {
// Fallback for non-secure contexts (HTTP)
sessionId = crypto.randomUUID ? crypto.randomUUID() : 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('session_id', sessionId);
}
await fetch(`${API_BASE}/analytics/event`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type,
session_id: sessionId,
...data
})
});
}
// Cities
static async getCities() {
const response = await fetch(`${API_BASE}/cities`);
return response.json();
}
}
// Export for use
window.API = API;