const express = require('express'); const db = require('../db'); const { requireAuth } = require('../auth'); const { resolveActingProfile } = require('../lib/account'); const { loadPosts, loadCommentsForPost } = require('../lib/queries'); const { canPost } = require('../lib/permissions'); const { isAllowedExternalUrl } = require('../lib/media'); const router = express.Router(); router.use(requireAuth); // Helfer: aktives Profil aufloesen (profileId aus body/query) async function acting(req) { const profileId = (req.body && req.body.profileId) || req.query.profileId; return resolveActingProfile(req.account, profileId); } // GET /api/feed?type=home|advertising&profileId= router.get('/feed', async (req, res) => { const { profile } = await acting(req); const type = req.query.type === 'advertising' ? 'advertising' : req.query.type === 'home' ? 'home' : null; const posts = await loadPosts(profile ? profile.id : 0, type); res.json({ posts }); }); // POST /api/posts {profileId, feedType, body, mediaUrl} router.post('/posts', async (req, res) => { const { profile } = await acting(req); if (!profile) return res.status(400).json({ error: 'no_profile' }); const feedType = String(req.body.feedType || '').trim(); const body = String(req.body.body || '').trim().slice(0, 2000); const mediaUrl = String(req.body.mediaUrl || '').trim().slice(0, 500); if (!body && !mediaUrl) return res.status(400).json({ error: 'empty_post' }); if (feedType !== 'home' && feedType !== 'advertising') return res.status(400).json({ error: 'invalid_feed' }); if (!canPost(profile.profile_type, feedType)) return res.status(403).json({ error: 'not_allowed_here' }); let mediaId = null; if (mediaUrl) { const check = isAllowedExternalUrl(mediaUrl); if (!check.ok) return res.status(400).json({ error: 'media_rejected', reason: check.reason }); mediaId = await db.insert( 'INSERT INTO bleeter_media (owner_profile_id, source_type, url) VALUES (?, ?, ?)', [profile.id, 'external_url', mediaUrl] ); } await db.insert( 'INSERT INTO bleeter_posts (author_profile_id, feed_type, body, media_id) VALUES (?, ?, ?, ?)', [profile.id, feedType, body, mediaId] ); res.json({ ok: true }); }); // DELETE /api/posts/:id {profileId} router.delete('/posts/:id', async (req, res) => { const { profile } = await acting(req); if (!profile) return res.status(400).json({ error: 'no_profile' }); const postId = Number(req.params.id); const post = await db.q1('SELECT author_profile_id FROM bleeter_posts WHERE id = ? AND deleted_at IS NULL AND self_deleted_at IS NULL', [postId]); if (!post) return res.status(404).json({ error: 'not_found' }); if (Number(post.author_profile_id) !== Number(profile.id)) return res.status(403).json({ error: 'not_owner' }); await db.exec('UPDATE bleeter_posts SET self_deleted_at = NOW() WHERE id = ?', [postId]); res.json({ ok: true }); }); // POST /api/posts/:id/like {profileId} -> toggle router.post('/posts/:id/like', async (req, res) => { const { profile } = await acting(req); if (!profile) return res.status(400).json({ error: 'no_profile' }); await toggleLike(profile.id, 'post', Number(req.params.id)); res.json({ ok: true }); }); // POST /api/posts/:id/comments {profileId, body} router.post('/posts/:id/comments', async (req, res) => { const { profile } = await acting(req); if (!profile) return res.status(400).json({ error: 'no_profile' }); const postId = Number(req.params.id); const body = String(req.body.body || '').trim().slice(0, 500); if (!body) return res.status(400).json({ error: 'empty_comment' }); const post = await db.q1('SELECT id FROM bleeter_posts WHERE id = ? AND deleted_at IS NULL AND self_deleted_at IS NULL AND hidden_at IS NULL', [postId]); if (!post) return res.status(404).json({ error: 'not_found' }); await db.insert('INSERT INTO bleeter_comments (post_id, author_profile_id, body) VALUES (?, ?, ?)', [postId, profile.id, body]); const comments = await loadCommentsForPost(postId, profile.id); res.json({ ok: true, comments }); }); // DELETE /api/comments/:id {profileId} router.delete('/comments/:id', async (req, res) => { const { profile } = await acting(req); if (!profile) return res.status(400).json({ error: 'no_profile' }); const commentId = Number(req.params.id); const c = await db.q1('SELECT author_profile_id, post_id FROM bleeter_comments WHERE id = ? AND deleted_at IS NULL AND self_deleted_at IS NULL', [commentId]); if (!c) return res.status(404).json({ error: 'not_found' }); if (Number(c.author_profile_id) !== Number(profile.id)) return res.status(403).json({ error: 'not_owner' }); await db.exec('UPDATE bleeter_comments SET self_deleted_at = NOW() WHERE id = ?', [commentId]); res.json({ ok: true }); }); // POST /api/comments/:id/like {profileId} -> toggle router.post('/comments/:id/like', async (req, res) => { const { profile } = await acting(req); if (!profile) return res.status(400).json({ error: 'no_profile' }); await toggleLike(profile.id, 'comment', Number(req.params.id)); res.json({ ok: true }); }); async function toggleLike(profileId, targetType, targetId) { const existing = await db.q1( 'SELECT id FROM bleeter_likes WHERE profile_id = ? AND target_type = ? AND target_id = ?', [profileId, targetType, targetId] ); if (existing) { await db.exec('DELETE FROM bleeter_likes WHERE id = ?', [existing.id]); } else { await db.exec( 'INSERT IGNORE INTO bleeter_likes (profile_id, target_type, target_id) VALUES (?, ?, ?)', [profileId, targetType, targetId] ); } } module.exports = router;