← back to Norma
app/api/social/my-posts/route.ts
96 lines
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { requireRole } from '@/lib/require-role';
export const dynamic = 'force-dynamic';
const DEFAULT_DAYS = 90;
const MAX_DAYS = 365;
export async function GET(request: NextRequest) {
const auth = requireRole(request, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
const { searchParams } = new URL(request.url);
const daysParam = parseInt(searchParams.get('days') ?? String(DEFAULT_DAYS), 10);
const days = Number.isFinite(daysParam) && daysParam > 0 ? Math.min(daysParam, MAX_DAYS) : DEFAULT_DAYS;
const platform = (searchParams.get('platform') ?? 'all').toLowerCase();
const status = (searchParams.get('status') ?? 'all').toLowerCase();
const conds: string[] = [
`COALESCE(p.published_at, s.scheduled_at, p.created_at) >= (NOW() - ($1 || ' days')::interval)`,
];
const vals: unknown[] = [String(days)];
let idx = 2;
if (platform !== 'all') {
conds.push(`p.platform = $${idx++}`);
vals.push(platform);
}
if (status !== 'all') {
conds.push(`p.status = $${idx++}`);
vals.push(status);
}
const sql = `
SELECT
p.id,
p.platform,
p.post_type,
p.body,
p.media_urls,
p.link_url,
p.hashtags,
p.platform_url,
p.status,
p.published_at,
p.created_at,
s.scheduled_at,
s.status AS schedule_status,
COALESCE(p.likes, 0) AS likes,
COALESCE(p.comments, 0) AS comments,
COALESCE(p.shares, 0) AS shares,
COALESCE(p.views, 0) AS views,
p.error_message,
a.account_name,
a.display_name,
a.avatar_url
FROM social_posts p
LEFT JOIN social_accounts a ON a.id = p.account_id
LEFT JOIN LATERAL (
SELECT scheduled_at, status
FROM social_schedule
WHERE post_id = p.id
ORDER BY scheduled_at DESC
LIMIT 1
) s ON true
WHERE ${conds.join(' AND ')}
ORDER BY COALESCE(p.published_at, s.scheduled_at, p.created_at) DESC
LIMIT 500
`;
const result = await query(sql, vals);
const counts = await query(
`SELECT
COUNT(*) FILTER (WHERE p.status = 'published')::int AS published,
COUNT(*) FILTER (WHERE p.status = 'scheduled' OR s.scheduled_at IS NOT NULL)::int AS scheduled,
COUNT(*) FILTER (WHERE p.status = 'failed')::int AS failed,
COUNT(*) FILTER (WHERE p.status = 'draft')::int AS draft,
COUNT(*)::int AS total
FROM social_posts p
LEFT JOIN LATERAL (
SELECT scheduled_at, status FROM social_schedule
WHERE post_id = p.id ORDER BY scheduled_at DESC LIMIT 1
) s ON true
WHERE COALESCE(p.published_at, s.scheduled_at, p.created_at) >= (NOW() - ($1 || ' days')::interval)`,
[String(days)],
);
return NextResponse.json({
posts: result.rows,
counts: counts.rows[0] ?? { published: 0, scheduled: 0, failed: 0, draft: 0, total: 0 },
range: { days, platform, status },
});
}