Files

399 lines
13 KiB
JavaScript

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<Array>} 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<object>} 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>} 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<number>} 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<object>} 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<void>}
*/
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 };