Features: - Manager-centric parking spot management - Fair assignment algorithm (parking/presence ratio) - Presence tracking calendar - Closing days (specific & weekly recurring) - Guarantees and exclusions - Authelia/LLDAP integration for SSO Stack: - FastAPI backend - SQLite database - Vanilla JS frontend - Docker deployment
175 lines
3.7 KiB
JavaScript
175 lines
3.7 KiB
JavaScript
/**
|
|
* API Client Wrapper
|
|
* Centralized API communication with auth handling
|
|
*/
|
|
|
|
const api = {
|
|
/**
|
|
* Get the auth token from localStorage
|
|
*/
|
|
getToken() {
|
|
return localStorage.getItem('access_token');
|
|
},
|
|
|
|
/**
|
|
* Set the auth token
|
|
*/
|
|
setToken(token) {
|
|
localStorage.setItem('access_token', token);
|
|
},
|
|
|
|
/**
|
|
* Clear the auth token
|
|
*/
|
|
clearToken() {
|
|
localStorage.removeItem('access_token');
|
|
},
|
|
|
|
/**
|
|
* Check if user is authenticated
|
|
*/
|
|
isAuthenticated() {
|
|
return !!this.getToken();
|
|
},
|
|
|
|
/**
|
|
* Redirect to login if not authenticated
|
|
*/
|
|
requireAuth() {
|
|
if (!this.isAuthenticated()) {
|
|
window.location.href = '/login';
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Make an API request
|
|
*/
|
|
async request(method, url, data = null) {
|
|
const options = {
|
|
method,
|
|
headers: {}
|
|
};
|
|
|
|
const token = this.getToken();
|
|
if (token) {
|
|
options.headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
if (data) {
|
|
options.headers['Content-Type'] = 'application/json';
|
|
options.body = JSON.stringify(data);
|
|
}
|
|
|
|
const response = await fetch(url, options);
|
|
|
|
// Handle 401 - redirect to login
|
|
if (response.status === 401) {
|
|
this.clearToken();
|
|
window.location.href = '/login';
|
|
return null;
|
|
}
|
|
|
|
return response;
|
|
},
|
|
|
|
/**
|
|
* GET request
|
|
*/
|
|
async get(url) {
|
|
return this.request('GET', url);
|
|
},
|
|
|
|
/**
|
|
* POST request
|
|
*/
|
|
async post(url, data) {
|
|
return this.request('POST', url, data);
|
|
},
|
|
|
|
/**
|
|
* PUT request
|
|
*/
|
|
async put(url, data) {
|
|
return this.request('PUT', url, data);
|
|
},
|
|
|
|
/**
|
|
* PATCH request
|
|
*/
|
|
async patch(url, data) {
|
|
return this.request('PATCH', url, data);
|
|
},
|
|
|
|
/**
|
|
* DELETE request
|
|
*/
|
|
async delete(url) {
|
|
return this.request('DELETE', url);
|
|
},
|
|
|
|
/**
|
|
* Get current user info
|
|
*/
|
|
async getCurrentUser() {
|
|
const response = await this.get('/api/auth/me');
|
|
if (response && response.ok) {
|
|
return await response.json();
|
|
}
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Login
|
|
*/
|
|
async login(email, password) {
|
|
const response = await fetch('/api/auth/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email, password })
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
this.setToken(data.access_token);
|
|
return { success: true };
|
|
}
|
|
|
|
const error = await response.json();
|
|
return { success: false, error: error.detail || 'Login failed' };
|
|
},
|
|
|
|
/**
|
|
* Register
|
|
*/
|
|
async register(email, password, name, officeId = null) {
|
|
const response = await fetch('/api/auth/register', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email, password, name, office_id: officeId })
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
this.setToken(data.access_token);
|
|
return { success: true };
|
|
}
|
|
|
|
const error = await response.json();
|
|
return { success: false, error: error.detail || 'Registration failed' };
|
|
},
|
|
|
|
/**
|
|
* Logout
|
|
*/
|
|
async logout() {
|
|
await this.post('/api/auth/logout', {});
|
|
this.clearToken();
|
|
window.location.href = '/login';
|
|
}
|
|
};
|
|
|
|
// Make globally available
|
|
window.api = api;
|