fix: replace multer with express-fileupload for media uploads
Indiekit's express.js applies express-fileupload globally, which consumes the multipart stream before multer can read it, causing "Unexpected end of form". Use req.files (from express-fileupload) instead of req.file (from multer). Remove multer dependency.
This commit is contained in:
+4
-12
@@ -67,18 +67,10 @@ export function createMastodonRouter({ collections, pluginOptions = {} }) {
|
||||
|
||||
// ─── Body parsers ───────────────────────────────────────────────────────
|
||||
// Mastodon clients send JSON, form-urlencoded, and occasionally text/plain.
|
||||
// Skip multipart/form-data requests — multer handles those in media routes.
|
||||
// If express.json/urlencoded consume the stream first, multer gets nothing.
|
||||
const jsonParser = express.json();
|
||||
const urlencodedParser = express.urlencoded({ extended: true });
|
||||
router.use("/api", (req, res, next) => {
|
||||
if (req.is("multipart/form-data")) return next();
|
||||
jsonParser(req, res, next);
|
||||
});
|
||||
router.use("/api", (req, res, next) => {
|
||||
if (req.is("multipart/form-data")) return next();
|
||||
urlencodedParser(req, res, next);
|
||||
});
|
||||
// Note: multipart/form-data is handled globally by express-fileupload
|
||||
// (configured in Indiekit's express.js), so no multer needed here.
|
||||
router.use("/api", express.json());
|
||||
router.use("/api", express.urlencoded({ extended: true }));
|
||||
router.use("/oauth", express.json());
|
||||
router.use("/oauth", express.urlencoded({ extended: true }));
|
||||
|
||||
|
||||
@@ -5,18 +5,16 @@
|
||||
* POST /api/v1/media — legacy upload (same as v2)
|
||||
* GET /api/v1/media/:id — get media attachment metadata
|
||||
* PUT /api/v1/media/:id — update media metadata (description/focus)
|
||||
*
|
||||
* File uploads are handled by express-fileupload (configured globally by
|
||||
* Indiekit's express.js). Files arrive on req.files, NOT req.file (multer).
|
||||
*/
|
||||
import express from "express";
|
||||
import multer from "multer";
|
||||
import { ObjectId } from "mongodb";
|
||||
import { tokenRequired } from "../middleware/token-required.js";
|
||||
import { scopeRequired } from "../middleware/scope-required.js";
|
||||
|
||||
const router = express.Router(); // eslint-disable-line new-cap
|
||||
const upload = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
limits: { fileSize: 40 * 1024 * 1024 },
|
||||
});
|
||||
|
||||
/**
|
||||
* Determine Mastodon media type from MIME type.
|
||||
@@ -54,6 +52,7 @@ function serializeMediaAttachment(doc) {
|
||||
|
||||
/**
|
||||
* Upload file to the Micropub media endpoint.
|
||||
* Accepts an express-fileupload file object (has .data Buffer, .mimetype, .name).
|
||||
* Returns the URL from the Location header.
|
||||
*/
|
||||
async function uploadToMediaEndpoint(file, application, token) {
|
||||
@@ -67,8 +66,8 @@ async function uploadToMediaEndpoint(file, application, token) {
|
||||
: new URL(mediaEndpoint, application.url).href;
|
||||
|
||||
const formData = new FormData();
|
||||
const blob = new Blob([file.buffer], { type: file.mimetype });
|
||||
formData.append("file", blob, file.originalname);
|
||||
const blob = new Blob([file.data], { type: file.mimetype });
|
||||
formData.append("file", blob, file.name);
|
||||
|
||||
const response = await fetch(mediaUrl, {
|
||||
method: "POST",
|
||||
@@ -95,7 +94,6 @@ router.post(
|
||||
"/api/v2/media",
|
||||
tokenRequired,
|
||||
scopeRequired("write", "write:media"),
|
||||
upload.single("file"),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const { application } = req.app.locals;
|
||||
@@ -103,7 +101,8 @@ router.post(
|
||||
const token =
|
||||
req.session?.access_token || req.mastodonToken?.accessToken;
|
||||
|
||||
if (!req.file) {
|
||||
const file = req.files?.file;
|
||||
if (!file) {
|
||||
return res.status(422).json({ error: "No file provided" });
|
||||
}
|
||||
|
||||
@@ -113,17 +112,13 @@ router.post(
|
||||
.json({ error: "Authentication required for media upload" });
|
||||
}
|
||||
|
||||
const fileUrl = await uploadToMediaEndpoint(
|
||||
req.file,
|
||||
application,
|
||||
token,
|
||||
);
|
||||
const fileUrl = await uploadToMediaEndpoint(file, application, token);
|
||||
|
||||
const doc = {
|
||||
url: fileUrl,
|
||||
description: req.body.description || "",
|
||||
focus: req.body.focus || null,
|
||||
mimeType: req.file.mimetype,
|
||||
mimeType: file.mimetype,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
@@ -143,7 +138,6 @@ router.post(
|
||||
"/api/v1/media",
|
||||
tokenRequired,
|
||||
scopeRequired("write", "write:media"),
|
||||
upload.single("file"),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const { application } = req.app.locals;
|
||||
@@ -151,7 +145,8 @@ router.post(
|
||||
const token =
|
||||
req.session?.access_token || req.mastodonToken?.accessToken;
|
||||
|
||||
if (!req.file) {
|
||||
const file = req.files?.file;
|
||||
if (!file) {
|
||||
return res.status(422).json({ error: "No file provided" });
|
||||
}
|
||||
|
||||
@@ -161,17 +156,13 @@ router.post(
|
||||
.json({ error: "Authentication required for media upload" });
|
||||
}
|
||||
|
||||
const fileUrl = await uploadToMediaEndpoint(
|
||||
req.file,
|
||||
application,
|
||||
token,
|
||||
);
|
||||
const fileUrl = await uploadToMediaEndpoint(file, application, token);
|
||||
|
||||
const doc = {
|
||||
url: fileUrl,
|
||||
description: req.body.description || "",
|
||||
focus: req.body.focus || null,
|
||||
mimeType: req.file.mimetype,
|
||||
mimeType: file.mimetype,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
|
||||
Generated
+2
-166
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
||||
"version": "3.11.2",
|
||||
"version": "3.11.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
||||
"version": "3.11.2",
|
||||
"version": "3.11.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fedify/debugger": "^2.1.0",
|
||||
@@ -17,7 +17,6 @@
|
||||
"express": "^5.0.0",
|
||||
"express-rate-limit": "^7.5.1",
|
||||
"ioredis": "^5.9.3",
|
||||
"multer": "^2.1.1",
|
||||
"sanitize-html": "^2.13.1",
|
||||
"unfurl.js": "^6.4.0"
|
||||
},
|
||||
@@ -1518,12 +1517,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/append-field": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
|
||||
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
@@ -1586,23 +1579,6 @@
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
|
||||
"dependencies": {
|
||||
"streamsearch": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/byte-encodings": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/byte-encodings/-/byte-encodings-1.0.11.tgz",
|
||||
@@ -1751,21 +1727,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
|
||||
"engines": [
|
||||
"node >= 6.0"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.0.2",
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
|
||||
@@ -3014,68 +2975,6 @@
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/multer": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz",
|
||||
"integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"append-field": "^1.0.0",
|
||||
"busboy": "^1.6.0",
|
||||
"concat-stream": "^2.0.0",
|
||||
"type-is": "^1.6.18"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/multer/node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/multer/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/multer/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/multer/node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
@@ -3402,20 +3301,6 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/redis-errors": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||
@@ -3453,26 +3338,6 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
@@ -3714,23 +3579,6 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/streamsearch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
|
||||
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/structured-field-values": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/structured-field-values/-/structured-field-values-2.0.4.tgz",
|
||||
@@ -3782,12 +3630,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/typedarray": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typo-js": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.3.1.tgz",
|
||||
@@ -3878,12 +3720,6 @@
|
||||
"integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
||||
+1
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
||||
"version": "3.11.2",
|
||||
"version": "3.11.3",
|
||||
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
||||
"keywords": [
|
||||
"indiekit",
|
||||
@@ -45,7 +45,6 @@
|
||||
"express": "^5.0.0",
|
||||
"express-rate-limit": "^7.5.1",
|
||||
"ioredis": "^5.9.3",
|
||||
"multer": "^2.1.1",
|
||||
"sanitize-html": "^2.13.1",
|
||||
"unfurl.js": "^6.4.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user