Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't upgrade to Strapi 5 #193

Open
squelix opened this issue Oct 18, 2024 · 4 comments
Open

Can't upgrade to Strapi 5 #193

squelix opened this issue Oct 18, 2024 · 4 comments

Comments

@squelix
Copy link

squelix commented Oct 18, 2024

When this plugin will be updated to support Strapi v5 ?

@Bodokh
Copy link

Bodokh commented Oct 22, 2024

I used O1 to generate an implementation for cloud storage using the S3 official plugin as an example
For me it works great, just add it as a custom provider as stated in the strapi docs

Here is the main plugin code in typescript (i pretty much copied also the build stuff from the S3 provider github code)

import {
  Bucket,
  File as GCSFile,
  GetSignedUrlConfig,
  Storage
} from '@google-cloud/storage';
import type { Credentials } from 'google-auth-library';
import { getOr } from 'lodash/fp';
import type { ReadStream } from 'node:fs';
import { isUrlFromBucket } from './utils';

export interface File {
  name: string;
  alternativeText?: string;
  caption?: string;
  width?: number;
  height?: number;
  formats?: Record<string, unknown>;
  hash: string;
  ext?: string;
  mime: string;
  size: number;
  sizeInBytes: number;
  url: string;
  previewUrl?: string;
  path?: string;
  provider?: string;
  provider_metadata?: Record<string, unknown>;
  stream?: ReadStream;
  buffer?: Buffer;
}

export interface GCSParams {
  ACL?: string; // e.g., 'publicRead'
  signedUrlExpires?: number; // in seconds
}

export interface DefaultOptions {
  credentials?: Credentials;
  projectId?: string;
  bucket: string; // making it required
  params?: GCSParams;
  [k: string]: any;
}

export type InitOptions = DefaultOptions & {
  baseUrl?: string;
  rootPath?: string;
  [k: string]: any;
};

const getConfig = ({ baseUrl, rootPath, ...options }: InitOptions) => {
  const { credentials, projectId, params, ...legacyOptions } = options;

  if (Object.keys(legacyOptions).length > 0) {
    process.emitWarning(
      "Google Cloud Storage configuration options passed at root level of the plugin's providerOptions are deprecated and will be removed in a future release. Please ensure all configurations are properly nested."
    );
  }

  const config = {
    credentials,
    projectId,
    ...legacyOptions,
    ...(credentials ? { credentials } : {}),
    params: {
      ACL: getOr('publicRead', ['params', 'ACL'], { params }),
      ...params,
    },
  };

  return config;
};

export default {
  init({ baseUrl, rootPath, ...options }: InitOptions) {
    console.log('options', options);

    const config = getConfig({ baseUrl, rootPath, ...options });
    const storage = new Storage({
      projectId: config.projectId,
      credentials: config.credentials,
    });

    console.log('config', config);

    const bucket: Bucket = storage.bucket(config.bucket);
    const filePrefix = rootPath ? `${rootPath.replace(/\/+$/, '')}/` : '';

    const getFileKey = (file: File) => {
      const path = file.path ? `${file.path}/` : '';
      return `${filePrefix}${path}${file.hash}${file.ext}`;
    };

    const upload = async (file: File, customParams: Partial<GCSParams> = {}) => {
      const fileKey = getFileKey(file);
      const gcsFile: GCSFile = bucket.file(fileKey);

      const stream = gcsFile.createWriteStream({
        resumable: false,
        metadata: {
          contentType: file.mime,
          cacheControl: 'public, max-age=31536000',
          ...customParams,
        },
        predefinedAcl: 'publicRead',
      });

      return new Promise<void>((resolve, reject) => {
        if (file.stream) {
          file.stream.pipe(stream)
            .on('error', reject)
            .on('finish', () => {
              file.url = baseUrl ? `${baseUrl}/${fileKey}` : gcsFile.publicUrl();
              resolve();
            });
        } else if (file.buffer) {
          stream.end(file.buffer, () => {
            file.url = baseUrl ? `${baseUrl}/${fileKey}` : gcsFile.publicUrl();
            resolve();
          });
        } else {
          reject(new Error('File must have either a stream or buffer'));
        }
      });
    };

    return {
      isPrivate() {
        return config.params.ACL === 'private';
      },

      async getSignedUrl(file: File, customParams: any): Promise<{ url: string }> {
        // Do not sign the url if it does not come from the same bucket.
        if (!isUrlFromBucket(file.url, config.bucket, baseUrl)) {
          return { url: file.url };
        }
        const fileKey = getFileKey(file);
        const gcsFile = bucket.file(fileKey);

        const options: GetSignedUrlConfig = {
          version: 'v4',
          action: 'read',
          expires: Date.now() + (getOr(15 * 60, ['params', 'signedUrlExpires'], config) * 1000),
          ...customParams,
        };

        const [url] = await gcsFile.getSignedUrl(options);
        return { url };
      },

      uploadStream(file: File, customParams = {}) {
        return upload(file, customParams);
      },

      upload(file: File, customParams = {}) {
        return upload(file, customParams);
      },

      async delete(file: File, customParams = {}): Promise<void> {
        const fileKey = getFileKey(file);
        const gcsFile = bucket.file(fileKey);
        await gcsFile.delete({ ignoreNotFound: true, ...customParams });
      },
    };
  },
};

util:

export const isUrlFromBucket = (url: string, bucket: string, baseUrl: string) => {
  return url.startsWith(`https://storage.googleapis.com/${bucket}/`);
};

then just update middlewares:

export default [
  'strapi::logger',
  'strapi::errors',
  // 'strapi::security',
  {
    name: 'strapi::security',
    config: {
      contentSecurityPolicy: {
        useDefaults: true,
        directives: {
          'connect-src': ["'self'", 'https:'],
          'img-src': [
            "'self'",
            'data:',
            'blob:',
            'yourbucket.storage.googleapis.com',
            'storage.googleapis.com',
          ],
          'media-src': [
            "'self'",
            'data:',
            'blob:',
            'yourbucket.storage.googleapis.com',
            'storage.googleapis.com',
          ],
          upgradeInsecureRequests: null,
        },
      },
    },
  },
  'strapi::cors',
  'strapi::poweredBy',
  'strapi::query',
  'strapi::body',
  'strapi::session',
  'strapi::favicon',
  'strapi::public',
];

Good luck.

@guissalustiano
Copy link

The provider API has not changed. This provider still works with strapi 5.

@suavelad
Copy link

when I try to install it , it get :

npm warn EBADENGINE Unsupported engine {
npm warn EBADENGINE package: '[email protected]',
npm warn EBADENGINE required: { node: '>=12.x.x <=16.x.x', npm: '>=6.0.0' },
npm warn EBADENGINE current: { node: 'v18.20.5', npm: '10.8.2' }
npm warn EBADENGINE }

@suavelad
Copy link

I configured the typescript plugin and middleware but it still stores in my local storage:
What is am I missing out

plugin.ts

export default ({ env }: { env: (key: string) => string }) => ({
'upload': {
provider: '@strapi-community/strapi-provider-upload-google-cloud-storage',
providerOptions: {
bucketName: env('GOOGLE_CLOUD_STORAGE_BUCKET_NAME'),
publicFiles: true, // Set to true if files are public
uniform: true, // Set to true if you're using uniform bucket-level access
baseUrl: 'https://storage.googleapis.com/xxxxx',
serviceAccount: JSON.parse(env('GOOGLE_CLOUD_STORAGE_CREDENTIALS')), // Parse the credentials from the environment variable
},
},
'users-permissions': {
config: {
jwtSecret: env('ADMIN_JWT_SECRET'),
},
},
});

middleware.ts

export default [
'strapi::logger',
'strapi::errors',
// 'strapi::security',
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': [
"'self'",
'data:',
'blob:',
'yourbucket.storage.googleapis.com',
'storage.googleapis.com',
],
'media-src': [
"'self'",
'data:',
'blob:',
'yourbucket.storage.googleapis.com',
'storage.googleapis.com',
],
upgradeInsecureRequests: null,
},
},
},
},
'strapi::cors',
'strapi::poweredBy',
'strapi::query',
'strapi::body',
'strapi::session',
'strapi::favicon',
'strapi::public',
];

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants