2025-08-22 14:45:40 -06:00

230 lines
7.5 KiB
JavaScript

const axios = require('axios');
const config = require('../config');
const logger = require('../utils/logger');
class NocoDBService {
constructor() {
this.apiUrl = config.nocodb.apiUrl;
this.apiToken = config.nocodb.apiToken;
this.projectId = config.nocodb.projectId;
this.timeout = 10000; // 10 seconds
// Create axios instance with defaults
this.client = axios.create({
baseURL: this.apiUrl,
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 => {
logger.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
getTableUrl(tableId) {
return `/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;
}
// Get ALL records from a table using pagination
async getAllPaginated(tableId, params = {}) {
try {
let allRecords = [];
let offset = 0;
const limit = params.limit || 100;
let hasMore = true;
while (hasMore) {
const response = await this.getAll(tableId, {
...params,
limit: limit,
offset: offset
});
const records = response.list || [];
allRecords = allRecords.concat(records);
// Check if there are more records
hasMore = records.length === limit;
offset += limit;
// Safety check to prevent infinite loops
if (offset > 10000) {
logger.warn(`Reached maximum offset limit while fetching records from table ${tableId}`);
break;
}
}
logger.info(`Fetched ${allRecords.length} total records from table ${tableId}`);
return {
list: allRecords,
pageInfo: {
totalRows: allRecords.length,
page: 1,
pageSize: allRecords.length,
isFirstPage: true,
isLastPage: true
}
};
} catch (error) {
logger.error('Error fetching paginated records:', error);
throw error;
}
}
// Get single record
async getById(tableId, recordId) {
const url = `${this.getTableUrl(tableId)}/${recordId}`;
const response = await this.client.get(url);
return response.data;
}
// Create record
async create(sheetId, data) {
try {
// Explicitly remove any ID field to prevent NocoDB conflicts
const cleanData = { ...data };
// Remove all possible ID field variations
delete cleanData.ID;
delete cleanData.id;
delete cleanData.Id;
delete cleanData.iD;
// Remove any undefined values to prevent issues
Object.keys(cleanData).forEach(key => {
if (cleanData[key] === undefined) {
delete cleanData[key];
}
});
logger.info(`Creating record in sheet ${sheetId}`);
logger.info(`Data being sent to NocoDB:`, JSON.stringify(cleanData, null, 2));
const url = this.getTableUrl(sheetId);
const response = await this.client.post(url, cleanData);
logger.info(`Create response status: ${response.status}`);
logger.info(`Create response:`, JSON.stringify(response.data, null, 2));
return response.data;
} catch (error) {
logger.error('Error creating record:', error);
logger.error('Error response data:', JSON.stringify(error.response?.data, null, 2));
logger.error('Error response status:', error.response?.status);
logger.error('Error response headers:', JSON.stringify(error.response?.headers, null, 2));
logger.error('Request URL:', error.config?.url);
logger.error('Request method:', error.config?.method);
logger.error('Request data:', JSON.stringify(error.config?.data, null, 2));
throw error;
}
}
// Update record
async update(tableId, recordId, data) {
const url = `${this.getTableUrl(tableId)}/${recordId}`;
const response = await this.client.patch(url, data);
return response.data;
}
// Delete record
async delete(tableId, recordId) {
const url = `${this.getTableUrl(tableId)}/${recordId}`;
const response = await this.client.delete(url);
return response.data;
}
// Get locations with proper filtering
async getLocations(params = {}) {
// For locations, we want all records by default, so use getAllPaginated
// unless specific limit/offset are provided
if (!params.limit && !params.offset) {
return this.getAllPaginated(config.nocodb.tableId, params);
}
const defaultParams = {
limit: 1000,
offset: 0,
...params
};
return this.getAll(config.nocodb.tableId, defaultParams);
}
// Get user by email
async getUserByEmail(email) {
if (!config.nocodb.loginSheetId) {
throw new Error('Login sheet not configured');
}
const response = await this.getAll(config.nocodb.loginSheetId, {
where: `(Email,eq,${email})`,
limit: 1
});
return response.list?.[0] || null;
}
// Get latest settings
async getLatestSettings() {
if (!config.nocodb.settingsSheetId) {
return null;
}
const response = await this.getAll(config.nocodb.settingsSheetId, {
sort: '-created_at',
limit: 1
});
return response.list?.[0] || null;
}
// Get settings with walk sheet data
async getWalkSheetSettings() {
if (!config.nocodb.settingsSheetId) {
return null;
}
const response = await this.getAll(config.nocodb.settingsSheetId, {
sort: '-created_at',
limit: 20
});
// Find first row with walk sheet data
const settings = response.list?.find(row =>
row.walk_sheet_title ||
row.walk_sheet_subtitle ||
row.walk_sheet_footer ||
row.qr_code_1_url ||
row.qr_code_2_url ||
row.qr_code_3_url
) || response.list?.[0];
return settings || null;
}
}
// Export singleton instance
module.exports = new NocoDBService();