← back to Dear Bubbe Nextjs
app/api/shopify/blogs/route.ts
249 lines
import { NextRequest, NextResponse } from 'next/server'
import fs from 'fs'
import path from 'path'
// Mock Shopify blogs data - replace with actual Shopify API integration
const MOCK_BLOGS = [
{
id: 'blog_001',
title: '5 Trending Wallcovering Styles for Modern Homes',
createdAt: '2024-11-20T10:30:00Z',
publishedAt: '2024-11-20T12:00:00Z',
tags: ['trends', 'modern', 'interior-design'],
handle: 'trending-wallcovering-styles-modern-homes',
summary: 'Discover the latest wallcovering trends that are transforming modern interior spaces.',
excerpt: 'From bold geometric patterns to subtle textured designs, explore the wallcovering styles that are defining contemporary home decor in 2024.'
},
{
id: 'blog_002',
title: 'How to Choose the Perfect Wallcovering for Your Dining Room',
createdAt: '2024-11-19T14:15:00Z',
publishedAt: '2024-11-19T16:00:00Z',
tags: ['dining-room', 'tips', 'selection-guide'],
handle: 'choose-perfect-wallcovering-dining-room',
summary: 'A comprehensive guide to selecting wallcoverings that enhance your dining experience.',
excerpt: 'Learn how to select wallcoverings that create the perfect ambiance for memorable dining experiences, considering factors like color psychology, pattern scale, and lighting.'
},
{
id: 'blog_003',
title: 'Vintage Damask: Bringing Classic Elegance to Contemporary Spaces',
createdAt: '2024-11-18T09:45:00Z',
publishedAt: null, // Draft
tags: ['vintage', 'damask', 'classic', 'elegance'],
handle: 'vintage-damask-classic-elegance-contemporary-spaces',
summary: 'Explore how traditional damask patterns can be integrated into modern interior design.',
excerpt: 'Discover the timeless appeal of damask wallcoverings and how to incorporate these classic patterns into contemporary interior design schemes.'
},
{
id: 'blog_004',
title: 'Tropical Themes: Creating a Resort Feel at Home',
createdAt: '2024-11-17T11:20:00Z',
publishedAt: '2024-11-17T13:30:00Z',
tags: ['tropical', 'resort', 'vacation', 'nature'],
handle: 'tropical-themes-resort-feel-home',
summary: 'Transform your living spaces with tropical-inspired wallcovering designs.',
excerpt: 'Bring the relaxing atmosphere of a tropical resort into your home with nature-inspired wallcovering patterns featuring lush foliage and exotic motifs.'
},
{
id: 'blog_005',
title: 'Abstract Art on Your Walls: Making a Bold Statement',
createdAt: '2024-11-16T16:00:00Z',
publishedAt: '2024-11-16T18:00:00Z',
tags: ['abstract', 'art', 'bold', 'statement'],
handle: 'abstract-art-walls-bold-statement',
summary: 'How abstract wallcovering designs can serve as stunning focal points in any room.',
excerpt: 'Explore how abstract wallcovering designs can transform your walls into captivating art pieces that reflect your personal style and creativity.'
}
]
// Get generated blog posts from the generate/blog API
function getGeneratedBlogs() {
try {
const blogsDir = path.join(process.cwd(), 'data', 'generated-blogs')
if (!fs.existsSync(blogsDir)) {
return []
}
const files = fs.readdirSync(blogsDir)
.filter(file => file.endsWith('.json'))
.sort()
.reverse()
.slice(0, 10) // Latest 10
return files.map(file => {
try {
const content = fs.readFileSync(path.join(blogsDir, file), 'utf-8')
const blog = JSON.parse(content)
return {
id: `generated_${file.replace('.json', '')}`,
title: blog.title,
createdAt: blog.createdAt,
publishedAt: blog.status === 'published' ? blog.createdAt : null,
tags: blog.tags || [],
handle: blog.title.toLowerCase().replace(/[^a-z0-9\s-]/g, '').replace(/\s+/g, '-'),
summary: blog.excerpt,
excerpt: blog.content.substring(0, 200) + '...',
isGenerated: true,
sku: blog.sku,
wordCount: blog.wordCount,
readingTime: blog.readingTime
}
} catch (e) {
console.error('Error parsing generated blog file:', file, e)
return null
}
}).filter((blog): blog is NonNullable<typeof blog> => blog !== null)
} catch (error) {
console.error('Error reading generated blogs:', error)
return []
}
}
interface ShopifyBlogsQuery {
limit?: number
sortKey?: 'CREATED_AT' | 'UPDATED_AT' | 'TITLE'
reverse?: boolean
published?: boolean
}
async function fetchShopifyBlogs(queryParams: ShopifyBlogsQuery) {
const { limit = 20, sortKey = 'CREATED_AT', reverse = true, published } = queryParams
// Combine mock blogs with generated blogs
let allBlogs = [...MOCK_BLOGS, ...getGeneratedBlogs()]
// Apply published filter
if (published !== undefined) {
allBlogs = allBlogs.filter(blog => {
if (!blog) return false
return published ? blog.publishedAt !== null : blog.publishedAt === null
})
}
// Apply sorting
allBlogs.sort((a, b) => {
let comparison = 0
switch (sortKey) {
case 'TITLE':
comparison = a.title.localeCompare(b.title)
break
case 'CREATED_AT':
case 'UPDATED_AT':
default:
comparison = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
break
}
return reverse ? -comparison : comparison
})
// Apply limit
allBlogs = allBlogs.slice(0, limit)
return {
data: {
articles: {
edges: allBlogs.map(blog => ({
node: blog
}))
}
}
}
}
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const queryParams: ShopifyBlogsQuery = {
limit: searchParams.get('limit') ? parseInt(searchParams.get('limit')!) : 20,
sortKey: (searchParams.get('sortKey') as any) || 'CREATED_AT',
reverse: searchParams.get('reverse') !== 'false',
published: searchParams.get('published') ? searchParams.get('published') === 'true' : undefined
}
console.log('[SHOPIFY] Fetching blog posts with params:', queryParams)
const shopifyResponse = await fetchShopifyBlogs(queryParams)
if (shopifyResponse.data?.articles) {
const blogs = shopifyResponse.data.articles.edges.map((edge: any) => ({
id: edge.node.id,
title: edge.node.title,
createdAt: edge.node.createdAt,
publishedAt: edge.node.publishedAt,
tags: Array.isArray(edge.node.tags) ? edge.node.tags.join(', ') : edge.node.tags || '',
handle: edge.node.handle,
summary: edge.node.summary,
excerpt: edge.node.excerpt,
isGenerated: edge.node.isGenerated || false,
sku: edge.node.sku || null,
wordCount: edge.node.wordCount,
readingTime: edge.node.readingTime
}))
console.log(`[SHOPIFY] Retrieved ${blogs.length} blog posts`)
// Count published vs draft
const publishedCount = blogs.filter(blog => blog.publishedAt).length
const draftCount = blogs.filter(blog => !blog.publishedAt).length
const generatedCount = blogs.filter(blog => blog.isGenerated).length
return NextResponse.json({
blogs,
count: blogs.length,
stats: {
total: blogs.length,
published: publishedCount,
drafts: draftCount,
generated: generatedCount
}
})
} else {
return NextResponse.json({
blogs: [],
count: 0,
stats: { total: 0, published: 0, drafts: 0, generated: 0 },
error: 'No blog posts found'
})
}
} catch (error) {
console.error('[SHOPIFY] Error fetching blog posts:', error)
return NextResponse.json({
error: 'Failed to fetch blog posts from Shopify',
details: error instanceof Error ? error.message : String(error)
}, { status: 500 })
}
}
export async function POST(request: NextRequest) {
try {
// This could be used to create new blog posts via GraphQL mutation
const body = await request.json()
console.log('[SHOPIFY] Create blog post request:', body)
// Mock successful creation
const newBlog = {
id: `blog_${Date.now()}`,
title: body.title || 'New Blog Post',
handle: (body.title || 'new-blog-post').toLowerCase().replace(/[^a-z0-9\s-]/g, '').replace(/\s+/g, '-'),
createdAt: new Date().toISOString(),
publishedAt: body.published ? new Date().toISOString() : null,
tags: body.tags || [],
summary: body.summary || 'New blog post summary',
excerpt: body.excerpt || body.content?.substring(0, 200) + '...' || 'Blog post excerpt'
}
return NextResponse.json({
success: true,
blog: newBlog,
message: 'Blog post created successfully (mock)'
})
} catch (error) {
console.error('[SHOPIFY] Error creating blog post:', error)
return NextResponse.json({
error: 'Failed to create blog post',
details: error instanceof Error ? error.message : String(error)
}, { status: 500 })
}
}