Added web front end
This commit is contained in:
219
web/static/js/api.js
Normal file
219
web/static/js/api.js
Normal file
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* 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('/');
|
||||
}
|
||||
|
||||
// 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'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user