initial commit
This commit is contained in:
261
app/api/topic/[id]/route.ts
Normal file
261
app/api/topic/[id]/route.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import TopicModel, { transformToTopic } from '@/models/topic'
|
||||
import { authMiddleware } from '@/lib/auth-middleware'
|
||||
import { uploadFile, moveToPermStorage, getFileUrl, generateUniqueFilename } from '@/lib/file-vault'
|
||||
import { z } from 'zod'
|
||||
|
||||
// Validation schema for topic updates
|
||||
const topicUpdateSchema = z.object({
|
||||
title: z.string().min(1).max(100).optional(),
|
||||
author: z.string().min(1).optional(),
|
||||
excerpt: z.string().min(1).max(200).optional(),
|
||||
content: z.string().min(1).optional(),
|
||||
contentRTE: z.unknown().optional(),
|
||||
contentImages: z.array(z.string()).optional(),
|
||||
tags: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string(),
|
||||
})
|
||||
)
|
||||
.min(1)
|
||||
.optional(),
|
||||
featured: z.boolean().optional(),
|
||||
isDraft: z.boolean().optional(),
|
||||
coverImage: z.string().url().optional(),
|
||||
coverImageKey: z.string().optional(),
|
||||
})
|
||||
|
||||
// GET /api/topic/[id] - Get topic by ID
|
||||
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
const { id } = await params
|
||||
|
||||
const topic = await TopicModel.findOne({ id }).lean()
|
||||
|
||||
if (!topic) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Topic not found', code: 'NOT_FOUND' },
|
||||
},
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// Check if topic is draft and user is not the owner
|
||||
if (topic.isDraft) {
|
||||
const user = await authMiddleware(request)
|
||||
if (!user || user.id !== topic.authorId) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Topic not found', code: 'NOT_FOUND' },
|
||||
},
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const transformedTopic = transformToTopic(topic)
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
data: transformedTopic,
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Error fetching topic:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Failed to fetch topic', code: 'SERVER_ERROR' },
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// PUT /api/topic/[id] - Update topic by ID
|
||||
export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
// Authentication required for updating topics
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Authentication required', code: 'AUTH_REQUIRED' },
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
|
||||
// Find the topic first
|
||||
const existingTopic = await TopicModel.findOne({ id })
|
||||
|
||||
if (!existingTopic) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Topic not found', code: 'NOT_FOUND' },
|
||||
},
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// Check ownership - users can only update their own topics
|
||||
if (existingTopic.authorId !== user.id) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'You can only edit your own topics', code: 'FORBIDDEN' },
|
||||
},
|
||||
{ status: 403 }
|
||||
)
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
|
||||
// Validate request body
|
||||
const validatedData = topicUpdateSchema.parse(body)
|
||||
|
||||
console.log('Updating topic for user:', user.id, 'Topic ID:', id)
|
||||
|
||||
try {
|
||||
// Update the topic with new data
|
||||
Object.assign(existingTopic, validatedData)
|
||||
existingTopic.publishedAt = Date.now() // Update timestamp
|
||||
|
||||
const updatedTopic = await existingTopic.save()
|
||||
|
||||
console.log('Topic updated successfully:', updatedTopic.id)
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
data: updatedTopic,
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Failed to update topic:', error)
|
||||
|
||||
if (error.code === 11000) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'A topic with this slug already exists', code: 'DUPLICATE_SLUG' },
|
||||
},
|
||||
{ status: 409 }
|
||||
)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating topic:', error)
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Validation failed',
|
||||
code: 'VALIDATION_ERROR',
|
||||
details: error.issues,
|
||||
},
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Failed to update topic', code: 'SERVER_ERROR' },
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/topic/[id] - Delete topic by ID
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
// Authentication required for deleting topics
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Authentication required', code: 'AUTH_REQUIRED' },
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
|
||||
// Find the topic first
|
||||
const existingTopic = await TopicModel.findOne({ id })
|
||||
|
||||
if (!existingTopic) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Topic not found', code: 'NOT_FOUND' },
|
||||
},
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// Check ownership - users can only delete their own topics
|
||||
if (existingTopic.authorId !== user.id) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'You can only delete your own topics', code: 'FORBIDDEN' },
|
||||
},
|
||||
{ status: 403 }
|
||||
)
|
||||
}
|
||||
|
||||
console.log('Deleting topic for user:', user.id, 'Topic ID:', id)
|
||||
|
||||
try {
|
||||
await TopicModel.deleteOne({ id })
|
||||
|
||||
console.log('Topic deleted successfully:', id)
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
message: 'Topic deleted successfully',
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Failed to delete topic:', error)
|
||||
throw error
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting topic:', error)
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Failed to delete topic', code: 'SERVER_ERROR' },
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
427
app/api/topic/route.ts
Normal file
427
app/api/topic/route.ts
Normal file
@@ -0,0 +1,427 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { randomUUID } from 'crypto'
|
||||
import TopicModel from '@/models/topic'
|
||||
import { authMiddleware } from '@/lib/auth-middleware'
|
||||
import { uploadFile, moveToPermStorage, getFileUrl, generateUniqueFilename } from '@/lib/file-vault'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
console.log('Starting topic post processing')
|
||||
|
||||
// Debug authentication data
|
||||
const authHeader = request.headers.get('authorization')
|
||||
const accessTokenCookie = request.cookies.get('accessToken')?.value
|
||||
console.log('🔍 Auth debug:', {
|
||||
hasAuthHeader: !!authHeader,
|
||||
authHeaderPrefix: authHeader?.substring(0, 20),
|
||||
hasAccessTokenCookie: !!accessTokenCookie,
|
||||
cookiePrefix: accessTokenCookie?.substring(0, 20),
|
||||
allCookies: Object.fromEntries(
|
||||
request.cookies.getAll().map((c) => [c.name, c.value?.substring(0, 20)])
|
||||
),
|
||||
})
|
||||
|
||||
// Authentication required
|
||||
const user = await authMiddleware(request)
|
||||
console.log('🔍 Auth result:', { user: user ? { id: user.id, email: user.email } : null })
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Authentication required', code: 'AUTH_REQUIRED' },
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const formData = await request.formData()
|
||||
const coverImage = formData.get('coverImage') as File
|
||||
const topicId = request.nextUrl.searchParams.get('topicId')
|
||||
console.log('Form data received', { topicId, hasCoverImage: !!coverImage })
|
||||
|
||||
// Get other form data with proper validation
|
||||
const title = formData.get('title') as string
|
||||
const author = formData.get('author') as string
|
||||
const excerpt = formData.get('excerpt') as string
|
||||
const content = formData.get('content') as string
|
||||
const contentRTE = formData.get('contentRTE') as string
|
||||
const featured = formData.get('featured') === 'true'
|
||||
// Get the LAST isDraft value (in case of duplicates)
|
||||
const allIsDraftValues = formData.getAll('isDraft')
|
||||
console.log('🐛 All isDraft values in FormData:', allIsDraftValues)
|
||||
const isDraft = allIsDraftValues[allIsDraftValues.length - 1] === 'true'
|
||||
console.log('🐛 Final isDraft value:', isDraft)
|
||||
|
||||
// Parse JSON fields safely (like dev-portfolio)
|
||||
let contentImages: string[] = []
|
||||
let tags: any[] = []
|
||||
|
||||
try {
|
||||
const contentImagesStr = formData.get('contentImages') as string
|
||||
contentImages = contentImagesStr ? JSON.parse(contentImagesStr) : []
|
||||
} catch (error) {
|
||||
console.warn('Failed to parse contentImages, using empty array:', error)
|
||||
contentImages = []
|
||||
}
|
||||
|
||||
try {
|
||||
const tagsStr = formData.get('tags') as string
|
||||
tags = tagsStr ? JSON.parse(tagsStr) : []
|
||||
} catch (error) {
|
||||
console.warn('Failed to parse tags, using empty array:', error)
|
||||
tags = []
|
||||
}
|
||||
|
||||
// Validate required fields (like dev-portfolio)
|
||||
if (!title?.trim()) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: { message: 'Title is required', code: 'VALIDATION_ERROR' } },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
if (!excerpt?.trim()) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: { message: 'Excerpt is required', code: 'VALIDATION_ERROR' } },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
if (!content?.trim()) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: { message: 'Content is required', code: 'VALIDATION_ERROR' } },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
if (!Array.isArray(tags) || tags.length === 0) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'At least one tag is required', code: 'VALIDATION_ERROR' },
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const targetTopicId = topicId ? topicId : randomUUID()
|
||||
|
||||
// For existing topic, fetch the current data
|
||||
let existingTopic = null
|
||||
|
||||
if (topicId) {
|
||||
existingTopic = await TopicModel.findOne({ id: topicId })
|
||||
if (!existingTopic) {
|
||||
return NextResponse.json({ success: false, message: 'Topic not found' }, { status: 404 })
|
||||
}
|
||||
}
|
||||
|
||||
// Handle cover image upload exactly like dev-portfolio
|
||||
let tempImageResult = null
|
||||
if (coverImage && coverImage instanceof File) {
|
||||
try {
|
||||
console.log('🖼️ Processing cover image upload:', {
|
||||
name: coverImage.name,
|
||||
size: coverImage.size,
|
||||
type: coverImage.type,
|
||||
})
|
||||
|
||||
// Upload using external API
|
||||
try {
|
||||
// Convert file to buffer
|
||||
const bytes = await coverImage.arrayBuffer()
|
||||
const buffer = Buffer.from(bytes)
|
||||
|
||||
console.log('📤 Buffer created, starting upload...')
|
||||
|
||||
// Upload directly to external API (no temp storage needed)
|
||||
const uploadResult = await uploadFile(buffer, coverImage.name, coverImage.type, user.id)
|
||||
const coverImageUrl = uploadResult.url
|
||||
const permanentPath = uploadResult.url
|
||||
|
||||
tempImageResult = {
|
||||
coverImage: coverImageUrl,
|
||||
coverImageKey: permanentPath,
|
||||
}
|
||||
|
||||
console.log('✅ Cover image uploaded successfully:', { permanentPath })
|
||||
} catch (uploadError) {
|
||||
console.error('❌ Cover image upload failed:', uploadError)
|
||||
throw new Error(`Failed to upload cover image: ${uploadError.message}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing cover image:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const dataToSave = {
|
||||
id: targetTopicId,
|
||||
publishedAt: Date.now(),
|
||||
title,
|
||||
author,
|
||||
authorId: user.id, // Add user ownership
|
||||
excerpt,
|
||||
content,
|
||||
contentRTE: contentRTE ? JSON.parse(contentRTE) : [],
|
||||
contentImages,
|
||||
featured,
|
||||
tags,
|
||||
isDraft,
|
||||
// Use uploaded image or existing image for updates, require image for new topics like dev-portfolio
|
||||
coverImage:
|
||||
tempImageResult?.coverImage ||
|
||||
existingTopic?.coverImage ||
|
||||
'https://via.placeholder.com/800x400',
|
||||
coverImageKey: tempImageResult?.coverImageKey || existingTopic?.coverImageKey || '',
|
||||
}
|
||||
console.log('Preparing to save topic data', { targetTopicId })
|
||||
|
||||
let savedArticle
|
||||
try {
|
||||
if (existingTopic) {
|
||||
// Update existing topic
|
||||
console.log('Updating existing topic')
|
||||
Object.assign(existingTopic, dataToSave)
|
||||
savedArticle = await existingTopic.save()
|
||||
} else {
|
||||
// Create new topic
|
||||
console.log('Creating new topic')
|
||||
const newArticle = new TopicModel(dataToSave)
|
||||
savedArticle = await newArticle.save()
|
||||
}
|
||||
console.log('Topic saved successfully', { topicId: savedArticle.id })
|
||||
} catch (error) {
|
||||
console.error('Failed to save topic', { error })
|
||||
throw error
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
data: savedArticle,
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Error processing topic post:', {
|
||||
error,
|
||||
message: error.message,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Failed to process topic post', error: error.message },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const page = parseInt(searchParams.get('page') || '1')
|
||||
const limit = parseInt(searchParams.get('limit') || '10')
|
||||
const featured = searchParams.get('featured') === 'true'
|
||||
const isDraft = searchParams.get('isDraft')
|
||||
const search = searchParams.get('search')
|
||||
const tag = searchParams.get('tag')
|
||||
|
||||
// Build query (like dev-portfolio)
|
||||
const query: any = {}
|
||||
|
||||
// Featured filter
|
||||
if (featured) query.featured = true
|
||||
|
||||
// Draft filter (only if explicitly set)
|
||||
if (isDraft !== null) query.isDraft = isDraft === 'true'
|
||||
|
||||
// Search functionality (like dev-portfolio)
|
||||
if (search) {
|
||||
query.$or = [
|
||||
{ title: { $regex: search, $options: 'i' } },
|
||||
{ excerpt: { $regex: search, $options: 'i' } },
|
||||
{ content: { $regex: search, $options: 'i' } },
|
||||
]
|
||||
}
|
||||
|
||||
// Tag filtering (like dev-portfolio)
|
||||
if (tag) {
|
||||
query['tags.name'] = { $regex: tag, $options: 'i' }
|
||||
}
|
||||
|
||||
const skip = (page - 1) * limit
|
||||
|
||||
const [topics, total] = await Promise.all([
|
||||
TopicModel.find(query).sort({ publishedAt: -1 }).skip(skip).limit(limit),
|
||||
TopicModel.countDocuments(query),
|
||||
])
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
topics,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
pages: Math.ceil(total / limit),
|
||||
},
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching topics:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Failed to fetch topics', code: 'SERVER_ERROR' },
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// PUT /api/topic - Update existing topic (like dev-portfolio)
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
console.log('Starting topic update processing')
|
||||
|
||||
// Authentication required
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Authentication required', code: 'AUTH_REQUIRED' },
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const formData = await request.formData()
|
||||
const topicId = formData.get('topicId') as string
|
||||
|
||||
if (!topicId) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Topic ID is required for updates', code: 'MISSING_TOPIC_ID' },
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Find existing topic and check ownership
|
||||
const existingTopic = await TopicModel.findOne({ id: topicId })
|
||||
if (!existingTopic) {
|
||||
return NextResponse.json({ success: false, message: 'Topic not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
// Check authorization - user can only update their own topics
|
||||
if (existingTopic.authorId !== user.id) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Not authorized to update this topic', code: 'UNAUTHORIZED' },
|
||||
},
|
||||
{ status: 403 }
|
||||
)
|
||||
}
|
||||
|
||||
// Use the same logic as POST but for updates
|
||||
// This reuses the form processing logic from POST
|
||||
const url = new URL(request.url)
|
||||
url.searchParams.set('topicId', topicId)
|
||||
|
||||
// Create new request for POST handler with topicId parameter
|
||||
const updateRequest = new NextRequest(url.toString(), {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: request.headers,
|
||||
})
|
||||
|
||||
return POST(updateRequest)
|
||||
} catch (error) {
|
||||
console.error('Error updating topic post:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Failed to update topic post', error: error.message },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/topic - Delete topic (like dev-portfolio)
|
||||
export async function DELETE(request: NextRequest) {
|
||||
try {
|
||||
console.log('Starting topic deletion')
|
||||
|
||||
// Authentication required
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Authentication required', code: 'AUTH_REQUIRED' },
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const topicId = searchParams.get('id')
|
||||
|
||||
if (!topicId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: { message: 'Topic ID is required', code: 'MISSING_TOPIC_ID' } },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Find existing topic and check ownership
|
||||
const existingTopic = await TopicModel.findOne({ id: topicId })
|
||||
if (!existingTopic) {
|
||||
return NextResponse.json({ success: false, message: 'Topic not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
// Check authorization - user can only delete their own topics
|
||||
if (existingTopic.authorId !== user.id) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Not authorized to delete this topic', code: 'UNAUTHORIZED' },
|
||||
},
|
||||
{ status: 403 }
|
||||
)
|
||||
}
|
||||
|
||||
console.log('Deleting topic:', { topicId, title: existingTopic.title?.substring(0, 50) })
|
||||
|
||||
// Delete the topic
|
||||
await TopicModel.deleteOne({ id: topicId })
|
||||
|
||||
console.log('Topic deleted successfully:', topicId)
|
||||
|
||||
// TODO: Implement image cleanup like dev-portfolio
|
||||
// if (existingTopic.coverImageKey && existingTopic.coverImageKey !== 'placeholder-key') {
|
||||
// await deleteFile(existingTopic.coverImageKey)
|
||||
// }
|
||||
// if (existingTopic.contentImages && existingTopic.contentImages.length > 0) {
|
||||
// for (const imagePath of existingTopic.contentImages) {
|
||||
// await deleteFile(imagePath)
|
||||
// }
|
||||
// }
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
message: 'Topic deleted successfully',
|
||||
data: { id: topicId },
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Error deleting topic:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Failed to delete topic', error: error.message },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
78
app/api/topic/slug/[slug]/route.ts
Normal file
78
app/api/topic/slug/[slug]/route.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import TopicModel, { transformToTopic } from '@/models/topic'
|
||||
import { authMiddleware } from '@/lib/auth-middleware'
|
||||
|
||||
// GET /api/topic/slug/[slug] - Get topic by slug (for public viewing)
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ slug: string }> }
|
||||
) {
|
||||
try {
|
||||
const { slug } = await params
|
||||
|
||||
if (!slug) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Slug parameter is required', code: 'MISSING_SLUG' },
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const topic = await TopicModel.findOne({ slug }).lean()
|
||||
|
||||
if (!topic) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Topic not found', code: 'NOT_FOUND' },
|
||||
},
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// Check if topic is draft and user is not the owner
|
||||
if (topic.isDraft) {
|
||||
const user = await authMiddleware(request)
|
||||
if (!user || user.id !== topic.authorId) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Topic not found', code: 'NOT_FOUND' },
|
||||
},
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const transformedTopic = transformToTopic(topic)
|
||||
|
||||
if (!transformedTopic) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Failed to process topic data', code: 'PROCESSING_ERROR' },
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
data: transformedTopic,
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Error fetching topic by slug:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Failed to fetch topic', code: 'SERVER_ERROR' },
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user