Skip to content

Uploading simple local file gives: "no such file or directory, mkdir 'C:\\...\\api-fastify\\public\\files\\id()" ... "Resource: \"${this.resource.id()}\" does not have an id property" #83

@rw3iss

Description

@rw3iss

I'm trying to setup AdminJS on a simple fastify backend, using typeorm and postges. I have been having issues getting a file upload to work, suggesting there is no id property set on the resource.
Following the example app for a single file, the upload form seems to work, but when I save it the backend gives the error:

{
    "statusCode": 500,
    "code": "ENOENT",
    "error": "Internal Server Error",
    "message": "ENOENT: no such file or directory, mkdir 'C:\\Users\\rw3iss\\Sites\\api-fastify\\public\\files\\id() {\n        
        const idProperty = this.resource.properties().find(p => p.isId());\n        
        if (!idProperty) {\n           
            throw new Error(`Resource: \"${this.resource.id()}\" does not have an id property`);\n        
        }\n        
return this.params[idProperty.name()];\n    }'"
}

relevant package.json dependency versions are:

 "dependencies": {
        "@adminjs/fastify": "^4.1.3",
        "@adminjs/passwords": "^4.0.0",
        "@adminjs/typeorm": "^5.0.1",
        "@adminjs/upload": "^4.0.2",
        "@fastify/session": "^10.9.0",
        "adminjs": "^7.8.7",
        "connect-pg-simple": "^9.0.1",
        "fastify": "^4.28.1",
        "pg": "^8.12.0",
    }

The typeorm File entity is defined as:

import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';

@Entity({ name: 'files' })
export class File extends BaseEntity {

    @PrimaryGeneratedColumn()
    public id: number;

    @Column({ name: 'key', nullable: true, type: 'varchar' })
    public key: string;

    @Column({ nullable: true, type: 'varchar' })
    public bucket: string;

    @Column({ nullable: true, type: 'varchar' })
    public mime: string;

    @Column('int', { nullable: true })
    public size: number;

    @Column('varchar', { nullable: true })
    public comment: string;

    @CreateDateColumn({ name: 'created_at' })
    public createdAt: Date;

    @UpdateDateColumn({ name: 'updated_at' })
    public updatedAt: Date;

}

export interface IFile {
    id: number;
    key: string;
    bucket: string;
    mime: string;
    size: number;
    comment: string | null;
}

The AdminJS File resource:

import uploadFeature from '@adminjs/upload';
import { BaseRecord } from 'adminjs';
import { File } from './../file.entity';
import { CreateResourceResult } from './CreateResourceResult';

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

export const createFileResource = (componentLoader): CreateResourceResult<typeof File> => ({
    resource: File,
    options: {
        listProperties: ['id', 'key', 'bucket', 'path']
    },
    features: [
        uploadFeature({
            componentLoader,
            provider: {
                local: localUploadProvider
            },
            multiple: false,
            properties: {
                file: 'file',
                filename: 'filename',
                filePath: 'filePath',
                filesToDelete: 'filesToDelete',
                key: 'key',
                mimeType: 'mime',
                bucket: 'bucket',
                size: 'size'
            },
            validation: {
                mimeTypes: ['image/jpeg', 'image/png']
            },
            uploadPath: (record: BaseRecord, filename) => {
                console.log(`get upload path`, record.id, filename);
                return `${record.id}/${filename}`;
            }
        })
    ],
});

The AdminJS config/initialization is:

        const app = Fastify({ logger: config.debug });
        app.register(fastifyTypeorm, config.postgres);
        app.register(fastifyStatic, {
            root: path.join(import.meta.dirname, 'public'),
            prefix: '/',
        })

        const componentLoader = new ComponentLoader();
        const fileResource = createFileResource(componentLoader);

        const admin = new AdminJS({
            rootPath: '/admin',
            componentLoader,
            resources: [
                fileResource
            ]
        })
        admin.watch();

        await AdminJSFastify.buildAuthenticatedRouter(
            admin,
            {
                authenticate,
                cookiePassword: config.adminJs.cookieSecret,
                cookieName: config.adminJs.cookieSecret,
            },
            app,
            {
                store: sessionStore as any,
                saveUninitialized: true,
                secret: config.adminJs.cookieSecret,
                cookie: {
                    httpOnly: config.isProduction,
                    secure: config.isProduction
                },
            }
        )

Here's a screenshot of the upload page after saving:
Screenshot 2024-07-14 051254

In trying to debug I notice the adminjs library tries to assign the properties, but it seems the id property never has _isId: true, I only ever see it false for any property, even after the property decoration is finished and merged:

 PropertyDecorator {
  property: Property {
    _path: 'id',
    _type: 'string',
    _isId: false,
    _isSortable: true,
    _position: 1,
    column: ColumnMetadata {

I've tried to add the isId: true property to the File resource id property, but it still never shows _isId: true anywhere.

{
    resource: File,
    options: {
        listProperties: ['id', 'key', 'bucket', 'path'],
        properties: {
            id: {
                isId: true
            }
    }, ...

Any ideas? I don't know if this an issue in the typeorm adapter or the adminjs BaseAdapter not loading those properties as expected, or maybe my config is wrong. I have been trying to dig further into the adminjs and upload feature code to see why. Still working it out...

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions