typescriptamazon-s3aws-lambdaserverlesssst

SST Typescript retrieve s3 bucket object names and metadata key pairs with filtering


I am new to SST, I tried to create an upload API + Lambda to upload files and set metadata and it worked. Now I am trying to create an API + Lambda to retrieve the list of files. I was able to retrieve the full list of files. However, when I tried to filter the results based on the metadata createdBy = USERNAME I wasn't able to do it, any guidance would be appreciated.

import { GetObjectOutput } from "aws-sdk/clients/s3";
import * as AWS from 'aws-sdk';

export async function main(): Promise<any> {
  const s3 = new AWS.S3();

  const params = {
    Bucket: 'uni-artifacts', 
  };

  try {
    console.log("Listing objects in bucket:", params.Bucket);
    const data = await s3.listObjectsV2(params).promise();

    if (!data.Contents) {
      return {
        statusCode: 200,
        body: JSON.stringify({ message: "No files found in the bucket" }),
      };
    }

    console.log("Number of objects found:", data.Contents.length);

    const files = data.Contents.map(async (obj) => {
      try {
        const metadata = await getMetadata(s3, obj.Key!, () => {});
        return {
          Key: obj.Key,
          Metadata: metadata,
        };
      } catch (error) {
        console.error("Error getting metadata for", obj.Key, error);
        return { Key: obj.Key }; // Example handling, adjust based on needs
      }
    });

    // Wait for all promises to settle (including potential rejections)
    const resolvedFiles = await Promise.allSettled(files);

    // Filter and handle successful and rejected results
    const successfulFiles = resolvedFiles.filter((result) => result.status === "fulfilled")
      .map((result) => result); // No need to access value here

    const failedFiles = resolvedFiles.filter((result) => result.status === "rejected")
      .map((result) => {
        // Handle potential undefined data.Contents
        if (data?.Contents) {
          const failedFile = data.Contents.find(obj => obj.Key === (result as PromiseRejectedResult).reason?.Key); // Type cast to access reason on rejected result
          return failedFile ? { Key: failedFile.Key, Error: (result as PromiseRejectedResult).reason?.message } : undefined;
        } else {
          // Handle case where data.Contents is undefined (e.g., error during listObjectsV2)
          return { Error: "Error listing objects in bucket" }; // Example error message, adjust as needed
        }
      });

    const responseBody = {
      statusCode: 200,
      body: JSON.stringify({
        successfulFiles,
        failedFiles: failedFiles.filter(file => file), // Filter out undefined entries in failedFiles
      }),
    };

    return responseBody;
  } catch (error) {
    console.error("Error retrieving files:", error);
    return {
      statusCode: 500,
      body: JSON.stringify({ message: "Error retrieving files" }),
    };
  }
}

function getMetadata(s3: AWS.S3, key: string, callback: (error: Error | undefined, metadata?: GetObjectOutput["Metadata"]) => void) {
  const params = {
    Bucket: 'uni-artifacts', // Replace with your bucket name
    Key: key,
  };

  s3.headObject(params, (err, data) => {
    if (err) {
      console.error("Error getting metadata for", key, err);
      return callback(err);
    }

    console.log("Got metadata for:", key);
    callback(undefined, data.Metadata);
  });
}


I tried to create an arraylist to store the filtered results. I think my logic is wrong.


Solution

  •     import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
    import * as AWS from 'aws-sdk';
    
    interface S3ObjectMetadata {
      createdBy?: string; // Optional createdBy property
      creationDate?: string; // Optional creationDate property
    }
    
    export async function main(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> {
      const s3 = new AWS.S3();
    
      // Parse the username parameter from the request URL
      const username = event.queryStringParameters?.username;
      console.log("Retrieved Username: " + username);
    
      const params = {
        Bucket: 'uni-artifacts', 
      };
    
      try {
        console.log("Listing objects in bucket:", params.Bucket);
        const data = await s3.listObjectsV2(params).promise();
    
        if (!data.Contents) {
          return {
            statusCode: 200,
            body: JSON.stringify({ message: "No files found in the bucket" }),
          };
        }
    
        console.log("Number of objects found:", data.Contents.length);
    
        const files = data.Contents.map(async (obj) => {
          try {
            const metadata = await getMetadata(s3, obj.Key!);
            return {
              Key: obj.Key,
              Metadata: metadata,
            };
          } catch (error) {
            console.error("Error getting metadata for", obj.Key, error);
            return { Key: obj.Key }; 
          }
        });
    
        const resolvedFiles = await Promise.allSettled(files);
    
        const successfulFiles = resolvedFiles.filter((result) => result.status === "fulfilled")
          .map((result) => {
            // Check if the result is fulfilled and has a value
            if (result.status === "fulfilled" && result.value) {
              const { Key, Metadata } = result.value; // Destructure if value exists
                //extract createdBy from metadata
              const createdBy = Metadata && typeof Metadata === 'object' && 'createdby' in Metadata ? Metadata.createdby : undefined;
              
              return {
                Key,
                Metadata: {
                  createdBy, 
                }
              };
            } else {
              // Handle unsuccessful results or cases where value is missing
              console.error("Unexpected result:", result); 
              return { Key: undefined, Metadata: {} }; //  empty object 
            }
          });
    
        // Filter files based on createdBy metadata matching the username
        const filteredFiles = username ?
          successfulFiles.filter(file => {
            const createdBy = file.Metadata?.createdBy || '';
            console.log(`Comparing username '${username}' with createdBy '${createdBy}'`);
            const isMatch = createdBy === username;
            console.log(`Comparison result: ${isMatch}`);
            return isMatch;
          }) :
          successfulFiles;
    
        console.log("Filtered files:", filteredFiles); 
    
        const responseBody = {
          statusCode: 200,
          body: JSON.stringify({
            files: filteredFiles,
          }),
        };
    
        return responseBody;
      } catch (error) {
        console.error("Error retrieving files:", error);
        return {
          statusCode: 500,
          body: JSON.stringify({ message: "Error retrieving files" }),
        };
      }
    }
    
    function getMetadata(s3: AWS.S3, key: string): Promise<S3ObjectMetadata | undefined> {
      const params = {
        Bucket: 'uni-artifacts', 
        Key: key,
      };
    
      return new Promise((resolve, reject) => {
        s3.headObject(params, (err, data) => {
          if (err) {
            console.error("Error getting metadata for", key, err);
            reject(err);
          } else {
            const metadata = data.Metadata;
            console.log("Retrieved metadata for " + key, metadata);
            resolve(metadata);
          }
        });
      });
    }