diff --git a/lib/mastodon/router.js b/lib/mastodon/router.js index 9cb25ca..8ba5140 100644 --- a/lib/mastodon/router.js +++ b/lib/mastodon/router.js @@ -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 })); diff --git a/lib/mastodon/routes/media.js b/lib/mastodon/routes/media.js index 8ade77e..b948a1d 100644 --- a/lib/mastodon/routes/media.js +++ b/lib/mastodon/routes/media.js @@ -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(), }; diff --git a/package-lock.json b/package-lock.json index b2262e5..c0b9672 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 95fd27f..c0d91b5 100644 --- a/package.json +++ b/package.json @@ -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" },