- GET /api/notification-channels: requireAuth(), admin sees all rows with owner_email JOIN, non-admin sees global-only - GET accepts ?owner=global|personal|all filter parameter - POST /api/notification-channels: requireAdmin(); preserves all four channel_type values (teams/telegram/ntfy/webhook); adds owner_user_id column - [id] routes: requireAuth() + per-row authorization (isAdmin || isOwner); global rows require admin - Admin channels page: Owner badge (Global vs Personal: email), Show filter select, disclaimer text for personal channels
84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
/**
|
|
* Notification Channels API — list and create channels.
|
|
* GET: requires auth; admins see all rows (with owner_email); non-admins see global-only.
|
|
* POST: requires admin.
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import { requireAuth, requireAdmin } from '@/lib/auth-utils';
|
|
import { postgresClient } from '@/lib/services/postgres-client';
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const { session, error } = await requireAuth();
|
|
if (error) return error;
|
|
|
|
const userRole = (session!.user as { role?: string }).role ?? 'user';
|
|
const isAdmin = userRole === 'admin' || userRole === 'super-admin';
|
|
|
|
try {
|
|
if (isAdmin) {
|
|
const ownerFilter = request.nextUrl.searchParams.get('owner') ?? 'all';
|
|
|
|
let whereSql = '';
|
|
if (ownerFilter === 'global') {
|
|
whereSql = 'WHERE nc.owner_user_id IS NULL';
|
|
} else if (ownerFilter === 'personal') {
|
|
whereSql = 'WHERE nc.owner_user_id IS NOT NULL';
|
|
}
|
|
|
|
const result = await postgresClient.query(
|
|
`SELECT nc.*, u.email AS owner_email
|
|
FROM notification_channels nc
|
|
LEFT JOIN "user" u ON u.id = nc.owner_user_id
|
|
${whereSql}
|
|
ORDER BY nc.owner_user_id NULLS FIRST, nc.name`
|
|
);
|
|
return NextResponse.json({ data: result.rows, total: result.rows.length });
|
|
} else {
|
|
// Non-admins: only global channels
|
|
const result = await postgresClient.query(
|
|
`SELECT * FROM notification_channels
|
|
WHERE owner_user_id IS NULL
|
|
ORDER BY name`
|
|
);
|
|
return NextResponse.json({ data: result.rows, total: result.rows.length });
|
|
}
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
console.error('GET /api/notification-channels failed:', err);
|
|
return NextResponse.json({ error: msg }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
const { error } = await requireAdmin();
|
|
if (error) return error;
|
|
|
|
try {
|
|
const body = await request.json();
|
|
const { name, channel_type, config, is_active, owner_user_id } = body;
|
|
|
|
if (!name || !channel_type) {
|
|
return NextResponse.json({ error: 'name and channel_type are required' }, { status: 400 });
|
|
}
|
|
|
|
// LOW 12 explicit acceptance: all four channel_type values remain accepted
|
|
const validTypes = ['teams', 'telegram', 'ntfy', 'webhook'];
|
|
if (!validTypes.includes(channel_type)) {
|
|
return NextResponse.json({ error: `channel_type must be one of: ${validTypes.join(', ')}` }, { status: 400 });
|
|
}
|
|
|
|
const result = await postgresClient.query(
|
|
`INSERT INTO notification_channels (name, channel_type, config, is_active, owner_user_id)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
RETURNING *`,
|
|
[name, channel_type, JSON.stringify(config || {}), is_active ?? true, owner_user_id ?? null]
|
|
);
|
|
|
|
return NextResponse.json(result.rows[0], { status: 201 });
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
console.error('POST /api/notification-channels failed:', err);
|
|
return NextResponse.json({ error: msg }, { status: 500 });
|
|
}
|
|
}
|