Files
sasa-maillist/web/static/js/api.js
2025-10-13 14:05:01 +00:00

289 lines
6.8 KiB
JavaScript

/**
* API Client for Mailing List Manager
* Handles all communication with the FastAPI backend
*/
class APIClient {
constructor() {
this.baseURL = this.getBaseURL();
this.token = null;
this.headers = {
'Content-Type': 'application/json'
};
}
/**
* Get the base URL for API calls
* Automatically detects if running in container or development
*/
getBaseURL() {
const protocol = window.location.protocol;
const hostname = window.location.hostname;
// If running on localhost, assume development mode
if (hostname === 'localhost' || hostname === '127.0.0.1') {
return `${protocol}//${hostname}:8000`;
}
// If running in production, assume API is on port 8000
return `${protocol}//${hostname}:8000`;
}
/**
* Set authentication token
*/
setToken(token) {
this.token = token;
this.headers['Authorization'] = `Bearer ${token}`;
}
/**
* Clear authentication token
*/
clearToken() {
this.token = null;
delete this.headers['Authorization'];
}
/**
* Make HTTP request to API
*/
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
headers: { ...this.headers },
...options
};
try {
const response = await fetch(url, config);
// Handle different response types
if (response.status === 204) {
return null; // No content
}
const contentType = response.headers.get('content-type');
let data;
if (contentType && contentType.includes('application/json')) {
data = await response.json();
} else {
data = await response.text();
}
if (!response.ok) {
throw new APIError(
data.detail || data || `HTTP ${response.status}`,
response.status,
data
);
}
return data;
} catch (error) {
if (error instanceof APIError) {
throw error;
}
// Network or other errors
throw new APIError(
'Network error or API unavailable',
0,
{ originalError: error.message }
);
}
}
// Health and authentication methods
async checkHealth() {
return this.request('/health');
}
async testAuth() {
return this.request('/');
}
// Authentication API
async login(username, password) {
const response = await this.request('/auth/login', {
method: 'POST',
body: JSON.stringify({
username: username,
password: password
})
});
if (response.access_token) {
this.setToken(response.access_token);
}
return response;
}
async logout() {
try {
await this.request('/auth/logout', {
method: 'POST'
});
} catch (error) {
// Ignore logout errors, we'll clear the token anyway
}
this.clearToken();
}
async getCurrentUser() {
return this.request('/auth/me');
}
// User management API
async getUsers() {
return this.request('/users');
}
async createUser(userData) {
return this.request('/users', {
method: 'POST',
body: JSON.stringify(userData)
});
}
async updateUser(userId, userData) {
return this.request(`/users/${userId}`, {
method: 'PATCH',
body: JSON.stringify(userData)
});
}
async deleteUser(userId) {
return this.request(`/users/${userId}`, {
method: 'DELETE'
});
}
// Mailing Lists API
async getLists() {
return this.request('/lists');
}
async getList(listId) {
return this.request(`/lists/${listId}`);
}
async createList(listData) {
return this.request('/lists', {
method: 'POST',
body: JSON.stringify(listData)
});
}
async updateList(listId, listData) {
return this.request(`/lists/${listId}`, {
method: 'PATCH',
body: JSON.stringify(listData)
});
}
async deleteList(listId) {
return this.request(`/lists/${listId}`, {
method: 'DELETE'
});
}
// Members API
async getMembers() {
return this.request('/members');
}
async getMember(memberId) {
return this.request(`/members/${memberId}`);
}
async createMember(memberData) {
return this.request('/members', {
method: 'POST',
body: JSON.stringify(memberData)
});
}
async updateMember(memberId, memberData) {
return this.request(`/members/${memberId}`, {
method: 'PATCH',
body: JSON.stringify(memberData)
});
}
async deleteMember(memberId) {
return this.request(`/members/${memberId}`, {
method: 'DELETE'
});
}
// Subscriptions API
async getListMembers(listId) {
return this.request(`/lists/${listId}/members`);
}
async createSubscription(subscriptionData) {
return this.request('/subscriptions', {
method: 'POST',
body: JSON.stringify(subscriptionData)
});
}
async deleteSubscription(listEmail, memberEmail) {
const params = new URLSearchParams({
list_email: listEmail,
member_email: memberEmail
});
return this.request(`/subscriptions?${params}`, {
method: 'DELETE'
});
}
/**
* Bulk import members from CSV data
*/
async bulkImportMembers(csvData, listIds) {
return this.request('/bulk-import', {
method: 'POST',
body: JSON.stringify({
csv_data: csvData,
list_ids: listIds
})
});
}
}
/**
* Custom API Error class
*/
class APIError extends Error {
constructor(message, status = 0, details = null) {
super(message);
this.name = 'APIError';
this.status = status;
this.details = details;
}
isAuthError() {
return this.status === 401;
}
isNotFound() {
return this.status === 404;
}
isBadRequest() {
return this.status === 400;
}
isServerError() {
return this.status >= 500;
}
}
// Create global API client instance
window.apiClient = new APIClient();
window.APIError = APIError;