import PocketBase from '/pocketbase.es.mjs'; // Initialize PocketBase const pb = new PocketBase('https://pocketbase.ccllc.pro'); // API key for admin operations (fetched from 'api' collection) let API_KEY = null; /** * Check if user is authenticated * @returns {boolean} */ export function isAuthenticated() { return pb.authStore.isValid; } /** * Get current authenticated user * @returns {object|null} */ export function getCurrentUser() { return pb.authStore.model; } /** * Logout current user */ export function logout() { pb.authStore.clear(); } /** * Fetch API key from the 'api' collection * The key is stored in a field called 'API_KEY' */ export async function loadApiKey() { // Try different collection name variations const collectionNames = ['api', 'API', 'Api']; for (const collectionName of collectionNames) { try { console.log(`Trying to load API key from collection: ${collectionName}`); // Fetch the API key from the collection const records = await pb.collection(collectionName).getFullList({ sort: '-created' }); console.log(`Found ${records?.length || 0} record(s) in ${collectionName} collection`); if (records && records.length > 0) { const firstRecord = records[0]; console.log(`First record fields:`, Object.keys(firstRecord)); console.log(`First record data:`, firstRecord); // Try different field name variations // Priority: API_KEY (as shown in PocketBase admin), then other variations API_KEY = firstRecord.API_KEY || firstRecord.Key || firstRecord.key || firstRecord.KEY || firstRecord.api_key || firstRecord.apiKey || firstRecord.ApiKey || null; if (API_KEY) { console.log(`✅ API key loaded successfully from ${collectionName} collection`); return API_KEY; // Success, exit function } else { console.warn(`API key field not found in ${collectionName} collection record. Available fields:`, Object.keys(firstRecord)); } } else { console.warn(`No records found in ${collectionName} collection`); } } catch (error) { console.warn(`Could not load from ${collectionName} collection:`, error.message); // Try next collection name } } // If we get here, no API key was found console.warn('⚠️ API key not found in any api collection. Operations will use user authentication.'); return null; } /** * Get API key (returns null if not loaded) * @returns {string|null} */ export function getApiKey() { return API_KEY; } /** * Load collection schema from PocketBase * @param {string} collectionName - Name of the collection * @returns {Promise} Collection schema */ export async function loadCollectionSchema(collectionName = 'Jobs') { // Try to fetch real schema from PocketBase try { // Use API key for schema access if available (bypasses collection rules) let collection; if (API_KEY) { // Make request with API key in header const response = await fetch(`${pb.baseUrl}/api/collections/${collectionName}`, { headers: { 'Authorization': `Bearer ${API_KEY}` } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } collection = await response.json(); } else { // Use standard PocketBase client (may fail with 403 if no permissions) collection = await pb.collections.getOne(collectionName); } console.log('Fetched collection:', collection); console.log('Collection schema:', collection.schema); if (collection.schema && collection.schema.length > 0) { // Map PocketBase schema to our format const schema = collection.schema.map(field => ({ name: field.name, type: field.type, required: field.required || false, options: field.options || {} })); console.log('Real collection schema loaded with', schema.length, 'fields'); console.log('Schema details:', schema.map(f => ({ name: f.name, type: f.type, required: f.required }))); return schema; } else { throw new Error('Collection schema is empty'); } } catch (error) { console.warn('Could not fetch collection schema, using defaults:', error.message); // Fallback to predefined schema const defaultSchema = [ { name: 'Job_Number', type: 'number' }, { name: 'Job_Name', type: 'text' }, { name: 'Job_Address', type: 'text' }, { name: 'updated', type: 'date' } ]; console.log('Using predefined collection schema with', defaultSchema.length, 'fields'); return defaultSchema; } } /** * Get collection info (for debugging) * @param {string} collectionName - Name of the collection * @returns {Promise} Collection info */ export async function getCollectionInfo(collectionName = 'Jobs') { try { let collection; if (API_KEY) { // Use API key for schema access const response = await fetch(`${pb.baseUrl}/api/collections/${collectionName}`, { headers: { 'Authorization': `Bearer ${API_KEY}` } }); if (response.ok) { collection = await response.json(); } else { throw new Error(`HTTP ${response.status}`); } } else { collection = await pb.collections.getOne(collectionName); } return collection; } catch (error) { console.warn('Could not fetch collection info:', error.message); throw error; } } /** * Load all jobs from the Jobs collection * @returns {Promise} Array of job records */ export async function loadJobs() { try { console.log('Attempting to load jobs from collection...'); const collectionUsed = 'Jobs'; // Use getFullList to fetch all records at once (better for client-side filtering) const records = await pb.collection(collectionUsed).getFullList({ sort: '-updated' }); console.log(`Jobs response from collection "${collectionUsed}":`, records); console.log('Number of jobs found:', records?.length || 0); // Debug: Log each job's structure if (records && records.length > 0) { console.log('Sample job structure:', records[0]); console.log('Job fields:', Object.keys(records[0])); } return records || []; } catch (error) { console.error('Error loading jobs:', error); console.error('Error details:', { message: error.message, status: error.response?.status, data: error.response?.data, url: error.config?.url }); // Handle specific error cases if (error.response?.status === 403) { throw new Error('Access denied to jobs collection. Please check your permissions in PocketBase.'); } else if (error.response?.status === 404) { throw new Error('Jobs collection not found. Please create the "jobs" collection in PocketBase.'); } else { throw new Error(`Failed to load jobs: ${error.message}`); } } } /** * Get the next available job number * @returns {Promise} Next job number (1000-9999) */ export async function getNextJobNumber() { try { // Use getFirstListItem to get only the highest job number (most efficient) // Sort by Job_Number descending to get the highest first const highestJob = await pb.collection('Jobs').getFirstListItem('', { sort: '-Job_Number' }); const highestJobNumber = highestJob.Job_Number || 0; const nextJobNumber = highestJobNumber + 1; // Ensure it's within 1000-9999 range if (nextJobNumber < 1000) { return 1000; } else if (nextJobNumber > 9999) { throw new Error('Maximum job number (9999) reached'); } return nextJobNumber; } catch (error) { // If no records exist or any other error, start with 1000 if (error.status === 404 || error.message?.includes('not found')) { // No jobs exist, start with 1000 return 1000; } console.error('Error getting next job number:', error); // Fallback: start with 1000 if we can't determine the next number return 1000; } } /** * Create a new job * @param {object} jobData - Job data object * @returns {Promise} Created job record */ export async function createJob(jobData) { try { // Check if job number already exists (optional validation) try { const existingJobs = await pb.collection('Jobs').getList(1, 1, { filter: `Job_Number = ${jobData.Job_Number}` }); if (existingJobs.items.length > 0) { throw new Error(`Job number ${jobData.Job_Number} already exists. Please refresh to get the next available number.`); } } catch (checkError) { if (checkError.message.includes('already exists')) { throw checkError; } // Ignore other errors from the check - continue with creation console.warn('Could not check for existing job number:', checkError.message); } // Create job in PocketBase const createdJob = await pb.collection('Jobs').create(jobData); console.log('Job created successfully:', createdJob); return createdJob; } catch (error) { console.error('Error creating job:', error); throw error; } } /** * Delete a job * @param {string} jobId - Job record ID * @returns {Promise} */ export async function deleteJob(jobId) { try { await pb.collection('Jobs').delete(jobId); } catch (error) { console.error('Error deleting job:', error.message); throw error; } } /** * Debug function to test PocketBase connection and permissions */ export async function debugPocketBase() { console.log('=== POCKETBASE DEBUG ==='); console.log('Base URL:', pb.baseUrl); console.log('Auth valid:', pb.authStore.isValid); console.log('Auth model:', pb.authStore.model); try { // Try to get collections const collections = await pb.collections.getFullList(); console.log('Available collections:', collections.map(c => c.name)); } catch (error) { console.error('Cannot list collections:', error.message); } // Test the Jobs collection specifically console.log('Testing Jobs collection...'); // Test read access try { const result = await pb.collection('Jobs').getList(1, 10); console.log(`✅ Read access: ${result.totalItems} items found`); if (result.items.length > 0) { console.log('Sample item:', result.items[0]); } } catch (error) { console.log('❌ Read access failed:', error.message); } // Test write access with dummy data try { const testData = { Job_Number: 9999, Job_Name: 'TEST_JOB_PLEASE_DELETE' }; const testResult = await pb.collection('Jobs').create(testData); console.log('✅ Write access successful, created test record:', testResult); // Clean up test record await pb.collection('Jobs').delete(testResult.id); console.log('✅ Test record cleaned up'); } catch (error) { console.log('❌ Write access failed:', error.message); if (error.response) { console.log('Error details:', error.response.data); } } console.log('=== END DEBUG ==='); } /** * Debug collections */ export async function debugCollections() { try { console.log('=== DEBUGGING COLLECTIONS ==='); // Try different collection names (only valid ones) const collectionNames = ['jobs', 'Jobs']; for (const collectionName of collectionNames) { try { console.log(`Trying collection: ${collectionName}`); const records = await pb.collection(collectionName).getList(1, 10); console.log(`${collectionName} found with ${records.totalItems} items`); if (records.totalItems > 0) { console.log(`${collectionName} sample item:`, records.items[0]); } } catch (error) { console.log(`${collectionName} error:`, error.message); } } console.log('=== END DEBUG ==='); } catch (error) { console.warn('Debug collections failed:', error.message); } } // Export PocketBase instance for advanced usage if needed export { pb };