Giter VIP home page Giter VIP logo

adminjs-upload's Introduction

Upload Feature for AdminJS

This is an official AdminJS feature which uploads files to resources.

AdminJS

AdminJS is an automatic admin interface which can be plugged into your application. You, as a developer, provide database models (like posts, comments, stores, products or whatever else your application uses), and AdminJS generates UI which allows you (or other trusted users) to manage content.

Check out the example application here: https://adminjs-demo.herokuapp.com/admin/

Or visit AdminJS github page.

Usage

To see example usage see the example-app or visit the @adminjs/upload section under AdminJS project page.

License

AdminJS is copyrighted © 2023 rst.software. It is a free software, and may be redistributed under the terms specified in the LICENSE file.

About rst.software

We’re an open, friendly team that helps clients from all over the world to transform their businesses and create astonishing products.

  • We are available for hire.
  • If you want to work for us - check out the career page.

adminjs-upload's People

Contributors

1ay1 avatar adamfrydrychrst avatar ariansobczak-rst avatar dziraf avatar jonaszjestem avatar jwasiak avatar jxnata avatar sarcastic-verma avatar semantic-release-bot avatar thiago-silva-tech avatar wojtek-krysiak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

adminjs-upload's Issues

TypeError: propertyPath.replace is not a function

I tried to add the upload feature to my Adminjs but I am getting the error:

TypeError: propertyPath.replace is not a function

I am using Mongoose

Here is my code:

const ImageResource = {
    resource: FileModel,
    features: [
        uploadFeature({
            provider: { aws: AWScredentials },
            properties: {
                key: {
                    type: 'string',
                },
            },
            validation: { mimeTypes: ['image/*'] },
        }),
    ],
}

Company (2)

Please can someone help me with this?

Storing multiple files in one model. What i am doing wrong?

Hello! I'm trying to upload two files to one model, and I ran into a problem:
If you upload both files, only the last one is saved. If you upload one file, and then edit the entry and add another one, it will not be saved.
This is my code:

import { Schema, model, Document } from 'mongoose';
import { ResourceWithOptions } from 'adminjs';
import uploadFeature from '@adminjs/upload';

interface IMedia extends Document {
  path: string,
  mimeType: string,
  size: number,
  filename: string,
  bucket: string,
}

interface IBrand extends Document {
  title: string,
  media: {
    big: IMedia,
    small: IMedia
  },
}

const mediaSchema = new Schema<IMedia>({
  path: { type: String },
  mimeType: String,
  size: Number,
  filename: String,
  bucket: String,
});

const brandSchema = new Schema<IBrand>({
  title: { type: String, required: true },
  media: {
    big: mediaSchema,
    small: mediaSchema
  },
});


const Brand = model<IBrand>('BrandExample', brandSchema)

export const brandExample: ResourceWithOptions = {
  resource: Brand,
  options: {},
  features: [
    uploadFeature({
      provider: {
        local: {
          bucket: 'uploads'
        }
      },
      properties: {
        key: `media.big.path`,
        mimeType: `media.big.mimeType`,
        size: `media.big.size`,
        bucket: `media.big.bucket`,
        filename: `media.big.filename`,
        file: `media.big.file`,
        filePath: `media.big.filePath`,
        filesToDelete: `media.big.filesToDelete`,
      },
      validation: {
        mimeTypes: ['image/jpeg', 'image/png']
      },
      uploadPath: (record, filename) => (
        `${record.id()}/media.big/${filename}`
      ),
    }),
    uploadFeature({
      provider: {
        local: {
          bucket: 'uploads'
        }
      },
      properties: {
        key: `media.small.path`,
        mimeType: `media.small.mimeType`,
        size: `media.small.size`,
        bucket: `media.small.bucket`,
        filename: `media.small.filename`,
        file: `media.small.file`,
        filePath: `media.small.filePath`,
        filesToDelete: `media.small.filesToDelete`,
      },
      validation: {
        mimeTypes: ['image/jpeg', 'image/png']
      },
      uploadPath: (record, filename) => (
        `${record.id()}/media.small/${filename}`
      ),
    })
  ]
};

image

Look like a bug or did I do something wrong?

Cannot read properties of null (reading 'file')

Description

Error when try to update model with nested filed inside.

Error

TypeError: Cannot read properties of null (reading 'file')
    at convertNestedParam (/home/salvacnj/api/node_modules/adminjs/lib/utils/param-converter/convert-nested-param.js:23:26)
    at Object.prepareParams (/home/salvacnj/api/node_modules/adminjs/lib/utils/param-converter/prepare-params.js:56:64)
    at Object.handler (/home/salvacnj/api/node_modules/adminjs/lib/backend/actions/edit/edit-action.js:64:51)
    at ActionDecorator.invokeHandler (/home/salvacnj/api/node_modules/adminjs/lib/backend/decorators/action/action-decorator.js:117:26)
    at ActionDecorator.handler (/home/salvacnj/api/node_modules/adminjs/lib/backend/decorators/action/action-decorator.js:73:30)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at ApiController.recordAction (/home/salvacnj/api/node_modules/adminjs/lib/backend/controllers/api-controller.js:168:28)
    at handler (/home/salvacnj/api/node_modules/@adminjs/express/src/buildRouter.ts:46:22)

Possible solution

Edit line 35 of node_modules/adminjs/lib/utils/param-converter/prepare-params.js to

if (param === undefined ||param === null) continue;

Environment

adminjs: 5.7.0
@adminjs/upload: 2.0.2

Error: You cannot upload file for not persisted record. Save record first

My Setup

Schema -

export const UploadedFile = new mongoose.Schema({
  path: String,
  type: String,
  size: Number,
  folder: String,
  filename: String,
});

export const Pods = mongoose.model('Pods', {
  name: {
    type: String,
    required: true,
  },
  message: {
    type: String,
    required: true,
    defualt: '',
  },
  uploadedFile: UploadedFile,
});

config --

 {
      resource: Pods,
      options: {
        parent: 'Manage Groups',
        properties: {},
      },
      features: [
        uploadFeature({
     provider: { local: { bucket: 'uploads' } },
          properties: {
            key: 'uploadedFile.path',
            bucket: 'uploadedFile.folder',
            mimeType: 'uploadedFile.mime',
            size: 'uploadedFile.size',
            filename: 'uploadedFile.filename',
            file: 'uploadFile',
          },
        }),
      ],

Express -

app.use('/uploads', express.static('uploads'));

I get this error on trying to upload an image on a record. Looking at the code it seems that this is result of a record without an id. But my record has an id to confirm i created a new record without uploading an image and then tried to upload with the same result.

Everything else works fine its only the image upload thats not working. Would appreciate any help.
Thanks.

MongoDB support

It would be very nice to have MongoDB support, to be able to upload files directly to the database.

ContentType AWS

When uploading a file to AWS, the ContentType of the uploaded file is not set properly. For example, the MimeType of an uploaded image to the edit page of a record is image/png but on S3, the ContentType is binary/octet-stream after the image upload to AWS in complete.
I managed to set the right ContentType for the upload by adding ContentType: file.type to the upload params inside the aws-provider.

Did I miss setting an option for the uploadFeature in my project or is this a bug?

Error: You cannot upload file for not persisted record. Save record first.

@Amixengineer I am facing similar issue while uploading file to s3..
These are the code snippets for options, package.json and schema.

const teacherParams = {
resource: Teacher,
options: {
listProperties: ["name", "email", "phone", "status"],
},
features: [
uploadFeature({
provider: {
aws: {
region: env.region,
bucket: env.bucket,
secretAccessKey: env.secretAccessKey,
accessKeyId: env.accessKeyId,
expires: 0,
},
},
properties: {
key: "fileUrl",
mimeType: "mimeType",
},
validation: {
mimeTypes: ["image/png", "image/jpg", "image/jpeg"],
},
multiple: true,
}),
],
};

"dependencies": {
"@admin-bro/upload": "^1.3.1",
"admin-bro": "^4.0.1",
"admin-bro-expressjs": "^2.1.1",
"admin-bro-mongoose": "^0.5.2",

const teacherSchema = new Schema({
name: String,
email: String,
about: String,
countryCode: Object,
phone: String,
createdAt: Date,
password: String,
status: Number,
permissionLevel: Number,
updatedAt: Date,
// updatedBy: Schema.Types.ObjectId,
timeZone: String,
fileUrl: [
{
type: String,
},
],
mimeType: [
{
type: String,
},
],
imageURL: String,
});

Any idea how to fix this @wojtek-krysiak @Amixengineer

Originally posted by @Saurabhkmr98 in #14 (comment)

EXDEV: cross-device link not permitted, rename 'C:/.../Temp/upload_xxxx' -> 'D:/projectPath/filename.png'

Description

I use Sequelize as Orm and postgresql as db. I wanna to store images in json. When I'm trying to upload a file to a resource, it gives an error to the console
Error: EXDEV: cross-device link not permitted, rename 'C:/.../Temp/upload_xxxx' -> 'D:/projectPath/filename.png'

and doesnt save anything to images field in db. It saves random file called upload_${somenumbers} to Temp folder, although I specified folder by { bucket: path.join(__dirname, '../public') }

adminRouter.js file

...
const adminJs = new AdminJS({ 
  databases: [db],
  rootPath: '/admin',
  resources: [
    {
      resource: User,
      options: { listProperties: ['id', 'email', 'ava', 'avaTxt', 'images'] },
      features: [
        uploadFeature({
          provider: { local: { bucket: path.join(__dirname, '../public') } },
          properties: {
            file: 'images.file',
            filePath: 'images.path',
            filename: 'images.filename',
            filesToDelete: 'images.toDelete',
            key: 'images.key',
            mimeType: 'images.mimeType',
            bucket: 'images.bucket',
          },
        }),
      ],
    },
  ],
});
...

server.js

...
server.use(express.static('public'));
const adminRouter = require('./Routes/adminRouter');
server.use('/admin', adminRouter);
...

Upload to AWS has the wrong ContentType

Hey there!

Describe the bug
I am using the upload feature to upload images directly to S3. When uploading a file to AWS, the ContentType of an uploaded image to the edit page of a record is image/png but on S3, the ContentType is binary/octet-stream after the image upload to S3 is complete.

Did I miss setting an option for the uploadFeature in my project or is this a bug?

I managed to set the right ContentType for the upload by adding ContentType: file.type to the upload params inside the aws-provider.

Installed libraries and their versions

To Reproduce
Steps to reproduce the behavior:

  1. Select AWS as provider for the upload feature
  2. Open the edit page of a record where the upload feature is enabled
  3. Upload an image
  4. Go to the S3 Bucket where the image was uploaded, select the uploaded image
  5. Open the Metadata information, check the ContentType

Expected behavior
The ContentType of the file in S3 after the upload should be the same as the ContentType of the uploaded file. For example, if I upload a png image the ContentType of the file in S3 should be image/png.

AdminBroOptions with schema

resource: User,
                options: {
                    properties: {
                        fileUrl: {
                            isVisible: true,
                        },
                        mimeType: {
                            isVisible: true,
                        },
                    },
                },
                features: [uploadFeature({
                    provider: {
                        aws: {
                            region: `${process.env.AWS_REGION}`,
                            bucket: `${process.env.S3_IMAGE_BUCKET_NAME}`,
                            accessKeyId: `${process.env.AWS_USER_KEY}`,
                            secretAccessKey: `${process.env.AWS_USER_SECRET}`,
                            expires: 0,
                        },
                    },
                    properties: {
                        key: 'fileUrl',
                        mimeType: 'mimeType',
                    },
                    uploadPath: (record: BaseRecord, filename: string) => {
                        return `${record.get('id')}/${filename}`;
                    },
                })]

Additional context

I could fix the issue by changing the upload function in upload/src/features/upload-file/provider/aws-provider.ts from

public async upload(file: UploadedFile, key: string): Promise<S3.ManagedUpload.SendData> {
    const uploadOptions = { partSize: 5 * 1024 * 1024, queueSize: 10 }
    const tmpFile = fs.createReadStream(file.path)
    const params: S3.PutObjectRequest = {
      Bucket: this.bucket,
      Key: key,
      Body: tmpFile,
    }
    if (!this.expires) {
      params.ACL = 'public-read'
    }
    return this.s3.upload(params, uploadOptions).promise()
  }

to

public async upload(file: UploadedFile, key: string): Promise<S3.ManagedUpload.SendData> {
    const uploadOptions = { partSize: 5 * 1024 * 1024, queueSize: 10 }
    const tmpFile = fs.createReadStream(file.path)
    const params: S3.PutObjectRequest = {
      Bucket: this.bucket,
      Key: key,
      ContentType: file.type,
      Body: tmpFile,
    }
    if (!this.expires) {
      params.ACL = 'public-read'
    }
    return this.s3.upload(params, uploadOptions).promise()
  }

So I am not sure if the ContentType param is missing here of if I made a mistake setting up the upload feature.

Change behaviour defined in build-path.ts

Is possible to change the behaviour defined by build-path.ts file that is overriding the key.

We are using a custom provider, and this code is messing with the value stored on the field of the database that is defined as key. How we can customize this behaviour?

Thank you

Element type is invalid; BasePropertyComponent error

Did you find any solutions regarding those two issues:
##25
##4
Javascript Error

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports. Check the render method of BasePropertyComponent.
See development console for more details...

Upload through child Resource

Is it possible to use a dedicated resource for uploading files and reference that resource in a parent resource ?
The only way a figure out to upload multiple files in the same resource is using jsonb columns in the resource's table, but what if I want to create a one-to-one relationship to a dedicated table for files ?

file.path is undefined

I'm trying to use the local upload option but it doesn't work - I'm getting the error The "oldPath" argument must be of type string or an instance of Buffer or URL. Received undefined.

After searches, I found that the file I'm receiving in the upload method has no path property. Why it can happen and how can I fix it?

Provider:

class LocalProvider extends BaseProvider {
  constructor (options: LocalUploadOptions) {
    super(options.bucket)
    if (!fs.existsSync(options.bucket)) {
      throw new Error('No folder')
    }
  }

  public async upload (file: UploadedFile, key: string): Promise<any> {
    const filePath = this.path(key)

    await fs.promises.mkdir(path.dirname(filePath), { recursive: true })
    await fs.promises.rename(file.path, filePath)
  }

  public async delete (key: string, bucket: string): Promise<any> {
    await fs.promises.unlink(this.path(key, bucket))
  }

  // eslint-disable-next-line class-methods-use-this
  public path (key: string, bucket?: string): string {
    return `${path.join(this.bucket, key)}`
  }
}

Usage:

      resource: { model: dmmf.modelMap.HelpRequest, client: prisma },
      options: {
        properties: {
          image: { isVisible: false }
        }
      },
      features: [uploadFeature({
        provider: new LocalProvider({ bucket: 'public' }),
        properties: {
          key: 'image', // to this db field feature will safe S3 key,
          mimeType: 'mimeType' // this property is important because allows to have previews,
        },
        validation: {
          mimeTypes: ['image/png', 'image/jpg', 'image/jpeg']
        }

Error: You cannot upload file for not persisted record. Save record first

Hello dear @wojtek-krysiak,

it seems that upload feature for mongoose does not work as it is expected. i get these two errors.

on the response tab in network

Error: new action can be invoked only via `post` http method
    at Object.handler (/Users/amix/Desktop/BndNail-NodeJs-Project/node_modules/admin-bro/lib/backend/actions/new/new-action.js:76:11)
    at ActionDecorator.invokeHandler (/Users/amix/Desktop/BndNail-NodeJs-Project/node_modules/admin-bro/lib/backend/decorators/action/action-decorator.js:117:26)
    at ActionDecorator.handler (/Users/amix/Desktop/BndNail-NodeJs-Project/node_modules/admin-bro/lib/backend/decorators/action/action-decorator.js:73:30)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async handler (/Users/amix/Desktop/BndNail-NodeJs-Project/node_modules/admin-bro-expressjs/plugin.js:59:22)

on terminal

Error: You cannot upload file for not persisted record. Save record first
    at Object.exports.buildRemotePath (/Users/amix/Desktop/BndNail-NodeJs-Projec\node_modules\@admin-bro\upload\build\features\upload-file\utils\build-remote-path.js:22:15)
    at Promise.all.uploadedFiles.map (/Users/amix/Desktop/BndNail-NodeJs-Projec\node_modules\@admin-bro\upload\build\features\upload-file\factories\update-record-factory.js:36:53)
    at Array.map (<anonymous>)
    at updateRecord (/Users/amix/Desktop/BndNail-NodeJs-Projec\node_modules\@admin-bro\upload\build\features\upload-file\factories\update-record-factory.js:35:62)
    at prevPromise.then.modifiedResponse (/Users/amix/Desktop/BndNail-NodeJs-Projec\node_modules\admin-bro\lib\backend\decorators\action\action-decorator.js:147:99)
    at process._tickCallback (internal/process/next_tick.js:68:7)

after searching through issues I came across to one comment that had the same problem as mine

SoftwareBrothers/adminjs#95 (comment)

Local Provider attempting to create upload in root partition

Describe the bug
The local provider in the @adminbro/upload module is attempting to create a directory in the root partition of unix instance instead of using the designated bucket directory built in the root level of the project.

Also, the upload components are not referring to the path of the file on the serving directory but uses the physical address of the directory the file uploaded to.

Installed libraries and their versions

  • @admin-bro/express ^3.1.0
  • @admin-bro/mongoose ^1.1.0
  • @admin-bro/upload ^1.3.1
  • express ~4.16.1

To Reproduce
This is the adminbro setup with models being used for the file upload

routes/admin.js

...
const adminBro = new AdminBro({
  databases: [mongoose],
  rootPath: '/admin',
  branding: {
      companyName: 'My resume site',
      logo: false
  },
  resources: [{
    resource: Experience,
    options: {
        properties: {
            uploadedFile: { isVisible: false }
        }
    },
    features: [uploadFeature({
        provider: { local: { bucket: path.join(__dirname, '../uploads') } },
        properties: {
            key: 'uploadedFile.path',
            bucket: 'uploadedFile.folder',
            mimeType: 'uploadedFile.type',
            size: 'uploadedFile.size',
            filename: 'uploadedFile.filename',
            file: 'uploadFile'
        },
        uploadPath: (record, filename) => (
            `${record.id()}/${filename}`
        )
    }),]
  }]
})
...

models/experience.js

const mongoose = require('mongoose');
const { Schema } = mongoose;

const UploadedFile = new Schema({
    path: String,
    type: String,
    size: Number,
    folder: String,
    filename: String
});

const schema = new Schema({
    role: String,
    organization: String,
    lengthOfService: {
        startDate: Date,
        endDate: Date
    },
    uploadedFile: UploadedFile,
    description: String

});

module.exports = mongoose.model('Experience', schema);

When the instance is started and I attempt to create an entry with the file upload, It will be successful however, the image fails to render to the admin page.

image

After saving...

image

If you look at uploadedFile.folder and uploadedFile.filepath they both point to a physical address. Which makes sense, because the bucket is pointing to a physical address not the serving directory address and whatever the prop is for the component is passing that down.

But that shouldn't even be the case to refer to the absolute address of the upload directory, it should be referring to the relative filepath in the project which is the project directory

node_modules/@admin-bro/upload/src/features/upload-file/providers/local-provider.ts

...
/**
 * Options required by the LocalAdapter
 *
 * @memberof module:@admin-bro/upload
 */
export type LocalUploadOptions = {
  /**
   * Path where files will be stored. For example: `path.join(__dirname, '../public')`
   */
  bucket: string;
}

export class LocalProvider extends BaseProvider {
  constructor(options: LocalUploadOptions) {
    super(options.bucket)
    if (!existsSync(options.bucket)) {
      throw new Error(ERROR_MESSAGES.NO_DIRECTORY(options.bucket))
    }
  }

  public async upload(file: UploadedFile, key: string): Promise<any> {
    const filePath = this.path(key)

    await fs.promises.mkdir(path.dirname(filePath), { recursive: true })
    await fs.promises.rename(file.path, filePath)
  }

  public async delete(key: string, bucket: string): Promise<any> {
    await fs.promises.unlink(this.path(key, bucket))
  }

  // eslint-disable-next-line class-methods-use-this
  public path(key: string, bucket?: string): string {
    return `${path.join(bucket || this.bucket, key)}`
  }
}

Expected behavior
What's expected is that, even if the absolute path is provided, that the frontend should refer to the key of the file, not the folder path. Or atleast the local provider uses the project root not the root partition of the local instances to place uploaded folers

Desktop (please complete the following information if relevant):

  • OS: Linux Mint 20.1 Cinnamon / 5.4.0-81-generic
  • Browser Version 1.29.77 Chromium: 93.0.4577.63 (Official Build) (64-bit)

Additional context
Is there a way to change the src of the image on the frontend so we can pass along the file path to the serving directory?

Element type is invalid: BasePropertyComponent

Receiving:

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports. Check the render method of `BasePropertyComponent`.

My config:

    {
      resource: Test,
      options: {
        listProperties: ["fileUrl", "mimeType"],
      },
      features: [
        uploadFeature({
          provider: {
            aws: {
              bucket: "bucket",
              secretAccessKey: "key",
              accessKeyId: "keyId",
              region: "region",
            },
          },
          properties: {
            key: "fileUrl", // to this db field feature will safe S3 key
            mimeType: "image/jpeg", // this property is important because allows to have previews
          },
        }),
      ],
    },
const Test = new mongoose.Schema(
  {
    fileUrl: { type: String, required: false },
    mimeType: { type: String, required: false },
  },
  { versionKey: false }
);

I'm not using any custom React element.

On Editing the multiple images, It gives the following error.

TypeError: uploadedFiles.map is not a function
    at updateRecord (/home/ayush/http/node/aoa/aoa_admin/node_modules/@admin-bro/upload/build/features/upload-file/factories/update-record-factory.js:37:62)
    at process._tickCallback (internal/process/next_tick.js:68:7)

ERROR: Trying to bundle file but it doesn't exist

I have an api with adminjs and upload feature with aws s3 that is working correctly when i run locally. But when deployed on vercel i get the following error:

t.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Module.load (internal/modules/cjs/loader.js:950:32)
Legacy server listening...
2022-12-08T12:33:06.859Z	undefined	ERROR	Unhandled Promise Rejection 	{"errorType":"Runtime.UnhandledPromiseRejection","errorMessage":"ConfigurationError: \n    Trying to bundle file '/var/task/node_modules/@adminjs/upload/src/features/upload-file/components/edit' but it doesn't exist\n    More information can be found at: https://docs.adminjs.co/AdminJS.html\n    ","reason":{"errorType":"ConfigurationError","errorMessage":"\n    Trying to bundle file '/var/task/node_modules/@adminjs/upload/src/features/upload-file/components/edit' but it doesn't exist\n    More information can be found at: https://docs.adminjs.co/AdminJS.html\n    ","name":"ConfigurationError","stack":["ConfigurationError: ","    Trying to bundle file '/var/task/node_modules/@adminjs/upload/src/features/upload-file/components/edit' but it doesn't exist","    More information can be found at: https://docs.adminjs.co/AdminJS.html","    ","    at Function.resolveFilePath (/var/task/node_modules/adminjs/lib/backend/utils/component-loader.js:108:11)","    at ComponentLoader.__unsafe_addWithoutChecks (/var/task/node_modules/adminjs/lib/backend/utils/component-loader.js:52:46)","    at Function.bundle (/var/task/node_modules/adminjs/lib/adminjs.js:391:41)","    at uploadFileFeature (/var/task/node_modules/@adminjs/upload/build/features/upload-file/upload-file.feature.js:75:45)","    at AdminOptions.buildResources (/var/task/src/utils/buildAdminOptions.js:235:21)","    at module.exports (/var/task/src/main/config/setup.js:18:39)","    at Object.<anonymous> (/var/task/src/main/config/app.js:6:1)","    at Module._compile (internal/modules/cjs/loader.js:1085:14)","    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)","    at Module.load (internal/modules/cjs/loader.js:950:32)"]},"promise":{},"stack":["Runtime.UnhandledPromiseRejection: ConfigurationError: ","    Trying to bundle file '/var/task/node_modules/@adminjs/upload/src/features/upload-file/components/edit' but it doesn't exist","    More information can be found at: https://docs.adminjs.co/AdminJS.html","    ","    at process.<anonymous> (/var/runtime/index.js:35:15)","    at process.emit (events.js:412:35)","    at process.emit (domain.js:475:12)","    at processPromiseRejections (internal/process/promises.js:245:33)","    at processTicksAndRejections (internal/process/task_queues.js:96:32)"]}
2022-12-08T12:33:06.863Z	undefined	ERROR	Unhandled rejection: ConfigurationError: 
    Trying to bundle file '/var/task/node_modules/@adminjs/upload/src/features/upload-file/components/edit' but it doesn't exist
    More information can be found at: https://docs.adminjs.co/AdminJS.html
    at Function.resolveFilePath (/var/task/node_modules/adminjs/lib/backend/utils/component-loader.js:108:11)
    at ComponentLoader.__unsafe_addWithoutChecks (/var/task/node_modules/adminjs/lib/backend/utils/component-loader.js:52:46)
    at Function.bundle (/var/task/node_modules/adminjs/lib/adminjs.js:391:41)
    at uploadFileFeature (/var/task/node_modules/@adminjs/upload/build/features/upload-file/upload-file.feature.js:75:45)
    at AdminOptions.buildResources (/var/task/src/utils/buildAdminOptions.js:235:21)
    at module.exports (/var/task/src/main/config/setup.js:18:39)
    at Object.<anonymous> (/var/task/src/main/config/app.js:6:1)
    at Module._compile (internal/modules/cjs/loader.js:1085:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Module.load (internal/modules/cjs/loader.js:950:32)
RequestId: c78d99be-b47c-4848-942d-37a81c45fe78 Error: Runtime exited with error: exit status 1
Runtime.ExitError

Installed libraries and their versions

"@adminjs/bundler": "^2.0.0",
"@adminjs/express": "^5.0.1",
"@adminjs/mongoose": "^3.0.0",
"@adminjs/upload": "^3.0.1",
"adminjs": "^6.6.5",

Setup

    require('dotenv').config();
    app.use(express.static(path.join(__dirname, '../../../public/')));
    app.use('/uploads', express.static('uploads'));
    const adminOptions = {
        locale: adminOptions().buildLocales(),
        resources: adminOptions().buildResources(),
        branding: adminOptions().buildBranding(),
        assets: {
            styles: ['/styles.css'],
        },
    };
    AdminJS.registerAdapter({ Database, Resource });
    const admin = new AdminJS(adminOptions);
    const adminRouter = AdminJSExpress.buildAuthenticatedRouter(admin, {
        ...  
    });
    app.use(admin.options.rootPath, adminRouter);

my entry file generated automatically

import Component0 from '../node_modules/@adminjs/upload/src/features/upload-file/components/edit.tsx'
AdminJS.UserComponents.Component0 = Component0
import Component1 from '../node_modules/@adminjs/upload/src/features/upload-file/components/list.tsx'
AdminJS.UserComponents.Component1 = Component1
import Component2 from '../node_modules/@adminjs/upload/src/features/upload-file/components/show.tsx'
AdminJS.UserComponents.Component2 = Component2
import Component3 from '../node_modules/@adminjs/upload/src/features/upload-file/components/edit.tsx'
AdminJS.UserComponents.Component3 = Component3
import Component4 from '../node_modules/@adminjs/upload/src/features/upload-file/components/list.tsx'
AdminJS.UserComponents.Component4 = Component4
import Component5 from '../node_modules/@adminjs/upload/src/features/upload-file/components/show.tsx'
AdminJS.UserComponents.Component5 = Component5
import Component6 from '../node_modules/@adminjs/upload/src/features/upload-file/components/edit.tsx'
AdminJS.UserComponents.Component6 = Component6
import Component7 from '../node_modules/@adminjs/upload/src/features/upload-file/components/list.tsx'
AdminJS.UserComponents.Component7 = Component7
import Component8 from '../node_modules/@adminjs/upload/src/features/upload-file/components/show.tsx'
AdminJS.UserComponents.Component8 = Component8
import Component9 from '../node_modules/@adminjs/upload/src/features/upload-file/components/edit.tsx'
AdminJS.UserComponents.Component9 = Component9
import Component10 from '../node_modules/@adminjs/upload/src/features/upload-file/components/list.tsx'
AdminJS.UserComponents.Component10 = Component10
import Component11 from '../node_modules/@adminjs/upload/src/features/upload-file/components/show.tsx'
AdminJS.UserComponents.Component11 = Component11
import Component12 from '../node_modules/@adminjs/upload/src/features/upload-file/components/edit.tsx'
AdminJS.UserComponents.Component12 = Component12
import Component13 from '../node_modules/@adminjs/upload/src/features/upload-file/components/list.tsx'
AdminJS.UserComponents.Component13 = Component13
import Component14 from '../node_modules/@adminjs/upload/src/features/upload-file/components/show.tsx'
AdminJS.UserComponents.Component14 = Component14
import Component15 from '../node_modules/@adminjs/upload/src/features/upload-file/components/edit.tsx'
AdminJS.UserComponents.Component15 = Component15
import Component16 from '../node_modules/@adminjs/upload/src/features/upload-file/components/list.tsx'
AdminJS.UserComponents.Component16 = Component16
import Component17 from '../node_modules/@adminjs/upload/src/features/upload-file/components/show.tsx'
AdminJS.UserComponents.Component17 = Component17
import Component18 from '../node_modules/@adminjs/upload/src/features/upload-file/components/edit.tsx'
AdminJS.UserComponents.Component18 = Component18
import Component19 from '../node_modules/@adminjs/upload/src/features/upload-file/components/list.tsx'
AdminJS.UserComponents.Component19 = Component19
import Component20 from '../node_modules/@adminjs/upload/src/features/upload-file/components/show.tsx'
AdminJS.UserComponents.Component20 = Component20

Obs: I already tried to use @adminjs/bundler with ADMIN_JS_SKIP_BUNDLE="true", but i'm not sure if i'm doing it correctly.

And my project an only .js app.

Bug Report: In aws, expires:0 option doesn't work!

constructor(options) {
        super(options.bucket);
        console.log(options)
        let AWS_S3;
        try {
            // eslint-disable-next-line
            const AWS = require('aws-sdk');
            AWS_S3 = AWS.S3;
        }
        catch (error) {
            throw new Error(constants_1.ERROR_MESSAGES.NO_AWS_SDK);
        }
        this.expires = options.expires || constants_1.DAY_IN_MINUTES;
        this.s3 = new AWS_S3(options);
    }
    async upload(file, key) {
        const uploadOptions = { partSize: 5 * 1024 * 1024, queueSize: 10 };
        const tmpFile = fs_1.default.createReadStream(file.path);
        const params = {
            Bucket: this.bucket,
            Key: key,
            Body: tmpFile,
        };
        if (!this.expires) {
            params.ACL = 'public-read';
        }
        return this.s3.upload(params, uploadOptions).promise();
    }

these are current build code in adminbro/upload.
As you can see, this.expires = options.expires || constants_1.DAY_IN_MINUTES;
even though we give parameter as 0, because of '||', this.expires never becomes 0...
which means params.ACL never gets to 'public-read'...
I hope I'm wrong... but i printed the values and I think I'm right.
Looks like this issue need some fix.
If I have mistaken something, I will be happy to learn about it.

applying two upload functionalities for a single model at the same time

Hello dear brothers,

it seems that when we apply two upload functionalities for one resource at the same time they mix up together and one of them tend not to show up.

company.blog.js

const AdminBro = require('admin-bro');
const { Blog } = require('../../models/Blog');
const { sort, timestamps } = require('../sort');
const uploadFeature = require('@admin-bro/upload');
const path = require('path');
const fs = require('fs');
/** @type {AdminBro.ResourceOptions} */
const options = {
    name: 'Blog (customize field)',
    sort,
    properties: {
        ...timestamps,
        slug: {
            position: 0,
        },
        description: {
            isVisible: {list: false, edit: true, filter: false, show: true},
            position: 1,
            components: {
                edit: AdminBro.bundle('../components/custom-fonts.edit.tsx')
            }
        },
        tags: {
            isVisible: {list: false, edit: true, filter: true, show: true},
            position: 8
        },
        keywords: {
            isVisible: {list: false, edit: true, filter: true, show: true},
            position: 9
        },
        draftMode: {
            isVisible: {list: true, edit: true, filter: true, show: true},
        },
        _id: {
            isVisible: { list: false, edit: false, filter: false, show: false},
        },
    },
};


module.exports = {
    options,
    resource: Blog,
    features: [
        uploadFeature({
            provider: { local: { bucket: path.join(__dirname, '../../uploads') } },
            properties: {
                key: 'featuredPhoto.path',
                bucket: 'featuredPhoto.folder',
                mimeType: 'featuredPhoto.type',
                size: 'featuredPhoto.size',
                filename: 'featuredPhoto.filename',
                file: 'featuredPhoto',
            },
        }),
        uploadFeature({
            provider: { local: { bucket: path.join(__dirname, '../../uploads') } },
            properties: {
                key: 'thumbnailPhoto.path',
                bucket: 'thumbnailPhoto.folder',
                mimeType: 'thumbnailPhoto.type',
                size: 'thumbnailPhoto.size',
                filename: 'thumbnailPhoto.filename',
                file: 'thumbnailPhoto',
            },
        }),
    ],
};

Models - Blog.js

const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const slug = require('slugs');

const blogSchema = new mongoose.Schema({
	title: {
		type: String,
		trim: true,
		required: 'لطفا یک عنوان برای مقاله وارد کنید.'
	},
	slug: {
		type: String,
		trim: true,
		required: 'لطفا لینک مقاله را وارد کنید.'
	},
	description: {
		type: String,
		required: 'توضیحات مربوط به مقاله را وارد کنید.'
	},
	category: [{
		type: mongoose.Schema.Types.ObjectId,
		ref: 'Category',
	}],
	subCategory: [{
		type: mongoose.Schema.Types.ObjectId,
		ref: 'SubCategory',
	}],
	tags: {
		type: [String]
	},
	authors: { type: mongoose.Schema.ObjectId, ref:'Admin', required: 'نام نوینسده را انتخاب کنید' },
	keywords: {
		type: [String]
	},
	draftMode: {
		type: Boolean,
		default: false
	}
	
}, { timestamps: true } );



blogSchema.virtual('admins', {
	ref: 'Admin',
	localField: 'authors',
	foreignField: '_id',
});

const FeaturedPhoto = new mongoose.Schema({
	path: String,
	type: String,
	size: Number,
	folder: String,
	filename: String,
  });


const ThumbnailPhoto = new mongoose.Schema({
	path: String,
	type: String,
	size: Number,
	folder: String,
	filename: String,
});


const Blog = mongoose.model('Blog', { blogSchema, featuredPhoto: FeaturedPhoto, thumbnailPhoto: ThumbnailPhoto, }, );
module.exports = { blogSchema, Blog, ThumbnailPhoto, FeaturedPhoto, };

thanks for the support and maintanance.

Error: Component "UploadEditComponent" has not been bundled, ensure it was added to your ComponentLoader instance (the one included in AdminJS options).

Hello i am trying to use the @adminjs/upload feature but I am getting this error.

image

Main Code

import AdminJSFastify from '@adminjs/fastify'
import FastifySession from '@fastify/session'
import Connect from 'connect-pg-simple'
import AdminJS, { ResourceOptions } from 'adminjs'
import { Database, Resource, getModelByName } from '@adminjs/prisma'
import prisma from '../prisma.js'
import fp from 'fastify-plugin'
import uploadFileFeature from '@adminjs/upload'
import { ComponentLoader } from 'adminjs'

const componentLoader = new ComponentLoader();

const localProvider = {
    bucket: 'public/files',
    opts: {
        baseUrl: '/files',
    },
};

const profile: { resource: any, options: ResourceOptions, features: any } = {
    resource: { model: getModelByName("Profile"), client: prisma },
    options: {
        properties: {
            logo: {
                type: "string"
            }
        }
    },
    features: [uploadFileFeature({
        componentLoader,
        provider: { local: localProvider },
        properties: {
            key: 's3Key',
            bucket: "bucket",
            mimeType: "mime",
        },
        validation: { mimeTypes: ['image/png'] }
    })],
}


AdminJS.registerAdapter({ Database, Resource })
const ConnectSession = Connect(FastifySession as any)
const sessionStore = new ConnectSession({
    conObject: {
        connectionString: process.env.DATABASE_URL,
        ssl: process.env.NODE_ENV === 'production',
    },
    tableName: 'session',
    createTableIfMissing: true,
})


const DEFAULT_ADMIN = {
    email: '[email protected]',
    password: 'password',
}

const authenticate = async (email: string, password: string) => {
    if (email === DEFAULT_ADMIN.email && password === DEFAULT_ADMIN.password) {
        return Promise.resolve(DEFAULT_ADMIN)
    }
    return null
}

export default fp(async (fastify) => {
    const admin = new AdminJS({
        componentLoader,
        resources: [
            profile,
            {
                resource: { model: getModelByName("Project"), client: prisma }
            }
        ],
        rootPath: '/admin'
    })

    const cookieSecret = ''

    await AdminJSFastify.buildAuthenticatedRouter(
        admin,
        {
            authenticate,
            cookiePassword: cookieSecret,
            cookieName: 'adminjs',
        },
        fastify,
        {
            store: sessionStore as any,
            saveUninitialized: true,
            secret: cookieSecret,
            cookie: {
                httpOnly: process.env.NODE_ENV === 'production',
                secure: process.env.NODE_ENV === 'production',
            },
        }
    )
})

Prisma schema

model Profile {
  id          Int      @id @default(autoincrement())
  address     String   @unique
  username    String?
  displayName String?
  logo        String?
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

package.json

{
  "type": "module",
  "name": "idk",
  "description": "This project was bootstrapped with Fastify-CLI.",
  "version": "1.0.0",
  "main": "app.ts",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "test": "npm run build:ts && tsc -p test/tsconfig.json && FASTIFY_AUTOLOAD_TYPESCRIPT=1 node --test --experimental-test-coverage --loader ts-node/esm test/**/*.ts",
    "start": "npm run build:ts && fastify start -l info dist/app.js",
    "build:ts": "tsc",
    "watch:ts": "tsc -w",
    "dev": "npm run build:ts && concurrently -k -p \"[{name}]\" -n \"TypeScript,App\" -c \"yellow.bold,cyan.bold\" \"npm:watch:ts\" \"npm:dev:start\"",
    "dev:start": "fastify start --ignore-watch=.ts$ -w -l info -P dist/app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@adminjs/fastify": "^4.1.2",
    "@adminjs/prisma": "^5.0.3",
    "@adminjs/upload": "^4.0.2",
    "@fastify/autoload": "^5.0.0",
    "@fastify/multipart": "^8.1.0",
    "@fastify/sensible": "^5.0.0",
    "@fastify/session": "^10.7.0",
    "@fastify/static": "^7.0.1",
    "@prisma/client": "^5.11.0",
    "adminjs": "^7.7.2",
    "connect-pg-simple": "^9.0.1",
    "fastify": "^4.26.1",
    "fastify-cli": "^6.1.1",
    "fastify-plugin": "^4.0.0"
  },
  "devDependencies": {
    "@types/connect-pg-simple": "^7.0.3",
    "@types/node": "^20.4.4",
    "c8": "^9.0.0",
    "concurrently": "^8.2.2",
    "fastify-tsconfig": "^2.0.0",
    "prisma": "^5.11.0",
    "ts-node": "^10.4.0",
    "typescript": "^5.2.2"
  }
}

Error: EACCES: permission denied, mkdir '/public'

I am using admin bro with an express server, i just installed the package

yarn add @admin-bro/upload

I've created the public folder and assign it as static folder for the express framework, as follow

app.use(express.static('public'));

Each time i am using the uploadFileFeature it is giving me this error Error: EACCES: permission denied, mkdir '/public'

I am using @admin-bro/upload": "^1.3.1 and testing on my localhost environment on Linux Ubunto

Any help please ?

upload multiple file

Hi,
When I set multiple to true, in the admin panel I can get multiple files,
I even can log the path of the files in the custom provider, it prints all the selected file paths,
But the data is not stored in the database,

    "@adminjs/express": "^4.0.3",
    "@adminjs/mongoose": "^2.0.2",
    "@adminjs/upload": "^2.0.2",
    "adminjs": "^5.7.3",
      features: [
        uploadFeature({
          multiple: true,
          provider,
          properties: {
            key: "image.path",
            bucket: "image.folder",
            mimeType: "image.type",
            size: "image.size",
            filename: "image.filename",
            //file: "uploadFile",
          },
          uploadPath: (record, filename) =>
            `assets/products/${record.id()}/${filename}`,
          validation: { mimeTypes: ["image/png", "image/jpeg", "image/jpg"] },
        }),
      ],

and my custom provider

import { BaseProvider } from "@adminjs/upload";
//import { ActionContext, UploadedFile } from "adminjs";
import { promises, existsSync } from "fs";
import { resolve, dirname } from "path";

export class UploadProvider extends BaseProvider {
  assetPath;
  constructor(bucket, assetPath) {
    super(bucket);

    this.assetPath = assetPath;
  }

  async upload(file, key, context) {
    const fullPath = resolve(this.assetPath, key);
    const dirPath = dirname(fullPath);

    if (!existsSync(dirPath)) {
      await promises.mkdir(dirPath, { recursive: true });
    }

    await promises.copyFile(file.path, fullPath);
    await promises.unlink(file.path);
    return key;
  }

  async delete(key, bucket, context) {
    const filePath = resolve(this.assetPath, key);

    if (existsSync(filePath)) {
      await promises.unlink(filePath);
      const dirPath = dirname(filePath);
      const otherFiles = await promises.readdir(dirPath);
      if (otherFiles && otherFiles.length == 0) {
        await promises.rmdir(dirPath);
      }
    }
  }

  path(key, bucket, context) {
    return "/" + bucket + "/" + key;
  }
}

Thanks for your attention.

EXDEV: cross-device link not permitted, rename '/tmp/upload_02b9eaa79cab07b75b41a5a6ffbc98cc' -> '//{new_file_path}'

Hi, learn your sysytem - it`s great

Found this issue when working with local provider
I found the same problem on https://stackoverflow.com/questions/8579055/how-do-i-move-files-in-node-js

I found why it`s not work - the reason is this part of code in (and because my LInux distro use for directory /tmp a tmpfs filesystem)
/src/features/upload-file/providers/local-provider.ts

  public async upload(file: UploadedFile, key: string): Promise<any> {
    const filePath = this.path(key)
    await fs.promises.mkdir(path.dirname(filePath), { recursive: true })
    await fs.promises.rename(file.path, filePath)
  }

As I understand AdminBro firstly upload file to /tmp directory and after move file to project dir
But in code use function fs.rename and it`s a problem because rename use only when you move file in the same mounting point of filesystem

see https://man7.org/linux/man-pages/man2/rename.2.html

The possible solution is to use function move of module fs-extra instead fs

  import * as fs from 'fs-extra'    

  public async upload(file: UploadedFile, key: string): Promise<any> {
    const filePath = this.path(key)
    await fs.move(file.path, filePatch, {overwrite: true})
  }

and replace other fs function in file by their clone in fs-extra

How to solve this error "You have to define `key` property in options at uploadFileFeature"?

{resource: IFile,
options:
{
navigation: HomePage,
properties: {
key: {
type: 'string',
},
// s3Key: {
// type: 'string',
// },
bucket: {
type: 'string',
},
mime: {
type: 'string',
},
comment: {
type: 'textarea',
isSortable: false,
},
},
},
features: [
uploadFeature({
componentLoader,
provider: { aws: S3_Credentials },
validation: { mimeTypes: ['image/png'] },
options:{
key: process.env.AWS_ACCESS_KEY_ID,
// s3Key: process.env.AWS_ACCESS_KEY_ID,
bucket: process.env.AWS_S3_BUCKET,
mime: 'image/png',
},
}),
],
},

I tried to insert an Image in MongoDB using Mongoose with the help of AWS S3, and by the documentation of @adminjs/upload, I got the code. When I got the above error, I replaced "s3Key" with "key" but nothing works..!!

I also tried it with Local File System, but got the same error there as well.
features: [
uploadFeature({
provider: localProvider,
validation: {
mimeTypes: ['image/png', 'application/pdf'],
},
options: {
key: 323,
bucket: './public/files',
s3Key: 'uploadFeature',
},
}),
],

I use https://docs.adminjs.co/basics/features/upload for reference.

Rollup PARSE_ERROR and error in the UI

When I try to add the upload functionality for my Blog mongoose model I get this error when I start the server

Error: Unexpected token (Note that you need plugins to import files that are not JavaScript)
    at error (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:7862:30)
    at Module.error (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:9795:16)
    at Module.tryParse (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:10193:25)
    at Module.setSource (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:10097:24)
    at ModuleLoader.addModuleSource (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:19399:20)
    at ModuleLoader.fetchModule (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:19455:9)
    at async Promise.all (index 0)
    at ModuleLoader.fetchStaticDependencies (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:19481:34)
    at async Promise.all (index 0)
    at ModuleLoader.fetchModule (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:19457:9)
    at async Promise.all (index 0) {
  code: 'PARSE_ERROR',
  parserError: SyntaxError: Unexpected token (6:10)
      at Parser.pp$4.raise (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:16681:13)
      at Parser.pp.unexpected (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:14191:8)
      at Parser.pp$1.parseVar (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:14732:12)
      at Parser.pp$1.parseVarStatement (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:14594:8)
      at Parser.pp$1.parseStatement (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:14344:17)
      at Parser.pp$1.parseTopLevel (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:14248:21)
      at Parser.parse (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:14040:15)
      at Function.parse (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:14071:35)
      at Graph.contextParse (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:20009:38)
      at Module.tryParse (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:10183:31)
      at Module.setSource (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:10097:24)
      at ModuleLoader.addModuleSource (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:19399:20)
      at ModuleLoader.fetchModule (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:19455:9)
      at async Promise.all (index 0)
      at ModuleLoader.fetchStaticDependencies (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:19481:34)
      at async Promise.all (index 0)
      at ModuleLoader.fetchModule (/home/Projects/litepages_backend/node_modules/rollup/dist/shared/rollup.js:19457:9)
      at async Promise.all (index 0) {
    pos: 261,
    loc: Position { line: 6, column: 10 },
    raisedAt: 262
  },
  id: '/home/Projects/litepages_backend/node_modules/@admin-bro/upload/src/features/upload-file/components/edit.tsx',
  pos: 261,
  loc: {
    column: 10,
    file: '/home/Projects/litepages_backend/node_modules/@admin-bro/upload/src/features/upload-file/components/edit.tsx',
    line: 6
  },
  frame: "4: import PropertyCustom from '../types/property-custom.type'\n" +
    '5: \n' +
    '6: const Edit: FC<EditPropertyProps> = ({ property, record, onChange }) => {\n' +
    '             ^\n' +
    '7:   const { params } = record\n' +
    '8:   const { custom } = property as unknown as { custom: PropertyCustom }',
  watchFiles: [
    '/home/Projects/litepages_backend/.adminbro/.entry.js',
    '/home/Projects/litepages_backend/node_modules/@admin-bro/upload/src/features/upload-file/components/edit.tsx',
    '/home/Projects/litepages_backend/node_modules/@admin-bro/upload/src/features/upload-file/components/list.tsx',
    '/home/Projects/litepages_backend/node_modules/@admin-bro/upload/src/features/upload-file/components/show.tsx'
  ]
}

I'm running

    "@admin-bro/express": "^3.1.0",
    "@admin-bro/mongoose": "^1.1.0",
    "@admin-bro/upload": "^1.3.1",
    "admin-bro": "^4.0.1",

and this is my resources configuration:

resources: [
        {
          resource: Blog,
          features: [
            uploadFeature({
              provider: { aws: { ...config.aws } },
              properties: {
                key: 'bannerImage', // to this db field feature will safe S3 key
                // where mime type will be stored
                mimeType: `mimeType`,
                // where bucket name will be stored
                bucket: `bucket`,
                // where size will be stored
                size: `size`,
              },
              validation: {
                mimeTypes: ['image/png', 'image/jpg', 'image/jpeg'],
              },
            }),
          ],
          options: {
            properties: {
              text: {
                type: 'richtext',
                props: {
                  borderless: true,
                },
              },
              bannerImage: {
                isVisible: false,
              },
              createdAt: {
                isVisible: {
                  list: true,
                  filter: true,
                  show: true,
                  edit: false,
                },
              },
              updatedAt: {
                isVisible: {
                  list: true,
                  filter: true,
                  show: true,
                  edit: false,
                },
              },
              slug: {
                isVisible: {
                  list: true,
                  filter: true,
                  show: true,
                  edit: false,
                },
              },
              slug_history: {
                isVisible: {
                  list: true,
                  filter: true,
                  show: true,
                  edit: false,
                },
              },
            },
          },
        }
      ],

If I open the view to create a new record I get this error

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports. Check the render method of `BasePropertyComponent`.

Desktop-screenshot

Has anyone had a similar issue?

alternative to admin-bro-upload-array?

Hi guys, I'm having a little problem with the library's upload feature. so basically here is my data model

{
...
data: [
{
fileUrl: [string], // list of strings
...
},
{
fileUrl: [string],
...
}
]
}

I need to be able to upload an array of files for each element in the array. I found this library but it's not for admin js instead for an old version of it. I really want to avoid downgrading. I think that there must be a way to do what I need but I can't find it anywhere.
even though this seems like a really useful feature

unknown error, mkdir while proceeding simple Local upload

I am using express+mongoose. I've created a simple admin panel and now struggling to make images upload. I did everything close to documentation and kept it very simple the code is:

Mongoose model

const newsSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
    trim: true    
  },
  text: {
    type: String,
    required: true,
  },
  pubDate: {
    type: Date,
    required: true
  },
  prevImg: uploadedFile
})

const News = mongoose.model('News', newsSchema)

module.exports = News

where uploadedFile is a mongoose schema:

const uploadedFile = new mongoose.Schema({
  path: String,
  type: String,
  size: Number,
  folder: String,
  filename: String
})

module.exports = uploadedFile

My admin setup is following:

const AdminBro = require('admin-bro')
const AdminBroExpress = require('@admin-bro/express')
const AdminBroMongoose = require('@admin-bro/mongoose')
const uploadFeature = require('@admin-bro/upload')
const auth = require('./auth');

const News = require('../models/news');
const User = require('../models/user');
const Partner = require('../models/partner');

AdminBro.registerAdapter(AdminBroMongoose)

const resources = [
  {
    resource: News,
    options: {prevImg: {isVisible: false}},
    features: [
      uploadFeature({
        provider: {local: {bucket: '/public'}},
        properties: {
          key: 'prevImg.path',
          bucket: 'prevImg.folder',
          mimeType: 'prevImg.type',
          size: 'prevImg.size',
          filename: 'prevImg.filename',
          file: 'prevImg'
        }
      })
    ]
  },
  {
    resource: Partner,
    options: {}
  },
  {
    resource: User,
    options: {
      properties: {
        password: { isVisible: false }
      }
    }
  }
]

const adminBro = new AdminBro({
  rootPath: '/admin',
  resources,
  branding: {
    logo: '/public/img/logo.png',
    companyName: 'Company',
    softwareBrothers: false
  }
})

module.exports = adminRouter = AdminBroExpress.buildAuthenticatedRouter(adminBro, {
  authenticate: auth,
  cookiePassword: 'session Key'
})

And I of course serve public directory:
UPD: I am sure that public directory is served properly because the logo in branding configuration is loading just fine.

const publicPath = path.join(__dirname, '../public')
app.use('/public', express.static(publicPath))

When adding new News in the admin pannel, it tries to save the record for couple of seconds and then returns an unknown error:
Error: UNKNOWN: unknown error, mkdir
I thought it was due to the access restrictions for a folder but when I fixed them nothing's changed.
Will appreciate any help. Thanks for your time spent.

AWS CredentialsProviderError

When trying to define a File resource I get the following error:

CredentialsProviderError: Could not load credentials from any providers
    at /Users/luispe/Repos/ultradrop-app/node_modules/@aws-sdk/credential-provider-node/dist-cjs/defaultProvider.js:13:11
    at /Users/luispe/Repos/ultradrop-app/node_modules/@smithy/property-provider/dist-cjs/chain.js:11:28
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async coalesceProvider (/Users/luispe/Repos/ultradrop-app/node_modules/@smithy/property-provider/dist-cjs/memoize.js:14:24)
    at async SignatureV4.credentialProvider (/Users/luispe/Repos/ultradrop-app/node_modules/@smithy/property-provider/dist-cjs/memoize.js:33:24)
    at async SignatureV4.presign (/Users/luispe/Repos/ultradrop-app/node_modules/@smithy/signature-v4/dist-cjs/SignatureV4.js:29:29)
    at async /Users/luispe/Repos/ultradrop-app/node_modules/@aws-sdk/s3-request-presigner/dist-cjs/getSignedUrl.js:32:27
    at async /Users/luispe/Repos/ultradrop-app/node_modules/@smithy/middleware-retry/dist-cjs/retryMiddleware.js:27:46
    at async /Users/luispe/Repos/ultradrop-app/node_modules/@aws-sdk/middleware-flexible-checksums/dist-cjs/flexibleChecksumsMiddleware.js:58:20
    at async /Users/luispe/Repos/ultradrop-app/node_modules/@aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:7:26
    at async getSignedUrl (/Users/luispe/Repos/ultradrop-app/node_modules/@aws-sdk/s3-request-presigner/dist-cjs/getSignedUrl.js:54:24)
    at async fillRecordWithPath (file:///Users/luispe/Repos/ultradrop-app/node_modules/@adminjs/upload/build/features/upload-file/utils/fill-record-with-path.js:11:20)
    at async fillPath (file:///Users/luispe/Repos/ultradrop-app/node_modules/@adminjs/upload/build/features/upload-file/upload-file.feature.js:37:21)
    at async file:///Users/luispe/Repos/ultradrop-app/node_modules/@adminjs/express/lib/buildRouter.js:24:22

This is my code:

export const createFileResource = (componentLoader: ComponentLoader) => ({
  resource: {
    id: "id",
    model: getModelByName("File"),
    client: prisma,
  },
  features: [
    uploadFeature({
      componentLoader,
      provider: {
        aws: {
          accessKeyId: process.env.AWS_ACCESS_KEY_ID,
          secretAccessKey: process.env.AWS_ACCESS_SECRET,
          region: process.env.AWS_REGION,
          bucket: process.env.AWS_BUCKET,
        },
      },
      properties: {
        key: "key",
        mimeType: "mime",
      },
    }),
  ],
});

I've even tried hardcoding my credentials, but it still didn't work

BUG: Cannot upload upload files into Postgres TypeORM with multiple: true

Description

When I'm trying to update/create a resource with uploader that has multiple: true option it will not save the photos into the database record

Possible issue source

After some research I found that with multiple: true params are stored in variant:
{ 'photos.bucketKey.0': 'kjgiqh23pib4h2b34jh234', 'photos.name.0': 'picture.png' }

Meaning 0 is placed at the end, so unflatten creates from it the following object:
{ photos: { bucketKey: ['kjgiqh23pib4h2b34jh234'], name: 'picture.png' } }

But running update on typeorm repository will throw following error:
QueryFailedError: cannot get array length of a non-array

I think it's the case why its not working

Possible solution

Replace the form of multiple files handling into:
{ 'photos.0.bucketKey': 'kjgiqh23pib4h2b34jh234', 'photos.0.name': 'picture.png' } (zeros are placed in the middle)

Environment

adminjs: 5.2.1
@adminjs/upload: 2.0.1
@adminjs/typeorm: 2.0.0
typeorm: 0.2.36
postgresql: 13.3

How to make upload mandatory

If my mongoose schema and admin code is is :

properties: {
  ...,
  media: {
    isVisible: { list: false, show: true, edit: false, filter: false },
  },
}

new Schema({
    label: { type: String, required: true },
    tag: { type: String, required: true },
    media: { type: Schema.Types.Mixed, required: true }
 })

Screenshot 2020-12-27 at 11 44 01 PM

I get an extra input field named media , if I get rid of

media: {
    isVisible: { list: false, show: true, edit: false, filter: false },
  },

Multifile upload with mongoose

I was struggling getting multiple file upload to work with mongoose models and wonder if there is a better way to do it.

What is working:

const Files = new Schema({
  key: [String],
  mimeType: [String],
  bucket: [String],
  size: [Number],
});

const modelWithImages = model(
  "WithImages",
  new Schema({
    multipleImages: Files,
  })
);

const adminJs = new AdminJS({
    databases: [database],
    resources: [
      {
        resource: modelWithImages,
        options: {
          properties: {
            "multipleImages.key": { isVisible: false },
            "multipleImages.mimeType": { isVisible: false },
            "multipleImages.bucket": { isVisible: false },
            "multipleImages.size": { isVisible: false },
          },
        },
        features: [
          uploadFileFeature({
            provider: {
              aws: {
                bucket: "adminjstest",
                region: "eu-central-1",
                expires: 0,
                accessKeyId: process.env.S3_KEY,
                secretAccessKey: process.env.S3_SECRET,
              },
            },
            properties: {
              key: "multipleImages.key",
              mimeType: "multipleImages.mimeType",
              bucket: "multipleImages.bucket",
              size: "multipleImages.size",
              file: "multipleImages.file",
              filePath: "multipleImages.filePath",
              filename: "multipleImages.filename",
              filesToDelete: "multipleImages.filesToDelete",
            },
            multiple: true,
            uploadPath: (record, filename) =>
              `${record.id()}/multipleImages/${filename}`,
          })
        ],
      },
    ],
    rootPath: "/",
  });

what i expected to work somehow:

const File = new Schema({
  key: String,
  mimeType: String,
  bucket: String,
  size: Number,
});

const modelWithImages = model(
  "WithImages",
  new Schema({
    multipleImages: [File],
  })
);

const adminJs = new AdminJS({
    databases: [database],
    resources: [
      {
        resource: modelWithImages,
        options: {
          properties: {
            multipleImages: {
              type: "mixed,
              isArray: true
            }
          },
        },
        features: [
          uploadFileFeature({
            provider: {
              aws: {
                bucket: "adminjstest",
                region: "eu-central-1",
                expires: 0,
                accessKeyId: process.env.S3_KEY,
                secretAccessKey: process.env.S3_SECRET,
              },
            },
            properties: {
              key: "multipleImages.key",
              mimeType: "multipleImages.mimeType",
              bucket: "multipleImages.bucket",
              size: "multipleImages.size",
              file: "multipleImages.file",
              filePath: "multipleImages.filePath",
              filename: "multipleImages.filename",
              filesToDelete: "multipleImages.filesToDelete",
            },
            multiple: true,
            uploadPath: (record, filename) =>
              `${record.id()}/multipleImages/${filename}`,
          })
        ],
      },
    ],
    rootPath: "/",
  });

While the second model is more in line with my understanding of having a nested array of files, it doesn't work with AdminJS Upload because upload with multiple expects key, mimeType, bucket and size to be arrays themself.

Is there any way to make it work with the second model, or could we introduce a way to let adminJS know what exactly is the array? For example, inside the uploadFeature properties we could be able to write a kinda template literal like "multipleImages.${index}.key" which could be interpreted in the frontend to know where the index of the array should go. Or the frontend could be intelligently checking if multipleImages is an Array (as specified in the resource options with multipleImages: {type: "mixed", isArray: true}) and if yes, try to create an object within this array.

several fileUploads in one model doesn't work

Hello! I've tried to create a model with several dropzones to load different images, but it doesn't save it.
Dropzones are displayed for this, but there is no information in my DB after saving.

Here's my code.

Test Model

const { Schema, model, Types } = require('mongoose')

const TestSchema = new Schema({
  title: String,
  preview: {
    fileUrl: String,
    type: String,
    folder: String,
    size: Number,
  },
  photo: {
    key: String,
    mime: String,
    bucket: String,
    filesize: Number,
  },
})

module.exports = model('Test', TestSchema)

Test Options

const AdminBro = require('admin-bro')

/** @type {AdminBro.ResourceOptions} */
const options = {
  listProperties: ['title'],
  editProperties: ['title', 'preview', 'photo'],
  navigation: {
    name: 'TEST',
    icon: '',
  },
  properties: {},
}

module.exports = options

AdminBroOptions

/** @type {import('admin-bro').AdminBroOptions} */

const options = {
  resources: [
    {
      resource: Test,
      options: testOptions,
      features: [
        uploadFeature({
          provider,
          properties: {
            file: `preview`,
            filePath: `preview.filePath`,
            filesToDelete: `preview.filesToDelete`,
            key: `preview.fileUrl`,
            mimeType: `preview.type`,
            bucket: `preview.folder`,
            size: `preview.size`,
          },
          validation: { mimeTypes },
          uploadPath: (record, filename) =>
            `${record.id()}/preview/${filename}`,
        }),
        uploadFeature({
          provider,
          properties: {
            file: `photo`,
            filePath: `photo.filePath`,
            filesToDelete: `photo.filesToDelete`,
            key: `photo.fileUrl`,
            mimeType: `photo.type`,
            bucket: `photo.folder`,
            size: `photo.size`,
          },
          validation: {
            mimeTypes: ['image/png', 'image/jpg', 'image/jpeg'],
          },
          uploadPath: (record, filename) => `${record.id()}/photo/${filename}`,
        }),
      ],
    },
}

module.exports = options

Can not upload file

Describe the bug

I need to attach files when I create a new record in a table. With empty files or files that have a small size, everything works perfectly. But for some reason, when you try to attach a pdf with a size of >3 mb, an error pops up in the panel. The console doesn't have it though. Therefore, I have a question: is there a default limit on the file size, and if there is one, is it possible to change it somehow?

Installed libraries and their versions

Here is the code snippet where I handle file uploading

uploadFeature({
          provider: { local: { bucket: path.join(__dirname, '../public/trips') } },
          properties: {
            file: 'registration.file',
            filePath: 'registration.path',
            filename: 'registration.filename',
            filesToDelete: 'registration.toDelete',
            key: 'registration.key',
            mimeType: 'registration.mimeType',
            bucket: 'registration.bucket',
          },
          multiple: true,
          uploadPath: (record, filename) => `${record.id()}/registration/${filename}`,
        }),

Upload in custom component

Hello!
Is it possible to use the upload mechanism inside a custom component, without using the standard frontend? I'd like a built-in mechanism to save my files and return the key, title, size, mimeType, etc.

AWSOptions type information is not compatible by aws-sdk v3

The type information of AWSOptions has not changed since using aws-sdk v2.
Therefore, this type needs to be changed to aws-sdk v3 compatible type.

Specifically, the differences are as follows.

aws-sdk v2 compatible type (current type)

export type AWSOptions = {
  accessKeyId?: string;
  secretAccessKey?: string;
  region: string;
  bucket: string;
  expires?: number;
}

aws-sdk v3 compatible type

export type AWSOptions = {
  credentials?: {
    accessKeyId: string;
    secretAccessKey: string;
  },
  region: string;
  bucket: string;
  expires?: number;
}

In the current implementation, the AWSOptions information is passed as is to the S3 Client option information.
So If it do not change the implementation of AWSProvider, it will need to change the AWSOptions type information.

Freezing on svg mimeType

I am trying to upload svg file (Mime type settings are correct) but its freezing when i check the network section on devtools its says pending. (There are no error or message)

Resource

const profileResource: {
    resource: any,
    options: ResourceOptions,
    features?: any
} = {
    resource: { model: getModelByName("Profile"), client: prisma },
    options: {
        properties: {
            mime: { isVisible: false },
            bucket: { isVisible: false },
            logo_key: { isVisible: { edit: false, list: false } }
        }
    },
    features: [uploadFileFeature({
        componentLoader,
        provider: { local: { bucket: 'public', opts: {} } },
        properties: { file: "logo", key: 'logo_key', bucket: 'bucket', mimeType: 'mime' },
        validation: { mimeTypes: ['image/png', "image/jpeg", "image/gif", "image/svg+xml"] },
    })],
}

Devtools

image

In page (Admin panel)

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.