Changeset e6c6e6f in Klonkt for src/routes/activitypub.js
- Timestamp:
- 07/20/2026 10:27:19 PM (7 weeks ago)
- Branches:
- main
- Children:
- 155c24e
- Parents:
- 024f4f8
- git-author:
- Robin <roboburr@…> (07/20/2026 10:27:18 PM)
- git-committer:
- Robin <roboburr@…> (07/20/2026 10:27:19 PM)
- File:
-
- 1 edited
-
src/routes/activitypub.js (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/activitypub.js
r024f4f8 re6c6e6f 19 19 import { apEnabled } from '../services/SettingsService.js'; 20 20 import OAuth from '../services/OAuthService.js'; 21 import multer from 'multer'; 22 import path from 'path'; 23 import fs from 'fs'; 24 import { fileURLToPath } from 'url'; 25 import { randomUUID } from 'crypto'; 21 26 22 27 const router = express.Router(); … … 118 123 }); 119 124 125 // ── uploadMedia (owner only, AP C2S) ────────────────────────────── 126 // The actor advertises endpoints.uploadMedia; this implements it. A bearer 127 // scoped to this site uploads one image/audio/video (multipart field "file", 128 // AP convention) into the same store the reply editor uses, and gets back 129 // { url, mediaType, name } to attach on a note (e.g. the help-buoy capture). 130 const AP_MEDIA_DIR = path.resolve( 131 process.env.REPLY_MEDIA_PATH || 132 path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'storage', 'media', 'reply-media') 133 ); 134 fs.mkdirSync(AP_MEDIA_DIR, { recursive: true }); 135 const AP_MEDIA_EXT = new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif', '.mp3', '.m4a', '.ogg', '.opus', '.flac', '.wav', '.mp4', '.webm', '.mov']); 136 const apMediaUpload = multer({ 137 storage: multer.diskStorage({ 138 destination: (req, file, cb) => cb(null, AP_MEDIA_DIR), 139 filename: (req, file, cb) => cb(null, `${randomUUID()}${path.extname(file.originalname || '').toLowerCase()}`), 140 }), 141 limits: { fileSize: 32 * 1024 * 1024 }, 142 fileFilter: (req, file, cb) => { 143 const ext = path.extname(file.originalname || '').toLowerCase(); 144 if (!AP_MEDIA_EXT.has(ext)) return cb(new Error('Media must be an image, audio or video file')); 145 cb(null, true); 146 }, 147 }); 148 router.post('/ap/users/:slug/uploadMedia', (req, res) => { 149 const auth = OAuth.verifyBearer(req.headers.authorization); 150 if (!auth || auth.site.slug !== req.params.slug) return res.status(403).end(); 151 apMediaUpload.single('file')(req, res, (err) => { 152 if (err) return res.status(400).json({ error: err.message }); 153 if (!req.file) return res.status(400).json({ error: 'No file' }); 154 const mime = String(req.file.mimetype || ''); 155 if (!/^(image|audio|video)\//.test(mime)) { 156 try { fs.unlinkSync(req.file.path); } catch { /* best effort */ } 157 return res.status(400).json({ error: 'Media must be an image, audio or video file' }); 158 } 159 res.status(201).json({ 160 url: '/media/reply-media/' + req.file.filename, 161 mediaType: mime, 162 name: String(req.file.originalname || '').slice(0, 120), 163 }); 164 }); 165 }); 166 120 167 // ── Followers (count-only public, full for the owner) ───────────── 121 168 // A C2S bearer scoped to this site (the account owner) gets the real actor
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)