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);
...
I think it's just a windows issue where it doesn't allow you to move files from one partition to another
I think it's just a windows issue where it doesn't allow you to move files from one partition to another
I have the same issue when using docker
Same problem using ArchLinux. I think is because the /tmp folder is in the root partition while the upload folder is in the home partition
I have the same issue in docker. Anyone solved this?
i have same problem
Solution:
Made a custom provider using the move method instead of rename (look at the upload method)
import fs, { existsSync } from "fs";
import { move } from "fs-extra";
import path from "path";
import { UploadedFile } from "adminjs";
import { BaseProvider } from "@adminjs/upload";
import { UPLOADS_DIR } from "../env";
export default class UploadProvider extends BaseProvider {
constructor() {
super(UPLOADS_DIR);
if (!existsSync(UPLOADS_DIR)) {
throw new Error(`directory: "${UPLOADS_DIR}" does not exists. Create it before running LocalAdapter`);
}
}
// * Fixed this method because original does rename instead of move and it doesn't work with docker volume
public async upload(file: UploadedFile, key: string): Promise<any> {
const filePath = process.platform === "win32" ? this.path(key) : this.path(key).slice(1); // adjusting file path according to OS
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
await move(file.path, filePath, { overwrite: true });
}
public async delete(key: string, bucket: string): Promise<any> {
await fs.promises.unlink(process.platform === "win32" ? this.path(key, bucket) : this.path(key, bucket).slice(1)); // adjusting file path according to OS
}
// eslint-disable-next-line class-methods-use-this
public path(key: string, bucket?: string): string {
// Windows doesn't requires the '/' in path, while UNIX system does
return process.platform === "win32"
? `${path.join(bucket || this.bucket, key)}`
: `/${path.join(bucket || this.bucket, key)}`;
}
}
And I connect it to the resource like this:
uploadFeature({
provider: new UploadProvider(),
validation: {
mimeTypes: ["image/jpeg", "image/png"],
},
})
It works fine!
it works on windows provided that the project is on the same partition as the windows installation, and on linux also it works provided you have the right admin rights and the right distribution: example the code does not work on heroku but works fine on aws ec2
The original LocaleProvider also doesn't work in docker. The code above should work everywhere, but I haven't tested it on Windows.
Addition: The original code does not work in docker if the "uploads" folder is mounted as "volume"
Can I also use this custom uploader for cloudinary? I have tried making something out of it but I could not, I would really appreciate if I can get any solution
@etomarat solution might be even simplified to the shape:
import path from 'path';
import fsExtra from 'fs-extra';
import {LocalProvider} from '@adminjs/upload';
import {UploadedFile} from 'adminjs';
class LocalProvider2 extends LocalProvider {
public async upload(file: UploadedFile, key: string): Promise<any> {
const filePath = process.platform === 'win32' ? this.path(key) : this.path(key).slice(1); // adjusting file path according to OS
await fsExtra.mkdir(path.dirname(filePath), {recursive: true});
await fsExtra.move(file.path, filePath, {overwrite: true});
}
}
Does anyone know about such a problem:
Executing (default): INSERT INTO "brands" ("id","title") VALUES (DEFAULT,$1) RETURNING "id","title","relatedItems","file","s3Key","bucket","mime";
Error: You cannot upload file for not persisted record. Save record first
at buildRemotePath (file:///C:/.../adminJs/node_modules/@adminjs/upload/build/features/upload-file/utils/build-remote-path.js:16:15)
at updateRecord (file:///C:/.../adminJs/node_modules/@adminjs/upload/build/features/upload-file/factories/update-record-factory.js:74:29)
at file:///C:/.../adminJs/node_modules/adminjs/lib/backend/decorators/action/action-decorator.js:120:99
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
It appeared after replacing the original UploadProvider with the one suggested by @etomarat. I use PostgreSQL with ExpressJS