From 241b8a0f76fce10cd5d8726e3c56bab5759da55e Mon Sep 17 00:00:00 2001 From: eewing Date: Wed, 26 Nov 2025 13:30:27 -0600 Subject: [PATCH] fix one issues still needs work on pulling info for pocketbase --- public/db.js | 398 +++++++++++++++++++++++++++++++++++++++++++++++ public/jobs.html | 342 ++++------------------------------------ src/index.ts | 1 + 3 files changed, 432 insertions(+), 309 deletions(-) create mode 100644 public/db.js diff --git a/public/db.js b/public/db.js new file mode 100644 index 0000000..d14ee2f --- /dev/null +++ b/public/db.js @@ -0,0 +1,398 @@ +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 }; + diff --git a/public/jobs.html b/public/jobs.html index 3643a21..ca6f58e 100644 --- a/public/jobs.html +++ b/public/jobs.html @@ -516,14 +516,25 @@