Source: utils/s3Client.js

import {Client as minioClient} from 'minio'
//import aws from 'aws-sdk'
import {PassThrough} from 'stream'


export let myMinioClient
export let publicMinioClient
export let s3

// ── Public bucket ─────────────────────────────────────────────────────────────


/** Bucket for publicly readable assets (app icons, etc.) */
export const PUBLIC_BUCKET = process.env.publicBucket ?? 'commtool-public';

/** Bucket for org data/manifest assets (PWA icons, manifests, etc.) */
export const DATA_BUCKET = process.env.bucket ?? process.env.bucket ?? 'kpe20';

/**
 * Build the direct public URL for an object in the public bucket.
 *
 * First checks S3publicBaseUrl for a full override, then tries
 * dedicated public S3 connection vars (publicS3endPoint etc.),
 * and finally falls back to the protected S3 endpoint as before.
 *
 * @param {string} key
 * @returns {string}
 */
export const publicObjectUrl = (key) =>
    `${process.env.S3publicBaseUrl ?? `https://${process.env.publicS3endPoint ?? process.env.S3endPoint}:${process.env.publicS3port ?? process.env.S3port}`}/${PUBLIC_BUCKET}/${key}`;

/** Set an anonymous read policy on a bucket (MinIO / S3).
 *  Uses publicMinioClient if available, otherwise falls back to myMinioClient. */
const setPublicReadPolicy = (bucket) => {
    const policy = JSON.stringify({
        Version: '2012-10-17',
        Statement: [{
            Effect: 'Allow',
            Principal: { AWS: ['*'] },
            Action:    ['s3:GetObject'],
            Resource:  [`arn:aws:s3:::${bucket}/*`],
        }],
    });
    const client = publicMinioClient ?? myMinioClient;
    return new Promise((resolve, reject) => {
        client.setBucketPolicy(bucket, policy, (err) => {
            if (err) reject(err); else resolve();
        });
    });
};

/**
 * Ensure the public bucket exists and is publicly readable.
 * Uses publicMinioClient if available (independent endpoint),
 * otherwise falls back to myMinioClient for backwards compatibility.
 */
export const initPublicBucket = async () => {
    const client = publicMinioClient ?? myMinioClient;
    return new Promise((resolve, reject) => {
        if (!client) { reject('s3 client not initialized'); return; }
        client.bucketExists(PUBLIC_BUCKET, (err, exists) => {
            if (err) { reject(err); return; }
            const onExists = () => setPublicReadPolicy(PUBLIC_BUCKET).then(resolve).catch(reject);
            if (!exists) {
                client.makeBucket(PUBLIC_BUCKET, (err) => {
                    if (err) { reject(err); return; }
                    console.log(`bucket ${PUBLIC_BUCKET} created`);
                    onExists();
                });
            } else {
                console.log(`bucket ${PUBLIC_BUCKET} already exists`);
                onExists();
            }
        });
    });
};



const configLifeCycle=(bucket)=>
{
    const lifecycleConfig={
        Rule: [{
            "ID": "7 days - expire",
            "Status": "Enabled",
            "Filter": {
                "Prefix":'public/7',
            },
            "Expiration": {
                "Days": "7"
            } 
        },
        {
            "ID": "7 days - delete marker",
            "Status": "Enabled",
            "Filter": {
                "Prefix":'public/7',
            },
            "Expiration": {
                "ExpiredObjectDeleteMarker": true
            } 
        },
        {
            "ID": "30 days - expire",
            "Status": "Enabled",
            "Filter": {
                "Prefix":'public/30',
            },
            "Expiration": {
                "Days": "30"
            } 
        },
        {
            "ID": "30 days - delete marker",
            "Status": "Enabled",
            "Filter": {
                "Prefix":'public/30',
            },
            "Expiration": {
                "ExpiredObjectDeleteMarker": true
            } 
        },
        {
            "ID": "100 days - expire",
            "Status": "Enabled",
            "Filter": {
                "Prefix":'public/100',
            },
            "Expiration": {
                "Days": "100"
            } 
        },
        {
            "ID": "100 days - delete marker",
            "Status": "Enabled",
            "Filter": {
                "Prefix":'public/100',
            },
            "Expiration": {
                "ExpiredObjectDeleteMarker": true
            } 
        },
        {
            "ID": "365 days - expire",
            "Status": "Enabled",
            "Filter": {
                "Prefix":'public/365',
            },
            "Expiration": {
                "Days": "365"
            } 
        },
        {
            "ID": "365 days - delete marker",
            "Status": "Enabled",
            "Filter": {
                "Prefix":'public/365',
            },
            "Expiration": {
                "ExpiredObjectDeleteMarker": true
            } 
        }
    ]
    }


    return new Promise((resolve, reject) => {
        myMinioClient.setBucketLifecycle(bucket,lifecycleConfig, function (err) {
            if (err) {
                reject(new Error("minio life cycle config failed: " + err))
            }
            else
            {
                resolve()
            }
        })
    })
}


export const initBucket= async (bucket)=>
{
    
    return new Promise((resolve, reject) =>
    {   
        if(!myMinioClient)
        {
            console.error('minio client not initialized')
            reject('minio client not initialized')
            return
        }
        myMinioClient.bucketExists(bucket, function(err, exists) 
        {
            if (err) 
            {
               throw new Error(`Error checking if bucket exists: ${err.message}`);
            }
            if (!exists) 
            {
                myMinioClient.makeBucket(bucket,  function(err) {
                if (err)
                {
                    console.error('Error creating bucket',bucket,err)
                }
                else
                {
                    console.log(`bucket ${bucket} created successfully`)
                    configLifeCycle(bucket).then(()=>resolve())
                }
                })
            }
            else
            {
                console.log(`bucket ${bucket} already exists`)
                configLifeCycle(bucket).then(()=>resolve())
            }

        })   
    })
}


export const initS3= async () =>
{
    
    try
    {
        myMinioClient = new minioClient({
            endPoint: process.env.S3endPoint,
            port: parseInt(process.env.S3port),
            useSSL: process.env.S3useSSL==='true',
            accessKey: process.env.S3accessKey,
            secretKey: process.env.S3secretKey,
            pathStyle: true,
            region: process.env.S3region || 'us-east-1' // Default region if not specified
        });

        // If dedicated public S3 endpoint vars are set, create a separate client
        if (process.env.publicS3endPoint) {
            publicMinioClient = new minioClient({
                endPoint: process.env.publicS3endPoint,
                port: parseInt(process.env.publicS3port ?? process.env.S3port),
                useSSL: (process.env.publicS3useSSL ?? process.env.S3useSSL) === 'true',
                accessKey: process.env.publicS3accessKey ?? process.env.S3accessKey,
                secretKey: process.env.publicS3secretKey ?? process.env.S3secretKey,
                pathStyle: true,
                region: process.env.publicS3region || process.env.S3region || 'us-east-1'
            });
        }

            // Create S3-compatible wrapper around MinIO client for busboy compatibility
        s3 = {
            upload: (params, callback) => {
                console.log('=== S3 UPLOAD DEBUG START ===');
                
                const objectStream = params.Body instanceof PassThrough ? params.Body : new PassThrough();

                // Stream-Events für Debugging
                objectStream.on('data', (chunk) => {
                    console.log('Stream: received data chunk of', chunk.length, 'bytes');
                });
                
                objectStream.on('end', () => {
                    console.log('Stream: END event received');
                });
                
                objectStream.on('error', (err) => {
                    console.log('Stream: ERROR event:', err);
                    callback(err);
                });

                console.log('Calling myMinioClient.putObject...');
            
                try {
                    myMinioClient.putObject(
                        params.Bucket,
                        params.Key,
                        objectStream,  // ← Verwende den PassThrough-Stream
                        null,
                        params.Metadata || {},
                        (err, etag) => {
                            console.log('=== MINIO CALLBACK CALLED ===');
                            if (err) return callback(err);
                            callback(null, {
                                Location: `https://${process.env.S3endPoint}:${parseInt(process.env.S3port)}/${params.Bucket}/${params.Key}`,
                                key: params.Key,
                                Key: params.Key,
                                Bucket: params.Bucket,
                                ETag: etag
                            });
                        }
                    );
                } catch (err) {
                    callback(err);
                }

                // Gib das S3-kompatible Interface zurück
                return {
                    Body: objectStream  // ← Das ist der Stream, in den geschrieben wird
                };
            }
        };
        await initBucket(process.env.bucket);
        await initPublicBucket();
    }
    catch(e)
    {
        console.error('minio connection failed')
        if(!process.env.S3endPoint)
        console.error('you have to pass the environment variable "S3endPoint"')
        if(!process.env.S3port)
        console.error('you have to pass the environment variable "S3port"')
        if(!process.env.S3accessKey)
        console.error('you have to pass the environment variable "S3accessKey"')
        if(!process.env.S3port)
        console.error('you have to pass the environment variable "S3secretKey"')
        console.error('your connection data may be invaliud or you forgot to pass S3useSSL=true for an ssl connection ')
    }
}   


/* original independant s3 client code, not used anymore, but kept for reference
try {

 s3 = new aws.S3({
    endpoint:  `${process.env.S3endPoint}:${process.env.S3port}`,
    accessKeyId:  process.env.S3accessKey,
    secretAccessKey: process.env.S3secretKey,
    sslEnabled: process.env.S3useSSL==='true',
    s3ForcePathStyle: true,
  })
}
catch(e)
{
    console.error('minio connection failed')
    if(!process.env.S3endPoint)
      console.error('you have to pass the environment variable "S3endPoint"')
    if(!process.env.S3port)
      console.error('you have to pass the environment variable "S3port"')
    if(!process.env.S3accessKey)
      console.error('you have to pass the environment variable "S3accessKey"')
    if(!process.env.S3port)
      console.error('you have to pass the environment variable "S3secretKey"')
    console.error('your connection data may be invaliud or you frogot to pass S3useSSL=true for an ssl connection ')
}*/