Skip to content
Snippets Groups Projects
s3objects.ts 7.18 KiB
Newer Older
  • Learn to ignore specific revisions
  • import { defineStore } from "pinia";
    
    import type { _Object as S3Object, HeadObjectOutput } from "@aws-sdk/client-s3";
    
    import {
      CopyObjectCommand,
      GetObjectCommand,
    
      PutObjectCommand,
      S3Client,
    } from "@aws-sdk/client-s3";
    import {
      paginateListObjectsV2,
      DeleteObjectCommand,
      DeleteObjectsCommand,
    } from "@aws-sdk/client-s3";
    import { environment } from "@/environment";
    import type { S3Key } from "@/client/s3proxy";
    import { useBucketStore } from "@/stores/buckets";
    import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
    import dayjs from "dayjs";
    import { Upload } from "@aws-sdk/lib-storage";
    import type { Progress } from "@aws-sdk/lib-storage";
    
    export const useS3ObjectStore = defineStore({
      id: "s3objects",
      state: () =>
        ({
          objectMapping: {},
    
          objectMetaMapping: {},
    
          client: new S3Client({
            region: "us-east-1",
            endpoint: environment.S3_URL,
            forcePathStyle: true,
            credentials: {
              accessKeyId: "",
              secretAccessKey: "",
            },
          }),
        }) as {
          objectMapping: Record<string, S3Object[]>;
    
          objectMetaMapping: Record<string, HeadObjectOutput>;
    
          client: S3Client;
        },
      getters: {
        getPresignedUrl(): (bucketName: string, key: string) => Promise<string> {
          return (bucketName, key) => {
    
            const keySplit = key.split("/");
    
            const command = new GetObjectCommand({
              Bucket: bucketName,
              Key: key,
    
              ResponseContentDisposition: `attachment; filename=${
                keySplit[keySplit.length - 1]
              }`,
    
            });
            return getSignedUrl(this.client, command, {
              expiresIn: 30,
            });
          };
        },
      },
      actions: {
        _pushObject(bucketName: string, newObj: S3Object) {
          if (this.objectMapping[bucketName] == undefined) {
            this.fetchS3Objects(bucketName);
          } else {
            const objIndex = this.objectMapping[bucketName].findIndex(
              (obj) => obj.Key === newObj.Key,
            );
            if (objIndex > -1) {
              this.objectMapping[bucketName][objIndex] = newObj;
            } else {
              this.objectMapping[bucketName].push(newObj);
            }
          }
        },
        updateS3Client(s3Key: S3Key) {
          this.client = new S3Client({
            region: "us-east-1",
            endpoint: environment.S3_URL,
            forcePathStyle: true,
            credentials: {
              accessKeyId: s3Key.access_key,
              secretAccessKey: s3Key.secret_key,
            },
          });
        },
        async fetchS3Objects(
          bucketName: string,
          prefix?: string,
          onFinally?: () => void,
        ): Promise<S3Object[]> {
          if (this.objectMapping[bucketName] != undefined) {
            onFinally?.();
          }
          const pag = paginateListObjectsV2(
            { client: this.client },
            { Bucket: bucketName, Prefix: prefix },
          );
          const objs: S3Object[] = [];
          try {
            for await (const page of pag) {
              objs.push(...(page.Contents ?? []));
            }
            this.objectMapping[bucketName] = objs;
          } finally {
            onFinally?.();
          }
          return objs;
        },
    
        fetchS3ObjectMeta(
          bucketName: string,
          key: string,
          onFinally?: () => void,
        ): Promise<HeadObjectOutput> {
          const identifier = bucketName + "/" + key;
          if (this.objectMetaMapping[identifier]) {
            onFinally?.();
          }
          const command = new HeadObjectCommand({
            Bucket: bucketName,
            Key: key,
          });
          return this.client
            .send(command)
            .then((resp) => {
              this.objectMetaMapping[identifier] = resp;
              return resp;
            })
            .finally(onFinally);
        },
    
        deleteObject(bucketName: string, key: string): Promise<void> {
          const command = new DeleteObjectCommand({
            Bucket: bucketName,
            Key: key,
          });
          return this.client
            .send(command)
            .then(() => {
              const bucketRepository = useBucketStore();
              bucketRepository.fetchBucket(bucketName);
              if (this.objectMapping[bucketName] == undefined) {
                this.fetchS3Objects(bucketName);
              } else {
                this.objectMapping[bucketName] = this.objectMapping[
                  bucketName
                ].filter((obj) => obj.Key !== key);
              }
            })
            .catch((err) => {
              console.error(err);
            });
        },
        deleteObjectsWithPrefix(bucketName: string, prefix: string): Promise<void> {
          if (this.objectMapping[bucketName] == undefined) {
            return Promise.resolve();
          }
          const command = new DeleteObjectsCommand({
            Bucket: bucketName,
            Delete: {
              Objects: this.objectMapping[bucketName]
                .filter((obj) => obj.Key?.startsWith(prefix))
                .map((obj) => {
                  return { Key: obj.Key };
                }),
            },
          });
          return this.client.send(command).then(() => {
            const bucketRepository = useBucketStore();
            bucketRepository.fetchBucket(bucketName);
            if (this.objectMapping[bucketName] == undefined) {
              this.fetchS3Objects(bucketName);
            } else {
              this.objectMapping[bucketName] = this.objectMapping[
                bucketName
              ].filter((obj) => !obj.Key?.startsWith(prefix));
            }
          });
        },
        copyObject(
          srcBucket: string,
          srcObject: S3Object,
          destBucket: string,
          destKey: string,
        ): Promise<S3Object> {
          if (srcObject.Key == undefined) {
            return Promise.resolve({});
          }
          const command = new CopyObjectCommand({
            Bucket: destBucket,
            CopySource: encodeURI(`/${srcBucket}/${srcObject.Key}`),
            Key: destKey,
          });
          return this.client.send(command).then(() => {
            const newObj = {
              Key: destKey,
              Size: srcObject.Size,
              LastModified: dayjs().toDate(),
            };
            this._pushObject(destBucket, newObj);
            return newObj;
          });
        },
        async uploadObjectFile(
          bucketName: string,
          key: string,
          file: File,
          onProgress?: (progress: Progress) => void,
        ): Promise<S3Object> {
          const parallelUploads3 = new Upload({
            client: this.client,
            params: {
              Bucket: bucketName,
              Body: file,
              ContentType: file.type,
              Key: key,
            },
            queueSize: 4, // optional concurrency configuration
            leavePartsOnError: false, // optional manually handle dropped parts
          });
          if (onProgress != undefined) {
            parallelUploads3.on("httpUploadProgress", onProgress);
          }
          await parallelUploads3.done();
          const newObj = {
            Key: key,
            Size: file.size ?? 0,
            LastModified: dayjs().toDate(),
          };
          this._pushObject(bucketName, newObj);
          return newObj;
        },
        createFolder(bucketName: string, key: string): Promise<S3Object> {
          const command = new PutObjectCommand({
            Bucket: bucketName,
            Body: "",
            ContentType: "text/plain",
            Key: key,
          });
          return this.client.send(command).then(() => {
            const newObj = {
              Key: key,
              Size: 0,
              LastModified: dayjs().toDate(),
            };
            this._pushObject(bucketName, newObj);
            return newObj;
          });
        },
      },
    });