/** * Parser for FFXI Trust character data from PostgreSQL database */ class TrustParser { constructor() { this.roles = []; this.characters = {}; this.charactersByRole = {}; this.allCharacters = []; this.dbConfig = null; } /** * Initialize the database configuration */ async initDbConfig() { try { // Try to read from environment variables first (for Docker deployment) if (window.ENV && window.ENV.PSQL_HOST) { console.log('Using database configuration from environment variables'); this.dbConfig = { PSQL_USER: window.ENV.PSQL_USER, PSQL_HOST: window.ENV.PSQL_HOST, PSQL_DBNAME: window.ENV.PSQL_DBNAME, PSQL_PASSWORD: window.ENV.PSQL_PASSWORD, PSQL_PORT: window.ENV.PSQL_PORT || '5432', }; return true; } // Fall back to db.conf file console.log('Reading database configuration from db.conf file'); const response = await fetch('db.conf'); const text = await response.text(); // Parse the db.conf file const dbConfig = {}; text.split('\n').forEach(line => { if (line.trim() === '') return; const [key, value] = line.split('='); if (key && value) { // Remove quotes if present const cleanValue = value.replace(/^['"]|['"]$/g, ''); dbConfig[key] = cleanValue; } }); this.dbConfig = dbConfig; return true; } catch (error) { console.error('Error loading database configuration:', error); return false; } } /** * Process the data from the database * @param {Array} data - Array of character objects from the database */ processData(data) { // Extract unique roles const roleSet = new Set(); data.forEach(char => { if (char.role && char.role.trim()) { roleSet.add(char.role.trim()); } }); this.roles = Array.from(roleSet); // Initialize charactersByRole this.roles.forEach(role => { this.charactersByRole[role] = []; }); // Process each character data.forEach(char => { // Create a character object with normalized property names const character = { id: char.id, name: char.name, altName: char.alt_name || '', role: char.role || 'Unknown', job: char.job || '', spells: char.spells || '', abilities: char.abilities || '', weaponSkills: char.weapon_skills || '', invincible: char.invincible || false, acquisition: char.acquisition || '', specialFeatures: char.special_features || '', trustSynergy: char.trust_synergy || '', trustSynergyNames: char.trust_synergy_names || [], acquired: char.acquired || false }; // Add to characters object this.characters[char.name] = character; // Add to charactersByRole if (character.role && this.charactersByRole[character.role]) { this.charactersByRole[character.role].push(character.name); } // Add to allCharacters this.allCharacters.push({ name: character.name, role: character.role }); }); } /** * Load data from the database * @param {Function} callback - Function to call when loading is complete */ async loadData(callback) { try { // Initialize database configuration if not already done if (!this.dbConfig) { const success = await this.initDbConfig(); if (!success) { throw new Error('Failed to initialize database configuration'); } } // Create the API endpoint URL const apiUrl = `/api/trusts`; // Fetch data from the API const response = await fetch(apiUrl); if (!response.ok) { throw new Error(`API request failed with status ${response.status}`); } const data = await response.json(); // Process the data this.processData(data); // Call the callback with the processed data if (callback) { callback({ roles: this.roles, characters: this.characters, charactersByRole: this.charactersByRole, allCharacters: this.allCharacters }); } } catch (error) { console.error('Error loading trust data:', error); // Fallback to JSON file if database connection fails console.log('Falling back to JSON file...'); this.loadFromJson(callback); } } /** * Fallback method to load data from JSON file * @param {Function} callback - Function to call when loading is complete */ loadFromJson(callback) { fetch('trusts.json') .then(response => response.json()) .then(data => { this.processData(data); // Call the callback with the processed data if (callback) { callback({ roles: this.roles, characters: this.characters, charactersByRole: this.charactersByRole, allCharacters: this.allCharacters }); } }) .catch(error => { console.error('Error loading trust data from JSON:', error); }); } /** * Search for characters by name or other attributes * @param {string} query - Search query * @returns {Array} - Array of matching characters */ searchCharacters(query) { if (!query) return this.allCharacters; query = query.toLowerCase(); return this.allCharacters.filter(char => { const character = this.characters[char.name]; // Search by name if (char.name.toLowerCase().includes(query)) return true; // Search by alt_name if (character.altName && character.altName.toLowerCase().includes(query)) return true; // Search by role if (char.role.toLowerCase().includes(query)) return true; // Search by job if (character.job && character.job.toLowerCase().includes(query)) return true; // Search by abilities if (character.abilities && character.abilities.toLowerCase().includes(query)) return true; // Search by spells if (character.spells && character.spells.toLowerCase().includes(query)) return true; return false; }); } /** * Toggle the acquired status of a character * @param {number} id - The ID of the character to toggle * @returns {Promise} - The updated character */ async toggleAcquired(id) { try { // Create the API endpoint URL const apiUrl = `/api/trusts/${id}/toggle-acquired`; // Send a PUT request to toggle the acquired status const response = await fetch(apiUrl, { method: 'PUT', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`API request failed with status ${response.status}`); } // Get the updated character const updatedChar = await response.json(); // Update the character in our local data if (this.characters[updatedChar.name]) { this.characters[updatedChar.name].acquired = updatedChar.acquired; } return updatedChar; } catch (error) { console.error('Error toggling acquired status:', error); throw error; } } } // Create a global instance of the parser const trustParser = new TrustParser();