Source: Router/languageFile/service.js

/**
 * Language File Service Layer
 *
 * Handles business logic for language file storage, retrieval and deletion
 * via the configured S3/MinIO bucket.
 *
 * @module LanguageFileService
 */

// @ts-check

import { myMinioClient } from '../../utils/s3Client.js';

const bucket = process.env.bucket ? process.env.bucket : 'kpe20';

/**
 * Generate the public URL for an uploaded language file.
 *
 * @param {{ fields: any, filename: { filename: string }, extension: string, params: { api: string } }} opts
 * @returns {string}
 */
export const languageUrlGen = ({ fields, filename, extension, params }) => {
    return `${params.api}/languages/${filename.filename}`;
};

/**
 * MIME-type filter that accepts only JSON files.
 *
 * @param {string} mimeType
 * @param {string} extension
 * @returns {boolean}
 */
export const filterJSON = (mimeType, extension) => {
    return ['application/json'].includes(mimeType) && ['json'].includes(extension);
};

/**
 * Build the scoped storage key from a raw key and a UID prefix.
 *
 * @param {string} key  - The raw object key returned by S3/MinIO.
 * @param {string} UID  - The owner UID used as a path prefix.
 * @returns {string}
 */
export const keyComponents = (key, UID) => {
    return `${UID}/${encodeURIComponent(key.replace(`${UID}/`, ''))}`;
};

/**
 * Retrieve a language file stream from the object store.
 *
 * @param {string} app      - Application identifier (bucket sub-path).
 * @param {string} filename - File name inside the languages directory.
 * @returns {Promise<import('stream').Readable>} Readable stream of the object.
 * @throws {Error} When the object cannot be found or accessed.
 */
export const getLanguageFile = (app, filename) => {
    return new Promise((resolve, reject) => {
        myMinioClient.getObject(
            bucket,
            `${app}/languages/${filename}`,
            /** @param {any} err @param {any} data */
            (err, data) => {
                if (err) {
                    reject(err);
                } else {
                    resolve(data);
                }
            }
        );
    });
};

/**
 * List all language files across all applications.
 *
 * @returns {Promise<Array<{name: string, size: number, lastModified: Date}>>}
 */
export const listLanguageFiles = () => {
    return new Promise((resolve, reject) => {
        /** @type {{name: string, size: number, lastModified: Date}[]} */
        const objects = [];
        const stream = myMinioClient.extensions.listObjectsV2WithMetadata(bucket, '', true);

        stream.on('data', /** @param {any} obj */ (obj) => {
            if (obj.name && obj.name.includes('/languages/')) {
                objects.push({ name: obj.name, size: obj.size, lastModified: obj.lastModified });
            }
        });

        stream.on('end', () => resolve(objects));
        stream.on('error', /** @param {any} err */ (err) => reject(err));
    });
};

/**
 * List language files for a specific application.
 *
 * @param {string} app - Application identifier (bucket sub-path prefix).
 * @returns {Promise<Array<{name: string, size: number, lastModified: Date}>>}
 */
export const listLanguageFilesByApp = (app) => {
    return new Promise((resolve, reject) => {
        /** @type {{name: string, size: number, lastModified: Date}[]} */
        const objects = [];
        const stream = myMinioClient.extensions.listObjectsV2WithMetadata(bucket, `${app}/languages/`, true);

        stream.on('data', /** @param {any} obj */ (obj) => {
            if (obj.name) {
                objects.push({ name: obj.name, size: obj.size, lastModified: obj.lastModified });
            }
        });

        stream.on('end', () => resolve(objects));
        stream.on('error', /** @param {any} err */ (err) => reject(err));
    });
};

/**
 * Delete a language file from the object store.
 *
 * @param {string} app      - Application identifier (bucket sub-path).
 * @param {string} filename - File name inside the languages directory.
 * @returns {Promise<void>}
 */
export const deleteLanguageFile = async (app, filename) => {
    return myMinioClient.removeObject(bucket, `${app}/languages/${filename}`);
};

export { bucket };