Auto-generate OBS source names and implement team-based stream organization #5
3 changed files with 24 additions and 40 deletions
|
@ -63,8 +63,13 @@ async function fetchTeamInfo(teamId: number) {
|
||||||
|
|
||||||
import { validateStreamInput } from '../../../lib/security';
|
import { validateStreamInput } from '../../../lib/security';
|
||||||
|
|
||||||
|
// Generate OBS source name from stream name
|
||||||
|
function generateOBSSourceName(streamName: string): string {
|
||||||
|
return streamName.toLowerCase().replace(/\s+/g, '_') + '_twitch';
|
||||||
|
}
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
let name: string, obs_source_name: string, url: string, team_id: number;
|
let name: string, url: string, team_id: number, obs_source_name: string;
|
||||||
|
|
||||||
// Parse and validate request body
|
// Parse and validate request body
|
||||||
try {
|
try {
|
||||||
|
@ -78,7 +83,10 @@ export async function POST(request: NextRequest) {
|
||||||
}, { status: 400 });
|
}, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
({ name, obs_source_name, url, team_id } = validation.data!);
|
({ name, url, team_id } = validation.data!);
|
||||||
|
|
||||||
|
// Auto-generate OBS source name from stream name
|
||||||
|
obs_source_name = generateOBSSourceName(name);
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 });
|
return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 });
|
||||||
|
|
|
@ -17,7 +17,6 @@ interface Stream {
|
||||||
export default function AddStream() {
|
export default function AddStream() {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
obs_source_name: '',
|
|
||||||
twitch_username: '',
|
twitch_username: '',
|
||||||
team_id: null,
|
team_id: null,
|
||||||
});
|
});
|
||||||
|
@ -125,9 +124,6 @@ export default function AddStream() {
|
||||||
errors.name = 'Stream name must be at least 2 characters';
|
errors.name = 'Stream name must be at least 2 characters';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.obs_source_name.trim()) {
|
|
||||||
errors.obs_source_name = 'OBS source name is required';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!formData.twitch_username.trim()) {
|
if (!formData.twitch_username.trim()) {
|
||||||
errors.twitch_username = 'Twitch username is required';
|
errors.twitch_username = 'Twitch username is required';
|
||||||
|
@ -162,7 +158,7 @@ export default function AddStream() {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showSuccess('Stream Added', `"${formData.name}" has been added successfully`);
|
showSuccess('Stream Added', `"${formData.name}" has been added successfully`);
|
||||||
setFormData({ name: '', obs_source_name: '', twitch_username: '', team_id: null });
|
setFormData({ name: '', twitch_username: '', team_id: null });
|
||||||
setValidationErrors({});
|
setValidationErrors({});
|
||||||
fetchData();
|
fetchData();
|
||||||
} else {
|
} else {
|
||||||
|
@ -214,28 +210,6 @@ export default function AddStream() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* OBS Source Name */}
|
|
||||||
<div>
|
|
||||||
<label className="block text-white font-semibold mb-3">
|
|
||||||
OBS Source Name
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="obs_source_name"
|
|
||||||
value={formData.obs_source_name}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
className={`input ${
|
|
||||||
validationErrors.obs_source_name ? 'border-red-500/60 bg-red-500/10' : ''
|
|
||||||
}`}
|
|
||||||
placeholder="Enter the exact source name from OBS"
|
|
||||||
/>
|
|
||||||
{validationErrors.obs_source_name && (
|
|
||||||
<div className="text-red-400 text-sm mt-2">
|
|
||||||
{validationErrors.obs_source_name}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Twitch Username */}
|
{/* Twitch Username */}
|
||||||
<div>
|
<div>
|
||||||
|
@ -317,11 +291,19 @@ export default function AddStream() {
|
||||||
return (
|
return (
|
||||||
<div key={stream.id} className="glass p-4">
|
<div key={stream.id} className="glass p-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center">
|
||||||
<div className="w-8 h-8 bg-gradient-to-br from-green-500 to-blue-600 rounded-lg flex items-center justify-center text-white font-bold text-sm">
|
<div
|
||||||
|
className="bg-gradient-to-br from-green-500 to-blue-600 rounded-lg flex items-center justify-center text-white font-bold flex-shrink-0"
|
||||||
|
style={{
|
||||||
|
width: '64px',
|
||||||
|
height: '64px',
|
||||||
|
fontSize: '24px',
|
||||||
|
marginRight: '16px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
{stream.name.charAt(0).toUpperCase()}
|
{stream.name.charAt(0).toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0 flex-1">
|
||||||
<div className="font-semibold text-white">{stream.name}</div>
|
<div className="font-semibold text-white">{stream.name}</div>
|
||||||
<div className="text-sm text-white/60">OBS: {stream.obs_source_name}</div>
|
<div className="text-sm text-white/60">OBS: {stream.obs_source_name}</div>
|
||||||
<div className="text-sm text-white/60">Team: {team?.name || 'Unknown'}</div>
|
<div className="text-sm text-white/60">Team: {team?.name || 'Unknown'}</div>
|
||||||
|
@ -329,12 +311,13 @@ export default function AddStream() {
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right space-y-2">
|
<div className="text-right space-y-2">
|
||||||
<div className="text-sm text-white/40">ID: {stream.id}</div>
|
<div className="text-sm text-white/40">ID: {stream.id}</div>
|
||||||
<div className="flex gap-2 justify-end">
|
<div className="flex justify-end">
|
||||||
<a
|
<a
|
||||||
href={stream.url}
|
href={stream.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="btn btn-primary text-sm"
|
className="btn btn-primary text-sm"
|
||||||
|
style={{ marginRight: '8px' }}
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||||
|
|
|
@ -38,7 +38,6 @@ export function sanitizeString(input: string, maxLength: number = 100): string {
|
||||||
// Validation schemas
|
// Validation schemas
|
||||||
export interface StreamInput {
|
export interface StreamInput {
|
||||||
name: string;
|
name: string;
|
||||||
obs_source_name: string;
|
|
||||||
url: string;
|
url: string;
|
||||||
team_id: number;
|
team_id: number;
|
||||||
}
|
}
|
||||||
|
@ -58,11 +57,6 @@ export function validateStreamInput(input: unknown): { valid: boolean; errors: s
|
||||||
errors.push('Name must be 100 characters or less');
|
errors.push('Name must be 100 characters or less');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.obs_source_name || typeof data.obs_source_name !== 'string') {
|
|
||||||
errors.push('OBS source name is required and must be a string');
|
|
||||||
} else if (data.obs_source_name.length > 100) {
|
|
||||||
errors.push('OBS source name must be 100 characters or less');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.url || typeof data.url !== 'string') {
|
if (!data.url || typeof data.url !== 'string') {
|
||||||
errors.push('URL is required and must be a string');
|
errors.push('URL is required and must be a string');
|
||||||
|
@ -83,7 +77,6 @@ export function validateStreamInput(input: unknown): { valid: boolean; errors: s
|
||||||
errors: [],
|
errors: [],
|
||||||
data: {
|
data: {
|
||||||
name: sanitizeString(data.name as string),
|
name: sanitizeString(data.name as string),
|
||||||
obs_source_name: sanitizeString(data.obs_source_name as string),
|
|
||||||
url: data.url as string,
|
url: data.url as string,
|
||||||
team_id: data.team_id as number,
|
team_id: data.team_id as number,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue