307 lines
7.4 KiB
JavaScript
307 lines
7.4 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 behind a reverse proxy, use /api path
|
|
return `${protocol}//${hostname}/api`;
|
|
}
|
|
|
|
/**
|
|
* 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}`;
|
|
|
|
// Merge options, with custom headers taking precedence
|
|
const config = {
|
|
...options,
|
|
headers: {
|
|
...this.headers,
|
|
...(options.headers || {})
|
|
}
|
|
};
|
|
|
|
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('/');
|
|
}
|
|
|
|
async login(username, password) {
|
|
// Don't include Authorization header for login
|
|
const tempHeaders = { ...this.headers };
|
|
delete tempHeaders['Authorization'];
|
|
|
|
const response = await this.request('/auth/login', {
|
|
method: 'POST',
|
|
headers: tempHeaders,
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
|
|
// Set the token from the response
|
|
if (response.access_token) {
|
|
this.setToken(response.access_token);
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
async logout() {
|
|
try {
|
|
await this.request('/auth/logout', {
|
|
method: 'POST'
|
|
});
|
|
} finally {
|
|
// Clear token even if logout fails
|
|
this.clearToken();
|
|
}
|
|
}
|
|
|
|
async getCurrentUser() {
|
|
return this.request('/auth/me');
|
|
}
|
|
|
|
// 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
|
|
})
|
|
});
|
|
}
|
|
|
|
// 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'
|
|
});
|
|
}
|
|
|
|
// Bounce Management API
|
|
async getMemberBounces(memberId) {
|
|
return this.request(`/members/${memberId}/bounces`);
|
|
}
|
|
|
|
async resetBounceStatus(memberId) {
|
|
return this.request(`/members/${memberId}/bounce-status`, {
|
|
method: 'PATCH'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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; |