672 lines
24 KiB
JavaScript
672 lines
24 KiB
JavaScript
const axios = require('axios');
|
|
|
|
class NocoDBService {
|
|
constructor() {
|
|
// Accept either full API URL or base URL
|
|
const rawApiUrl = process.env.NOCODB_API_URL || process.env.NOCODB_URL;
|
|
this.apiToken = process.env.NOCODB_API_TOKEN;
|
|
this.projectId = process.env.NOCODB_PROJECT_ID;
|
|
this.timeout = 10000;
|
|
|
|
// Normalize base URL and API prefix to avoid double "/api/v1"
|
|
let baseUrl = rawApiUrl || '';
|
|
if (baseUrl.endsWith('/')) baseUrl = baseUrl.slice(0, -1);
|
|
// If env provided includes /api/v1, strip it from base and keep prefix
|
|
if (/\/api\/v1$/.test(baseUrl)) {
|
|
baseUrl = baseUrl.replace(/\/api\/v1$/, '');
|
|
}
|
|
this.baseUrl = baseUrl || '';
|
|
this.apiPrefix = '/api/v1';
|
|
|
|
// Table mapping from environment variables
|
|
this.tableIds = {
|
|
representatives: process.env.NOCODB_TABLE_REPRESENTATIVES,
|
|
emails: process.env.NOCODB_TABLE_EMAILS,
|
|
postalCodes: process.env.NOCODB_TABLE_POSTAL_CODES,
|
|
campaigns: process.env.NOCODB_TABLE_CAMPAIGNS,
|
|
campaignEmails: process.env.NOCODB_TABLE_CAMPAIGN_EMAILS,
|
|
users: process.env.NOCODB_TABLE_USERS
|
|
};
|
|
|
|
// Validate that all table IDs are set
|
|
const missingTables = Object.entries(this.tableIds)
|
|
.filter(([key, value]) => !value)
|
|
.map(([key]) => key);
|
|
|
|
if (missingTables.length > 0) {
|
|
console.error('Missing NocoDB table IDs in environment variables:', missingTables);
|
|
console.error('Please run the build-nocodb.sh script to set up the database tables.');
|
|
}
|
|
|
|
// Create axios instance with normalized base URL
|
|
this.client = axios.create({
|
|
baseURL: this.baseUrl,
|
|
timeout: this.timeout,
|
|
headers: {
|
|
'xc-token': this.apiToken,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
// Add response interceptor for error handling
|
|
this.client.interceptors.response.use(
|
|
response => response,
|
|
error => {
|
|
console.error('NocoDB API Error:', {
|
|
message: error.message,
|
|
url: error.config?.url,
|
|
method: error.config?.method,
|
|
status: error.response?.status,
|
|
data: error.response?.data
|
|
});
|
|
throw error;
|
|
}
|
|
);
|
|
}
|
|
|
|
// Build table URL using table ID
|
|
getTableUrl(tableId) {
|
|
// Always prefix with single "/api/v1"
|
|
return `${this.apiPrefix}/db/data/v1/${this.projectId}/${tableId}`;
|
|
}
|
|
|
|
// Get all records from a table
|
|
async getAll(tableId, params = {}) {
|
|
const url = this.getTableUrl(tableId);
|
|
const response = await this.client.get(url, { params });
|
|
return response.data;
|
|
}
|
|
|
|
// Create record
|
|
async create(tableId, data) {
|
|
try {
|
|
// Clean the data to remove any null values and system fields that NocoDB manages
|
|
const cleanData = Object.keys(data).reduce((clean, key) => {
|
|
// Skip null/undefined values
|
|
if (data[key] === null || data[key] === undefined) {
|
|
return clean;
|
|
}
|
|
|
|
// Skip any potential ID or system fields that NocoDB manages automatically
|
|
const systemFields = ['id', 'Id', 'ID', 'CreatedAt', 'UpdatedAt', 'created_at', 'updated_at'];
|
|
if (systemFields.includes(key)) {
|
|
console.log(`Skipping system field: ${key}`);
|
|
return clean;
|
|
}
|
|
|
|
clean[key] = data[key];
|
|
return clean;
|
|
}, {});
|
|
|
|
console.log(`Creating record in table ${tableId} with data:`, JSON.stringify(cleanData, null, 2));
|
|
|
|
const url = this.getTableUrl(tableId);
|
|
const response = await this.client.post(url, cleanData);
|
|
console.log(`Record created successfully in table ${tableId}`);
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error(`Error creating record in table ${tableId}:`, error.message);
|
|
if (error.response?.data) {
|
|
console.error('Full error response:', JSON.stringify(error.response.data, null, 2));
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Update record
|
|
async update(tableId, recordId, data) {
|
|
try {
|
|
// Clean the data to remove any null values which can cause NocoDB issues
|
|
const cleanData = Object.keys(data).reduce((clean, key) => {
|
|
if (data[key] !== null && data[key] !== undefined) {
|
|
clean[key] = data[key];
|
|
}
|
|
return clean;
|
|
}, {});
|
|
|
|
const url = `${this.getTableUrl(tableId)}/${recordId}`;
|
|
const response = await this.client.patch(url, cleanData);
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Error updating record:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async storeRepresentatives(postalCode, representatives) {
|
|
try {
|
|
const stored = [];
|
|
console.log(`Attempting to store ${representatives.length} representatives for postal code ${postalCode}`);
|
|
|
|
// First, clear any existing representatives for this postal code to avoid duplicates
|
|
try {
|
|
const existingQuery = await this.getAll(this.tableIds.representatives, {
|
|
where: `(Postal Code,eq,${postalCode})`
|
|
});
|
|
|
|
if (existingQuery.list && existingQuery.list.length > 0) {
|
|
console.log(`Found ${existingQuery.list.length} existing representatives for ${postalCode}, using cached data`);
|
|
return { success: true, count: existingQuery.list.length, cached: true };
|
|
}
|
|
} catch (checkError) {
|
|
console.log('Could not check for existing representatives:', checkError.message);
|
|
// Continue anyway
|
|
}
|
|
|
|
// Store each representative, handling duplicates gracefully
|
|
for (const rep of representatives) {
|
|
const record = {
|
|
'Postal Code': postalCode,
|
|
'Name': rep.name || '',
|
|
'Email': rep.email || '',
|
|
'District Name': rep.district_name || '',
|
|
'Elected Office': rep.elected_office || '',
|
|
'Party Name': rep.party_name || '',
|
|
'Representative Set Name': rep.representative_set_name || '',
|
|
'Profile URL': rep.url || '',
|
|
'Photo URL': rep.photo_url || '',
|
|
'Offices': rep.offices ? JSON.stringify(rep.offices) : '[]',
|
|
'Cached At': new Date().toISOString()
|
|
};
|
|
|
|
try {
|
|
const result = await this.create(this.tableIds.representatives, record);
|
|
stored.push(result);
|
|
console.log(`Successfully stored representative: ${rep.name}`);
|
|
} catch (createError) {
|
|
// Handle any duplicate or constraint errors gracefully
|
|
if (createError.response?.status === 400) {
|
|
console.log(`Skipping representative ${rep.name} due to constraint: ${createError.response?.data?.message || createError.message}`);
|
|
// Continue to next representative without failing
|
|
} else {
|
|
console.log(`Error storing representative ${rep.name}:`, createError.message);
|
|
// For non-400 errors, we might want to continue or fail - let's continue for now
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`Successfully stored ${stored.length} out of ${representatives.length} representatives for ${postalCode}`);
|
|
return { success: true, count: stored.length };
|
|
|
|
} catch (error) {
|
|
// Catch-all error handler - never let this method throw
|
|
console.log('Error in storeRepresentatives:', error.response?.data || error.message);
|
|
return { success: false, error: error.message, count: 0 };
|
|
}
|
|
}
|
|
|
|
async getRepresentativesByPostalCode(postalCode) {
|
|
try {
|
|
// Try to query with the most likely column name
|
|
const response = await this.getAll(this.tableIds.representatives, {
|
|
where: `(Postal Code,eq,${postalCode})`
|
|
});
|
|
|
|
const cachedRecords = response.list || [];
|
|
|
|
// Transform NocoDB format back to API format
|
|
const transformedRecords = cachedRecords.map(record => ({
|
|
name: record['Name'],
|
|
email: record['Email'],
|
|
district_name: record['District Name'],
|
|
elected_office: record['Elected Office'],
|
|
party_name: record['Party Name'],
|
|
representative_set_name: record['Representative Set Name'],
|
|
url: record['Profile URL'],
|
|
photo_url: record['Photo URL'],
|
|
offices: record['Offices'] ? JSON.parse(record['Offices']) : []
|
|
}));
|
|
|
|
return transformedRecords;
|
|
} catch (error) {
|
|
// If we get a 502 or other server error, just return empty array
|
|
if (error.response && (error.response.status === 502 || error.response.status >= 500)) {
|
|
console.log('NocoDB server unavailable (502/5xx error), returning empty cache result');
|
|
return [];
|
|
}
|
|
|
|
// For other errors like column not found, also return empty array
|
|
console.log('NocoDB cache error, returning empty array:', error.response?.data?.msg || error.message);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async clearRepresentativesByPostalCode(postalCode) {
|
|
try {
|
|
// Get existing records
|
|
const existing = await this.getRepresentativesByPostalCode(postalCode);
|
|
|
|
// Delete each record using client
|
|
for (const record of existing) {
|
|
const url = `${this.getTableUrl(this.tableIds.representatives)}/${record.Id}`;
|
|
await this.client.delete(url);
|
|
}
|
|
|
|
return { success: true, deleted: existing.length };
|
|
} catch (error) {
|
|
console.error('Error clearing representatives:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async logEmailSend(emailData) {
|
|
try {
|
|
const record = {
|
|
'Recipient Email': emailData.recipientEmail,
|
|
'Sender Name': emailData.senderName,
|
|
'Sender Email': emailData.senderEmail,
|
|
'Subject': emailData.subject,
|
|
'Message': emailData.message || '',
|
|
'Postal Code': emailData.postalCode,
|
|
'Status': emailData.status,
|
|
'Sent At': emailData.timestamp,
|
|
'Sender IP': emailData.senderIP || null // Add IP tracking for rate limiting
|
|
};
|
|
|
|
await this.create(this.tableIds.emails, record);
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('Error logging email:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async logEmailPreview(previewData) {
|
|
try {
|
|
// Let NocoDB handle all ID generation - just provide the basic data
|
|
const record = {
|
|
'Recipient Email': previewData.recipientEmail,
|
|
'Sender Name': previewData.senderName,
|
|
'Sender Email': previewData.senderEmail,
|
|
'Subject': previewData.subject,
|
|
'Message': previewData.message || '',
|
|
'Postal Code': previewData.postalCode,
|
|
'Status': 'previewed',
|
|
'Sent At': new Date().toISOString(), // Simple timestamp, let NocoDB handle uniqueness
|
|
'Sender IP': previewData.senderIP || 'unknown'
|
|
};
|
|
|
|
console.log('Attempting to log email preview...');
|
|
await this.create(this.tableIds.emails, record);
|
|
console.log('Email preview logged successfully');
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('Error logging email preview:', error);
|
|
|
|
// Check if it's a duplicate record error
|
|
if (error.response && error.response.data && error.response.data.code === '23505') {
|
|
console.warn('Duplicate constraint violation - this suggests NocoDB has hidden unique constraints');
|
|
console.warn('Skipping preview log to avoid breaking the preview functionality');
|
|
return { success: true, warning: 'Duplicate preview log skipped due to constraint' };
|
|
}
|
|
|
|
// Don't throw error - preview logging is optional and shouldn't break the preview
|
|
console.warn('Preview logging failed but continuing with preview functionality');
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
// Check if an email was recently sent to this recipient from this IP
|
|
async checkRecentEmailSend(senderIP, recipientEmail, windowMinutes = 5) {
|
|
try {
|
|
const windowStart = new Date(Date.now() - (windowMinutes * 60 * 1000)).toISOString();
|
|
|
|
const params = {
|
|
where: `(Sender IP,eq,${senderIP})~and(Recipient Email,eq,${recipientEmail})~and(Sent At,gte,${windowStart})`,
|
|
sort: '-CreatedAt',
|
|
limit: 1
|
|
};
|
|
|
|
const response = await this.getAll(this.tableIds.emails, params);
|
|
return response.list && response.list.length > 0 ? response.list[0] : null;
|
|
} catch (error) {
|
|
console.error('Error checking recent email send:', error);
|
|
return null; // On error, allow the send (fallback to in-memory limiter)
|
|
}
|
|
}
|
|
|
|
async getEmailLogs(filters = {}) {
|
|
try {
|
|
let whereClause = '';
|
|
const conditions = [];
|
|
|
|
if (filters.postalCode) {
|
|
conditions.push(`(Postal Code,eq,${filters.postalCode})`);
|
|
}
|
|
if (filters.senderEmail) {
|
|
conditions.push(`(sender_email,eq,${filters.senderEmail})`);
|
|
}
|
|
if (filters.status) {
|
|
conditions.push(`(status,eq,${filters.status})`);
|
|
}
|
|
|
|
if (conditions.length > 0) {
|
|
whereClause = `?where=${conditions.join('~and')}`;
|
|
}
|
|
|
|
const params = {};
|
|
if (conditions.length > 0) {
|
|
params.where = conditions.join('~and');
|
|
}
|
|
params.sort = '-CreatedAt';
|
|
|
|
const response = await this.getAll(this.tableIds.emails, params);
|
|
return response.list || [];
|
|
} catch (error) {
|
|
console.error('Error getting email logs:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async storePostalCodeInfo(postalCodeData) {
|
|
try {
|
|
// Map fields to NocoDB column titles
|
|
const mappedData = {
|
|
'Postal Code': postalCodeData.postal_code,
|
|
'City': postalCodeData.city,
|
|
'Province': postalCodeData.province
|
|
};
|
|
const response = await this.create(this.tableIds.postalCodes, mappedData);
|
|
return response;
|
|
} catch (error) {
|
|
// Don't throw error for postal code caching failures
|
|
console.log('Postal code info storage failed:', error.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Campaign management methods
|
|
async getAllCampaigns() {
|
|
try {
|
|
const response = await this.getAll(this.tableIds.campaigns, {
|
|
sort: '-CreatedAt'
|
|
});
|
|
return response.list || [];
|
|
} catch (error) {
|
|
console.error('Get all campaigns failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getCampaignById(id) {
|
|
try {
|
|
// Use direct record endpoint to avoid casing issues on Id column
|
|
const url = `${this.getTableUrl(this.tableIds.campaigns)}/${id}`;
|
|
const response = await this.client.get(url);
|
|
return response.data || null;
|
|
} catch (error) {
|
|
console.error('Get campaign by ID failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getCampaignBySlug(slug) {
|
|
try {
|
|
const response = await this.getAll(this.tableIds.campaigns, {
|
|
where: `(Campaign Slug,eq,${slug})`
|
|
});
|
|
return response.list && response.list.length > 0 ? response.list[0] : null;
|
|
} catch (error) {
|
|
console.error('Get campaign by slug failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async createCampaign(campaignData) {
|
|
try {
|
|
// Map field names to NocoDB column titles
|
|
const mappedData = {
|
|
'Campaign Slug': campaignData.slug,
|
|
'Campaign Title': campaignData.title,
|
|
'Description': campaignData.description,
|
|
'Email Subject': campaignData.email_subject,
|
|
'Email Body': campaignData.email_body,
|
|
'Call to Action': campaignData.call_to_action,
|
|
'Cover Photo': campaignData.cover_photo,
|
|
'Status': campaignData.status,
|
|
'Allow SMTP Email': campaignData.allow_smtp_email,
|
|
'Allow Mailto Link': campaignData.allow_mailto_link,
|
|
'Collect User Info': campaignData.collect_user_info,
|
|
'Show Email Count': campaignData.show_email_count,
|
|
'Allow Email Editing': campaignData.allow_email_editing,
|
|
'Target Government Levels': campaignData.target_government_levels,
|
|
'Created By User ID': campaignData.created_by_user_id,
|
|
'Created By User Email': campaignData.created_by_user_email,
|
|
'Created By User Name': campaignData.created_by_user_name
|
|
};
|
|
|
|
const response = await this.create(this.tableIds.campaigns, mappedData);
|
|
return response;
|
|
} catch (error) {
|
|
console.error('Create campaign failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async updateCampaign(id, updates) {
|
|
try {
|
|
// Map field names to NocoDB column titles
|
|
const mappedUpdates = {};
|
|
|
|
if (updates.slug !== undefined) mappedUpdates['Campaign Slug'] = updates.slug;
|
|
if (updates.title !== undefined) mappedUpdates['Campaign Title'] = updates.title;
|
|
if (updates.description !== undefined) mappedUpdates['Description'] = updates.description;
|
|
if (updates.email_subject !== undefined) mappedUpdates['Email Subject'] = updates.email_subject;
|
|
if (updates.email_body !== undefined) mappedUpdates['Email Body'] = updates.email_body;
|
|
if (updates.call_to_action !== undefined) mappedUpdates['Call to Action'] = updates.call_to_action;
|
|
if (updates.cover_photo !== undefined) mappedUpdates['Cover Photo'] = updates.cover_photo;
|
|
if (updates.status !== undefined) mappedUpdates['Status'] = updates.status;
|
|
if (updates.allow_smtp_email !== undefined) mappedUpdates['Allow SMTP Email'] = updates.allow_smtp_email;
|
|
if (updates.allow_mailto_link !== undefined) mappedUpdates['Allow Mailto Link'] = updates.allow_mailto_link;
|
|
if (updates.collect_user_info !== undefined) mappedUpdates['Collect User Info'] = updates.collect_user_info;
|
|
if (updates.show_email_count !== undefined) mappedUpdates['Show Email Count'] = updates.show_email_count;
|
|
if (updates.allow_email_editing !== undefined) mappedUpdates['Allow Email Editing'] = updates.allow_email_editing;
|
|
if (updates.target_government_levels !== undefined) mappedUpdates['Target Government Levels'] = updates.target_government_levels;
|
|
if (updates.updated_at !== undefined) mappedUpdates['UpdatedAt'] = updates.updated_at;
|
|
|
|
const url = `${this.getTableUrl(this.tableIds.campaigns)}/${id}`;
|
|
const response = await this.client.patch(url, mappedUpdates);
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Update campaign failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async deleteCampaign(id) {
|
|
try {
|
|
const url = `${this.getTableUrl(this.tableIds.campaigns)}/${id}`;
|
|
const response = await this.client.delete(url);
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Delete campaign failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Campaign email tracking methods
|
|
async logCampaignEmail(emailData) {
|
|
try {
|
|
// Map fields to NocoDB column titles
|
|
const mappedData = {
|
|
'Campaign ID': emailData.campaign_id,
|
|
'Campaign Slug': emailData.campaign_slug,
|
|
'User Email': emailData.user_email,
|
|
'User Name': emailData.user_name,
|
|
'User Postal Code': emailData.user_postal_code,
|
|
'Recipient Email': emailData.recipient_email,
|
|
'Recipient Name': emailData.recipient_name,
|
|
'Recipient Title': emailData.recipient_title,
|
|
'Government Level': emailData.recipient_level,
|
|
'Email Method': emailData.email_method,
|
|
'Subject': emailData.subject,
|
|
'Message': emailData.message,
|
|
'Status': emailData.status
|
|
// Note: 'Sent At' has default value of now() so we don't need to set it
|
|
};
|
|
|
|
try {
|
|
const response = await this.create(this.tableIds.campaignEmails, mappedData);
|
|
return response;
|
|
} catch (createError) {
|
|
// Handle duplicate record errors gracefully
|
|
if (createError.response?.status === 400 &&
|
|
(createError.response?.data?.message?.includes('already exists') ||
|
|
createError.response?.data?.code === '23505')) {
|
|
console.log(`Campaign email log already exists for user ${emailData.user_email} and campaign ${emailData.campaign_slug}, skipping...`);
|
|
// Return a success response to indicate the logging was handled
|
|
return { success: true, duplicate: true };
|
|
} else {
|
|
// Re-throw other errors
|
|
throw createError;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Log campaign email failed:', error.response?.data || error.message);
|
|
// Return a failure response but don't throw - logging should not break the main flow
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
async getCampaignEmailCount(campaignId) {
|
|
try {
|
|
const response = await this.getAll(this.tableIds.campaignEmails, {
|
|
where: `(Campaign ID,eq,${campaignId})`,
|
|
limit: 1000 // Get enough to count
|
|
});
|
|
return response.pageInfo ? response.pageInfo.totalRows : (response.list ? response.list.length : 0);
|
|
} catch (error) {
|
|
console.error('Get campaign email count failed:', error);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
async getCampaignAnalytics(campaignId) {
|
|
try {
|
|
const response = await this.getAll(this.tableIds.campaignEmails, {
|
|
where: `(Campaign ID,eq,${campaignId})`,
|
|
limit: 1000
|
|
});
|
|
|
|
const emails = response.list || [];
|
|
|
|
const analytics = {
|
|
totalEmails: emails.length,
|
|
smtpEmails: emails.filter(e => (e['Email Method'] || e.email_method) === 'smtp').length,
|
|
mailtoClicks: emails.filter(e => (e['Email Method'] || e.email_method) === 'mailto').length,
|
|
successfulEmails: emails.filter(e => {
|
|
const status = e['Status'] || e.status;
|
|
return status === 'sent' || status === 'clicked';
|
|
}).length,
|
|
failedEmails: emails.filter(e => (e['Status'] || e.status) === 'failed').length,
|
|
byLevel: {},
|
|
byDate: {},
|
|
recentEmails: emails.slice(0, 10).map(email => ({
|
|
timestamp: email['Sent At'] || email.timestamp || email.sent_at,
|
|
user_name: email['User Name'] || email.user_name,
|
|
recipient_name: email['Recipient Name'] || email.recipient_name,
|
|
recipient_level: email['Government Level'] || email.recipient_level,
|
|
email_method: email['Email Method'] || email.email_method,
|
|
status: email['Status'] || email.status
|
|
}))
|
|
};
|
|
|
|
// Group by government level
|
|
emails.forEach(email => {
|
|
const level = email['Government Level'] || email.recipient_level || 'Other';
|
|
analytics.byLevel[level] = (analytics.byLevel[level] || 0) + 1;
|
|
});
|
|
|
|
// Group by date
|
|
emails.forEach(email => {
|
|
const timestamp = email['Sent At'] || email.timestamp || email.sent_at;
|
|
if (timestamp) {
|
|
const date = timestamp.split('T')[0]; // Get date part
|
|
analytics.byDate[date] = (analytics.byDate[date] || 0) + 1;
|
|
}
|
|
});
|
|
|
|
return analytics;
|
|
} catch (error) {
|
|
console.error('Get campaign analytics failed:', error);
|
|
return {
|
|
totalEmails: 0,
|
|
smtpEmails: 0,
|
|
mailtoClicks: 0,
|
|
successfulEmails: 0,
|
|
failedEmails: 0,
|
|
byLevel: {},
|
|
byDate: {},
|
|
recentEmails: []
|
|
};
|
|
}
|
|
}
|
|
|
|
// User management methods
|
|
async getUserByEmail(email) {
|
|
if (!this.tableIds.users) {
|
|
throw new Error('Users table not configured');
|
|
}
|
|
|
|
try {
|
|
const response = await this.getAll(this.tableIds.users, {
|
|
where: `(Email,eq,${email})`,
|
|
limit: 1
|
|
});
|
|
|
|
return response.list?.[0] || null;
|
|
} catch (error) {
|
|
console.error('Error in getUserByEmail:', error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async createUser(userData) {
|
|
if (!this.tableIds.users) {
|
|
throw new Error('Users table not configured');
|
|
}
|
|
|
|
return await this.create(this.tableIds.users, userData);
|
|
}
|
|
|
|
async updateUser(userId, userData) {
|
|
if (!this.tableIds.users) {
|
|
throw new Error('Users table not configured');
|
|
}
|
|
|
|
return await this.update(this.tableIds.users, userId, userData);
|
|
}
|
|
|
|
async deleteUser(userId) {
|
|
if (!this.tableIds.users) {
|
|
throw new Error('Users table not configured');
|
|
}
|
|
|
|
const url = `${this.getTableUrl(this.tableIds.users)}/${userId}`;
|
|
const response = await this.client.delete(url);
|
|
return response.data;
|
|
}
|
|
|
|
async getById(tableId, recordId) {
|
|
try {
|
|
const url = `${this.getTableUrl(tableId)}/${recordId}`;
|
|
const response = await this.client.get(url);
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error('Error getting record by ID:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getAllUsers(params = {}) {
|
|
if (!this.tableIds.users) {
|
|
throw new Error('Users table not configured');
|
|
}
|
|
|
|
return await this.getAll(this.tableIds.users, params);
|
|
}
|
|
}
|
|
|
|
module.exports = new NocoDBService(); |