mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 17:46:06 +00:00
chore: clean up lint/type issues and tighten comment media URL validation
This commit is contained in:
@@ -2,26 +2,12 @@ import Link from 'next/link';
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Globe,
|
||||
Lock,
|
||||
UserPlus,
|
||||
} from 'lucide-react';
|
||||
import { GuestGate } from '@/components/guest-gate';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { db } from '@/lib/db';
|
||||
import { ProjectContentClient } from './project-content-client';
|
||||
|
||||
function VisibilityIcon({ visibility }: { visibility: string }) {
|
||||
switch (visibility) {
|
||||
case 'PUBLIC':
|
||||
return <Globe className="h-3.5 w-3.5" />;
|
||||
case 'INVITE':
|
||||
return <UserPlus className="h-3.5 w-3.5" />;
|
||||
default:
|
||||
return <Lock className="h-3.5 w-3.5" />;
|
||||
}
|
||||
}
|
||||
|
||||
function formatDuration(seconds: number | null): string {
|
||||
if (!seconds) return '0:00';
|
||||
const totalSeconds = Math.floor(seconds);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { ArrowLeft, Copy, Check, Loader2, UserPlus, Trash2, Share2, Globe, Lock, Mail, X } from 'lucide-react';
|
||||
import { ArrowLeft, Copy, Check, Loader2, UserPlus, Share2, Globe, Lock, Mail, X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Bell, Send, Mail, CheckCircle2, AlertCircle, Loader2, ExternalLink, Globe } from 'lucide-react';
|
||||
import { Bell, Send, Mail, CheckCircle2, AlertCircle, Loader2, Globe } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextRequest } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { checkRateLimit, getClientIp, rateLimitHeaders, RATE_LIMIT_CONFIGS } from '@/lib/rate-limit';
|
||||
import { apiErrors, successResponse, ErrorCode, withCacheControl } from '@/lib/api-response';
|
||||
import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
|
||||
@@ -107,7 +107,8 @@ export async function GET(request: NextRequest, { params }: RouteParams) {
|
||||
}
|
||||
|
||||
// Strip internal project data from response
|
||||
const { version: _version, ...commentData } = comment;
|
||||
const commentData = { ...comment } as Omit<typeof comment, 'version'> & { version?: unknown };
|
||||
delete commentData.version;
|
||||
const response = successResponse(commentData);
|
||||
return withCacheControl(response, 'private, no-cache');
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { NextRequest } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { validateOptionalUrl } from '@/lib/validation';
|
||||
import { rateLimit } from '@/lib/rate-limit';
|
||||
import { notifyProjectOwner } from '@/lib/notifications';
|
||||
import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response';
|
||||
|
||||
type RouteParams = { params: Promise<{ versionId: string }> };
|
||||
const SAFE_IMAGE_PATH = /^\/api\/upload\/image\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[a-z0-9]+$/i;
|
||||
const SAFE_AUDIO_PATH = /^\/api\/upload\/audio\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[a-z0-9]+$/i;
|
||||
|
||||
// GET /api/versions/[versionId]/comments
|
||||
export async function GET(request: NextRequest, { params }: RouteParams) {
|
||||
@@ -218,19 +219,12 @@ export async function POST(request: NextRequest, { params }: RouteParams) {
|
||||
return apiErrors.badRequest('Guest name is required for guest comments');
|
||||
}
|
||||
|
||||
// Validate voice URL uses safe scheme (allow internal /api/ paths)
|
||||
if (voiceUrl && !voiceUrl.startsWith('/api/')) {
|
||||
const voiceUrlError = validateOptionalUrl(voiceUrl, 'Voice URL');
|
||||
if (voiceUrlError) {
|
||||
return apiErrors.badRequest(voiceUrlError);
|
||||
}
|
||||
if (voiceUrl && !SAFE_AUDIO_PATH.test(voiceUrl)) {
|
||||
return apiErrors.badRequest('Voice URL must reference an uploaded audio file');
|
||||
}
|
||||
|
||||
if (imageUrl && !imageUrl.startsWith('/api/')) {
|
||||
const imageUrlError = validateOptionalUrl(imageUrl, 'Image URL');
|
||||
if (imageUrlError) {
|
||||
return apiErrors.badRequest(imageUrlError);
|
||||
}
|
||||
if (imageUrl && !SAFE_IMAGE_PATH.test(imageUrl)) {
|
||||
return apiErrors.badRequest('Image URL must reference an uploaded image file');
|
||||
}
|
||||
|
||||
const comment = await db.comment.create({
|
||||
|
||||
Reference in New Issue
Block a user