import { Database, QueryResult, RunResult, SQLiteValue } from 'node-sqlite3-wasm'; import System from "../System.js"; export interface LocationQueryResult extends Record { loc_id: string; loc_name: string; element_id: string; element_name: string; element_description: string; sub_element_id: string; sub_elem_name: string; sub_elem_description: string; series_location_id: string | null; } export interface LocationElementQueryResult extends Record { sub_element_id: string; sub_elem_name: string; sub_elem_description: string; element_id: string; element_name: string; element_description: string; } export interface LocationByTagResult extends Record { element_name: string; element_description: string; sub_elem_name: string; sub_elem_description: string; } export interface BookLocationTable extends Record { loc_id: string; book_id: string; user_id: string; loc_name: string; loc_original_name: string; last_update: number; } export interface LocationElementTable extends Record { element_id: string; location: string; user_id: string; element_name: string; original_name: string; element_description: string | null; last_update: number; } export interface LocationSubElementTable extends Record { sub_element_id: string; element_id: string; user_id: string; sub_elem_name: string; original_name: string; sub_elem_description: string | null; last_update: number; } export interface SyncedLocationResult extends Record { loc_id: string; book_id: string; loc_name: string; last_update: number; } export interface SyncedLocationElementResult extends Record { element_id: string; location: string; element_name: string; last_update: number; } export interface SyncedLocationSubElementResult extends Record { sub_element_id: string; element_id: string; sub_elem_name: string; last_update: number; } export default class LocationRepo { /** * Retrieves all locations with their elements and sub-elements for a specific book. * @param userId - The user's unique identifier * @param bookId - The book's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns An array of location query results with nested elements */ static getLocation(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): LocationQueryResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT loc_id, loc_name, element.element_id AS element_id, element.element_name, element.element_description, sub_elem.sub_element_id AS sub_element_id, sub_elem.sub_elem_name, sub_elem.sub_elem_description, location.series_location_id FROM book_location AS location LEFT JOIN location_element AS element ON location.loc_id = element.location LEFT JOIN location_sub_element AS sub_elem ON element.element_id = sub_elem.element_id WHERE location.user_id = ? AND location.book_id = ?'; const params: SQLiteValue[] = [userId, bookId]; const locations: LocationQueryResult[] = db.all(query, params) as LocationQueryResult[]; return locations; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les emplacements.` : `Unable to retrieve locations.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Inserts a new location section for a book. * @param userId - The user's unique identifier * @param locationId - The new location's unique identifier * @param bookId - The book's unique identifier * @param encryptedName - The encrypted location name * @param originalName - The original (unencrypted) location name * @param lang - The language for error messages ('fr' or 'en') * @returns The location ID if insertion was successful */ static insertLocation(userId: string, locationId: string, bookId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr', seriesLocationId: string | null = null): string { let insertResult: RunResult; try { const db: Database = System.getDb(); const query: string = seriesLocationId ? 'INSERT INTO book_location (loc_id, book_id, user_id, loc_name, loc_original_name, series_location_id, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)' : 'INSERT INTO book_location (loc_id, book_id, user_id, loc_name, loc_original_name, last_update) VALUES (?, ?, ?, ?, ?, ?)'; const params: SQLiteValue[] = seriesLocationId ? [locationId, bookId, userId, encryptedName, originalName, seriesLocationId, System.timeStampInSeconds()] : [locationId, bookId, userId, encryptedName, originalName, System.timeStampInSeconds()]; insertResult = db.run(query, params); } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible d'ajouter la section d'emplacement.` : `Unable to add location section.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } if (!insertResult || insertResult.changes === 0) { throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout de la section d'emplacement.` : `Error adding location section.`); } return locationId; } /** * Inserts a new location element within a location section. * @param userId - The user's unique identifier * @param elementId - The new element's unique identifier * @param locationId - The parent location's unique identifier * @param encryptedName - The encrypted element name * @param originalName - The original (unencrypted) element name * @param lang - The language for error messages ('fr' or 'en') * @returns The element ID if insertion was successful */ static insertLocationElement(userId: string, elementId: string, locationId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr'): string { let insertResult: RunResult; try { const db: Database = System.getDb(); const query: string = 'INSERT INTO location_element (element_id, location, user_id, element_name, original_name, element_description, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)'; const params: SQLiteValue[] = [elementId, locationId, userId, encryptedName, originalName, '', System.timeStampInSeconds()]; insertResult = db.run(query, params); } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible d'ajouter l'élément d'emplacement.` : `Unable to add location element.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } if (!insertResult || insertResult.changes === 0) { throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout de l'élément d'emplacement.` : `Error adding location element.`); } return elementId; } /** * Inserts a new sub-element within a location element. * @param userId - The user's unique identifier * @param subElementId - The new sub-element's unique identifier * @param elementId - The parent element's unique identifier * @param encryptedName - The encrypted sub-element name * @param originalName - The original (unencrypted) sub-element name * @param lang - The language for error messages ('fr' or 'en') * @returns The sub-element ID if insertion was successful */ static insertLocationSubElement(userId: string, subElementId: string, elementId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr'): string { let insertResult: RunResult; try { const db: Database = System.getDb(); const query: string = 'INSERT INTO location_sub_element (sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)'; const params: SQLiteValue[] = [subElementId, elementId, userId, encryptedName, originalName, '', System.timeStampInSeconds()]; insertResult = db.run(query, params); } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible d'ajouter le sous-élément d'emplacement.` : `Unable to add location sub-element.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } if (!insertResult || insertResult.changes === 0) { throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout du sous-élément d'emplacement.` : `Error adding location sub-element.`); } return subElementId; } /** * Updates an existing location sub-element's name and description. * @param userId - The user's unique identifier * @param id - The sub-element's unique identifier * @param encryptedName - The new encrypted sub-element name * @param originalName - The new original (unencrypted) sub-element name * @param encryptDescription - The new encrypted description * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the update affected at least one row */ static updateLocationSubElement(userId: string, id: string, encryptedName: string, originalName: string, encryptDescription: string, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = ` UPDATE location_sub_element SET sub_elem_name = ?, original_name = ?, sub_elem_description = ?, last_update = ? WHERE sub_element_id = ? AND user_id = ? `; const params: SQLiteValue[] = [encryptedName, originalName, encryptDescription, lastUpdate, id, userId]; const updateResult: RunResult = db.run(query, params); return updateResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de mettre à jour le sous-élément d'emplacement.` : `Unable to update location sub-element.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Updates an existing location element's name and description. * @param userId - The user's unique identifier * @param id - The element's unique identifier * @param encryptedName - The new encrypted element name * @param originalName - The new original (unencrypted) element name * @param encryptedDescription - The new encrypted description * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the update affected at least one row */ static updateLocationElement(userId: string, id: string, encryptedName: string, originalName: string, encryptedDescription: string, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = ` UPDATE location_element SET element_name = ?, original_name = ?, element_description = ?, last_update = ? WHERE element_id = ? AND user_id = ? `; const params: SQLiteValue[] = [encryptedName, originalName, encryptedDescription, lastUpdate, id, userId]; const updateResult: RunResult = db.run(query, params); return updateResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de mettre à jour l'élément d'emplacement.` : `Unable to update location element.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Updates an existing location section's name. * @param userId - The user's unique identifier * @param id - The location section's unique identifier * @param encryptedName - The new encrypted location name * @param originalName - The new original (unencrypted) location name * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the update affected at least one row */ static updateLocationSection(userId: string, id: string, encryptedName: string, originalName: string, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = ` UPDATE book_location SET loc_name = ?, loc_original_name = ?, last_update = ? WHERE loc_id = ? AND user_id = ? `; const params: SQLiteValue[] = [encryptedName, originalName, lastUpdate, id, userId]; const updateResult: RunResult = db.run(query, params); return updateResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de mettre à jour la section d'emplacement.` : `Unable to update location section.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Deletes a location section by its ID. * @param userId - The user's unique identifier * @param locationId - The location section's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns True if the deletion affected at least one row */ static deleteLocationSection(userId: string, locationId: string, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'DELETE FROM book_location WHERE loc_id = ? AND user_id = ?'; const params: SQLiteValue[] = [locationId, userId]; const deleteResult: RunResult = db.run(query, params); return deleteResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de supprimer la section d'emplacement.` : `Unable to delete location section.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Deletes a location element by its ID. * @param userId - The user's unique identifier * @param elementId - The element's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns True if the deletion affected at least one row */ static deleteLocationElement(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'DELETE FROM location_element WHERE element_id = ? AND user_id = ?'; const params: SQLiteValue[] = [elementId, userId]; const deleteResult: RunResult = db.run(query, params); return deleteResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de supprimer l'élément d'emplacement.` : `Unable to delete location element.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Deletes a location sub-element by its ID. * @param userId - The user's unique identifier * @param subElementId - The sub-element's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns True if the deletion affected at least one row */ static deleteLocationSubElement(userId: string, subElementId: string, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const query: string = 'DELETE FROM location_sub_element WHERE sub_element_id = ? AND user_id = ?'; const params: SQLiteValue[] = [subElementId, userId]; const deleteResult: RunResult = db.run(query, params); return deleteResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de supprimer le sous-élément d'emplacement.` : `Unable to delete location sub-element.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all location elements and sub-elements for tagging purposes. * @param userId - The user's unique identifier * @param bookId - The book's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns An array of location elements with their sub-elements */ static fetchLocationTags(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): LocationElementQueryResult[] { try { const db: Database = System.getDb(); const query: string = ` SELECT se.sub_element_id AS sub_element_id, se.sub_elem_name, se.sub_elem_description, el.element_id AS element_id, el.element_name, el.element_description FROM location_sub_element AS se RIGHT JOIN location_element AS el ON se.element_id = el.element_id LEFT JOIN book_location AS lo ON el.location = lo.loc_id WHERE lo.book_id = ? AND lo.user_id = ? `; const params: SQLiteValue[] = [bookId, userId]; const locationTags: LocationElementQueryResult[] = db.all(query, params) as LocationElementQueryResult[]; return locationTags; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les tags d'emplacement.` : `Unable to retrieve location tags.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches locations by their tag IDs (element or sub-element IDs). * @param userId - The user's unique identifier * @param locations - An array of location tag IDs to search for * @param lang - The language for error messages ('fr' or 'en') * @returns An array of locations matching the provided tags * @throws Error if no tags are provided or no locations are found */ static fetchLocationsByTags(userId: string, locations: string[], lang: 'fr' | 'en' = 'fr'): LocationByTagResult[] { if (locations.length === 0) { throw new Error(lang === 'fr' ? `Aucun tag fourni.` : `No tags provided.`); } try { const db: Database = System.getDb(); const locationPlaceholders: string = locations.map((): string => '?').join(','); const query: string = ` SELECT el.element_name, el.element_description, se.sub_elem_name, se.sub_elem_description FROM location_element AS el LEFT JOIN location_sub_element AS se ON el.element_id = se.element_id WHERE el.user_id = ? AND (el.element_id IN (${locationPlaceholders}) OR se.sub_element_id IN (${locationPlaceholders})) `; const params: SQLiteValue[] = [userId, ...locations, ...locations]; const locationsByTags: LocationByTagResult[] = db.all(query, params) as LocationByTagResult[]; if (locationsByTags.length === 0) { throw new Error(lang === 'fr' ? `Aucun emplacement trouvé avec ces tags.` : `No locations found with these tags.`); } return locationsByTags; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les emplacements par tags.` : `Unable to retrieve locations by tags.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Checks if a location exists in the database. * @param userId - The user's unique identifier * @param locId - The location's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns True if the location exists, false otherwise */ static isLocationExist(userId: string, locId: string, lang: "fr" | "en"): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT 1 FROM `book_location` WHERE `loc_id` = ? AND `user_id` = ?'; const params: SQLiteValue[] = [locId, userId]; const existingLocation: QueryResult | null = db.get(query, params) || null; return existingLocation !== null; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de vérifier l'existence de l'emplacement.` : `Unable to check location existence.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Checks if a location element exists in the database. * @param userId - The user's unique identifier * @param elementId - The element's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns True if the location element exists, false otherwise */ static isLocationElementExist(userId: string, elementId: string, lang: "fr" | "en"): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT 1 FROM `location_element` WHERE `element_id` = ? AND `user_id` = ?'; const params: SQLiteValue[] = [elementId, userId]; const existingElement: QueryResult | null = db.get(query, params) || null; return existingElement !== null; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de vérifier l'existence de l'élément d'emplacement.` : `Unable to check location element existence.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Checks if a location sub-element exists in the database. * @param userId - The user's unique identifier * @param subElementId - The sub-element's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns True if the location sub-element exists, false otherwise */ static isLocationSubElementExist(userId: string, subElementId: string, lang: "fr" | "en"): boolean { try { const db: Database = System.getDb(); const query: string = 'SELECT 1 FROM `location_sub_element` WHERE `sub_element_id` = ? AND `user_id` = ?'; const params: SQLiteValue[] = [subElementId, userId]; const existingSubElement: QueryResult | null = db.get(query, params) || null; return existingSubElement !== null; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de vérifier l'existence du sous-élément d'emplacement.` : `Unable to check location sub-element existence.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all locations for a specific book. * @param userId - The user's unique identifier * @param bookId - The book's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns A promise resolving to an array of book location records */ static async fetchBookLocations(userId: string, bookId: string, lang: 'fr' | 'en'): Promise { try { const db: Database = System.getDb(); const query: string = ` SELECT loc_id, book_id, user_id, loc_name, loc_original_name, last_update FROM book_location WHERE user_id = ? AND book_id = ? `; const params: SQLiteValue[] = [userId, bookId]; const bookLocations: BookLocationTable[] = db.all(query, params) as BookLocationTable[]; return bookLocations; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les lieux.` : `Unable to retrieve locations.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all elements for a specific location. * @param userId - The user's unique identifier * @param locationId - The location's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns A promise resolving to an array of location element records */ static async fetchLocationElements(userId: string, locationId: string, lang: 'fr' | 'en'): Promise { try { const db: Database = System.getDb(); const query: string = ` SELECT element_id, location, user_id, element_name, original_name, element_description, last_update FROM location_element WHERE user_id = ? AND location = ? `; const params: SQLiteValue[] = [userId, locationId]; const locationElements: LocationElementTable[] = db.all(query, params) as LocationElementTable[]; return locationElements; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments de lieu.` : `Unable to retrieve location elements.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all sub-elements for a specific location element. * @param userId - The user's unique identifier * @param elementId - The element's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns A promise resolving to an array of location sub-element records */ static async fetchLocationSubElements(userId: string, elementId: string, lang: 'fr' | 'en'): Promise { try { const db: Database = System.getDb(); const query: string = ` SELECT sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update FROM location_sub_element WHERE user_id = ? AND element_id = ? `; const params: SQLiteValue[] = [userId, elementId]; const locationSubElements: LocationSubElementTable[] = db.all(query, params) as LocationSubElementTable[]; return locationSubElements; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les sous-éléments de lieu.` : `Unable to retrieve location sub-elements.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all synced locations for a user (used for synchronization). * @param userId - The user's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns An array of synced location records */ static fetchSyncedLocations(userId: string, lang: 'fr' | 'en'): SyncedLocationResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT loc_id, book_id, loc_name, last_update FROM book_location WHERE user_id = ?'; const params: SQLiteValue[] = [userId]; const syncedLocations: SyncedLocationResult[] = db.all(query, params) as SyncedLocationResult[]; return syncedLocations; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les lieux synchronisés.` : `Unable to retrieve synced locations.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all synced location elements for a user (used for synchronization). * @param userId - The user's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns An array of synced location element records */ static fetchSyncedLocationElements(userId: string, lang: 'fr' | 'en'): SyncedLocationElementResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT element_id, location, element_name, last_update FROM location_element WHERE user_id = ?'; const params: SQLiteValue[] = [userId]; const syncedLocationElements: SyncedLocationElementResult[] = db.all(query, params) as SyncedLocationElementResult[]; return syncedLocationElements; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments de lieu synchronisés.` : `Unable to retrieve synced location elements.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches all synced location sub-elements for a user (used for synchronization). * @param userId - The user's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns An array of synced location sub-element records */ static fetchSyncedLocationSubElements(userId: string, lang: 'fr' | 'en'): SyncedLocationSubElementResult[] { try { const db: Database = System.getDb(); const query: string = 'SELECT sub_element_id, element_id, sub_elem_name, last_update FROM location_sub_element WHERE user_id = ?'; const params: SQLiteValue[] = [userId]; const syncedLocationSubElements: SyncedLocationSubElementResult[] = db.all(query, params) as SyncedLocationSubElementResult[]; return syncedLocationSubElements; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de récupérer les sous-éléments de lieu synchronisés.` : `Unable to retrieve synced location sub-elements.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Inserts a synced location from the remote server. * @param locId - The location's unique identifier * @param bookId - The book's unique identifier * @param userId - The user's unique identifier * @param locName - The encrypted location name * @param locOriginalName - The original (unencrypted) location name * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the insertion affected at least one row */ static insertSyncLocation(locId: string, bookId: string, userId: string, locName: string, locOriginalName: string, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = ` INSERT INTO book_location (loc_id, book_id, user_id, loc_name, loc_original_name, last_update) VALUES (?, ?, ?, ?, ?, ?) `; const params: SQLiteValue[] = [locId, bookId, userId, locName, locOriginalName, lastUpdate]; const insertResult: RunResult = db.run(query, params); return insertResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible d'insérer le lieu.` : `Unable to insert location.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Inserts a synced location element from the remote server. * @param elementId - The element's unique identifier * @param location - The parent location's unique identifier * @param userId - The user's unique identifier * @param elementName - The encrypted element name * @param originalName - The original (unencrypted) element name * @param elementDescription - The encrypted element description (can be null) * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the insertion affected at least one row */ static insertSyncLocationElement(elementId: string, location: string, userId: string, elementName: string, originalName: string, elementDescription: string | null, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = ` INSERT INTO location_element (element_id, location, user_id, element_name, original_name, element_description, last_update) VALUES (?, ?, ?, ?, ?, ?, ?) `; const params: SQLiteValue[] = [elementId, location, userId, elementName, originalName, elementDescription, lastUpdate]; const insertResult: RunResult = db.run(query, params); return insertResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible d'insérer l'élément du lieu.` : `Unable to insert location element.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Inserts a synced location sub-element from the remote server. * @param subElementId - The sub-element's unique identifier * @param elementId - The parent element's unique identifier * @param userId - The user's unique identifier * @param subElemName - The encrypted sub-element name * @param originalName - The original (unencrypted) sub-element name * @param subElemDescription - The encrypted sub-element description (can be null) * @param lastUpdate - The timestamp of the last update * @param lang - The language for error messages ('fr' or 'en') * @returns True if the insertion affected at least one row */ static insertSyncLocationSubElement(subElementId: string, elementId: string, userId: string, subElemName: string, originalName: string, subElemDescription: string | null, lastUpdate: number, lang: 'fr' | 'en'): boolean { try { const db: Database = System.getDb(); const query: string = ` INSERT INTO location_sub_element (sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update) VALUES (?, ?, ?, ?, ?, ?, ?) `; const params: SQLiteValue[] = [subElementId, elementId, userId, subElemName, originalName, subElemDescription, lastUpdate]; const insertResult: RunResult = db.run(query, params); return insertResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible d'insérer le sous-élément du lieu.` : `Unable to insert location sub-element.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches complete location data by its ID (without user filtering). * @param id - The location's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns A promise resolving to an array of book location records */ static async fetchCompleteLocationById(id: string, lang: "fr" | "en"): Promise { try { const db: Database = System.getDb(); const query: string = ` SELECT loc_id, book_id, user_id, loc_name, loc_original_name, last_update FROM book_location WHERE loc_id = ? `; const params: SQLiteValue[] = [id]; const completeLocation: BookLocationTable[] = db.all(query, params) as BookLocationTable[]; return completeLocation; } catch (error: unknown) { if (error instanceof Error) { throw new Error(lang === 'fr' ? `Impossible de récupérer le lieu complet.` : `Unable to retrieve complete location.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches complete location element data by its ID (without user filtering). * @param id - The element's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns A promise resolving to an array of location element records */ static async fetchCompleteLocationElementById(id: string, lang: "fr" | "en"): Promise { try { const db: Database = System.getDb(); const query: string = ` SELECT element_id, location, user_id, element_name, original_name, element_description, last_update FROM location_element WHERE element_id = ? `; const params: SQLiteValue[] = [id]; const completeLocationElement: LocationElementTable[] = db.all(query, params) as LocationElementTable[]; return completeLocationElement; } catch (error: unknown) { if (error instanceof Error) { throw new Error(lang === 'fr' ? `Impossible de récupérer l'élément de lieu complet.` : `Unable to retrieve complete location element.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Fetches complete location sub-element data by its ID (without user filtering). * @param id - The sub-element's unique identifier * @param lang - The language for error messages ('fr' or 'en') * @returns A promise resolving to an array of location sub-element records */ static async fetchCompleteLocationSubElementById(id: string, lang: "fr" | "en"): Promise { try { const db: Database = System.getDb(); const query: string = ` SELECT sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update FROM location_sub_element WHERE sub_element_id = ? `; const params: SQLiteValue[] = [id]; const completeLocationSubElement: LocationSubElementTable[] = db.all(query, params) as LocationSubElementTable[]; return completeLocationSubElement; } catch (error: unknown) { if (error instanceof Error) { throw new Error(lang === 'fr' ? `Impossible de récupérer le sous-élément de lieu complet.` : `Unable to retrieve complete location sub-element.`); } else { throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } /** * Updates a location section with optional name change and series link. * @param userId - The user's unique identifier * @param sectionId - The section's unique identifier * @param encryptedName - The new encrypted name (optional) * @param originalName - The new original name (optional) * @param seriesLocationId - The series location ID to link (optional, null to unlink) * @param lang - The language for error messages ('fr' or 'en') * @returns True if the update was successful */ static updateSectionWithSeriesLink(userId: string, sectionId: string, encryptedName: string | null, originalName: string | null, seriesLocationId: string | null, lang: 'fr' | 'en' = 'fr'): boolean { try { const db: Database = System.getDb(); const setClauses: string[] = ['last_update=' + System.timeStampInSeconds()]; const params: SQLiteValue[] = []; if (encryptedName !== null && originalName !== null) { setClauses.push('loc_name=?', 'loc_original_name=?'); params.push(encryptedName, originalName); } if (seriesLocationId !== undefined) { setClauses.push('series_location_id=?'); params.push(seriesLocationId); } params.push(sectionId, userId); const query: string = 'UPDATE book_location SET ' + setClauses.join(', ') + ' WHERE loc_id=? AND user_id=?'; const updateResult: RunResult = db.run(query, params); return updateResult.changes > 0; } catch (error: unknown) { if (error instanceof Error) { console.error(`DB Error: ${error.message}`); throw new Error(lang === 'fr' ? `Impossible de mettre à jour la section d'emplacement.` : `Unable to update location section.`); } else { console.error("An unknown error occurred."); throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred."); } } } }