Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions app/admin/import/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export default function ImportPage() {
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Upload className="h-5 w-5" />
<Upload className="size-5" />
Import Facts
</CardTitle>
</CardHeader>
Expand Down Expand Up @@ -192,12 +192,12 @@ export default function ImportPage() {
<CardTitle className="flex items-center gap-2">
{results ? (
results.success ? (
<CheckCircle className="h-5 w-5 text-green-500" />
<CheckCircle className="size-5 text-green-500" />
) : (
<XCircle className="h-5 w-5 text-red-500" />
<XCircle className="size-5 text-red-500" />
)
) : (
<AlertCircle className="h-5 w-5 text-gray-500" />
<AlertCircle className="size-5 text-gray-500" />
)}
Import Results
</CardTitle>
Expand Down Expand Up @@ -259,7 +259,7 @@ export default function ImportPage() {
</div>
) : (
<div className="py-8 text-center text-muted-foreground">
<AlertCircle className="mx-auto mb-4 h-12 w-12 opacity-50" />
<AlertCircle className="mx-auto mb-4 size-12 opacity-50" />
<p>Import results will appear here after running an import.</p>
</div>
)}
Expand Down
4 changes: 2 additions & 2 deletions app/admin/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ export default function AdminLayout({
<div className="flex items-center gap-4">
<Link href="/">
<Button variant="ghost" size="sm">
<ArrowLeft className="mr-2 h-4 w-4" />
<ArrowLeft className="mr-2 size-4" />
Back to App
</Button>
</Link>
<div className="flex items-center gap-2">
<Settings className="h-5 w-5" />
<Settings className="size-5" />
<h1 className="text-xl font-semibold">Admin Panel</h1>
</div>
</div>
Expand Down
26 changes: 13 additions & 13 deletions app/admin/topics/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
const getEntityIcon = (type: string) => {
switch (type) {
case "TECH":
return <Hash className="h-4 w-4" />
return <Hash className="size-4" />
case "ORG":
return <Building className="h-4 w-4" />
return <Building className="size-4" />
case "PERSON":
return <User className="h-4 w-4" />
return <User className="size-4" />
case "LOCATION":
return <MapPin className="h-4 w-4" />
return <MapPin className="size-4" />
case "CONCEPT":
return <Lightbulb className="h-4 w-4" />
return <Lightbulb className="size-4" />
case "EVENT":
return <Calendar className="h-4 w-4" />
return <Calendar className="size-4" />
default:
return <TrendingUp className="h-4 w-4" />
return <TrendingUp className="size-4" />
}
}

Expand All @@ -41,7 +41,7 @@ async function TopicStats() {
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Articles</CardTitle>
<TrendingUp className="h-4 w-4 text-muted-foreground" />
<TrendingUp className="size-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats.totalArticles}</div>
Expand All @@ -53,7 +53,7 @@ async function TopicStats() {
<CardTitle className="text-sm font-medium">
Articles with Topics
</CardTitle>
<Hash className="h-4 w-4 text-muted-foreground" />
<Hash className="size-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats.articlesWithTopics}</div>
Expand All @@ -66,7 +66,7 @@ async function TopicStats() {
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Topics</CardTitle>
<Lightbulb className="h-4 w-4 text-muted-foreground" />
<Lightbulb className="size-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats.totalTopics}</div>
Expand All @@ -76,7 +76,7 @@ async function TopicStats() {
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Trending Topics</CardTitle>
<TrendingUp className="h-4 w-4 text-muted-foreground" />
<TrendingUp className="size-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{stats.trendingTopics}</div>
Expand Down Expand Up @@ -171,8 +171,8 @@ export default function TopicsAdminPage() {
className="flex items-center justify-between rounded-lg border p-3"
>
<div className="flex items-center gap-3">
<div className="h-4 w-4 animate-pulse rounded bg-muted"></div>
<div className="h-4 w-4 animate-pulse rounded bg-muted"></div>
<div className="size-4 animate-pulse rounded bg-muted"></div>
<div className="size-4 animate-pulse rounded bg-muted"></div>
<div>
<div className="mb-2 h-4 w-32 animate-pulse rounded bg-muted"></div>
<div className="h-3 w-24 animate-pulse rounded bg-muted"></div>
Expand Down
133 changes: 133 additions & 0 deletions app/api/articles/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { NextRequest, NextResponse } from "next/server"

import { getArticlesByTopics } from "@/lib/articles"

// Cache for 5 minutes
export const revalidate = 300
export const dynamic = "force-dynamic"

export async function GET(request: NextRequest) {
try {
const { searchParams } = request.nextUrl

// Parse query parameters
const topicsParam = searchParams.get("topics")
const timeFilter = searchParams.get("timeFilter") || "7d"
const topicTypesParam = searchParams.get("topicTypes")
const sortBy = searchParams.get("sortBy") || "score"

// Validate required parameters
if (!topicsParam) {
return NextResponse.json(
{ error: "topics parameter is required" },
{ status: 400 }
)
}

// Parse topics array
const topics = topicsParam
.split(",")
.map((t) => t.trim())
.filter(Boolean)
if (topics.length === 0) {
return NextResponse.json(
{ error: "At least one topic is required" },
{ status: 400 }
)
}

// Parse topic types array
const topicTypes = topicTypesParam
? topicTypesParam
.split(",")
.map((t) => t.trim())
.filter(Boolean)
: []

// Validate timeFilter
const validTimeFilters = ["24h", "7d", "30d", "all"]
if (!validTimeFilters.includes(timeFilter)) {
return NextResponse.json(
{ error: "timeFilter must be one of: 24h, 7d, 30d, all" },
{ status: 400 }
)
}

// Validate sortBy
const validSortBy = ["time", "score"]
if (!validSortBy.includes(sortBy)) {
return NextResponse.json(
{ error: "sortBy must be one of: time, score" },
{ status: 400 }
)
}

// Convert timeFilter to hours
let timeWindow: number | null = null
switch (timeFilter) {
case "24h":
timeWindow = 24
break
case "7d":
timeWindow = 168
break
case "30d":
timeWindow = 720
break
case "all":
timeWindow = null
break
}

// Get articles
const articles = await getArticlesByTopics(topics, {
timeWindow,
topicTypes,
limit: 50,
})

// Sort results
let sortedArticles = [...articles]
if (sortBy === "time") {
sortedArticles.sort(
(a, b) =>
new Date(b.published_at).getTime() -
new Date(a.published_at).getTime()
)
} else {
// Already sorted by relevance score from the query
sortedArticles.sort((a, b) => b.relevanceScore - a.relevanceScore)
}

// Prepare response
const response = {
articles: sortedArticles,
metadata: {
totalResults: sortedArticles.length,
timeFilter,
searchType: "topic" as const,
topics,
topicTypes,
sortBy,
generatedAt: new Date().toISOString(),
},
}

return NextResponse.json(response, {
headers: {
"Cache-Control": "public, s-maxage=300, stale-while-revalidate=600",
"CDN-Cache-Control": "public, s-maxage=300",
"Vercel-CDN-Cache-Control": "public, s-maxage=300",
},
})
} catch (error) {
console.error("Error in GET /api/articles:", error)
return NextResponse.json(
{
error: "Failed to retrieve articles",
details: error instanceof Error ? error.message : "Unknown error",
},
{ status: 500 }
)
}
}
94 changes: 94 additions & 0 deletions app/api/articles/search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { NextRequest, NextResponse } from "next/server"

import { searchArticlesByText } from "@/lib/articles"

// Cache for 5 minutes
export const revalidate = 300
export const dynamic = "force-dynamic"

export async function GET(request: NextRequest) {
try {
const { searchParams } = request.nextUrl

// Parse query parameters
const query = searchParams.get("q")
const timeFilter = searchParams.get("timeFilter") || "7d"

// Validate required parameters
if (!query) {
return NextResponse.json(
{ error: "q (query) parameter is required" },
{ status: 400 }
)
}

// Validate query length
if (query.length < 3) {
return NextResponse.json(
{ error: "Query must be at least 3 characters long" },
{ status: 400 }
)
}

// Validate timeFilter
const validTimeFilters = ["24h", "7d", "30d", "all"]
if (!validTimeFilters.includes(timeFilter)) {
return NextResponse.json(
{ error: "timeFilter must be one of: 24h, 7d, 30d, all" },
{ status: 400 }
)
}

// Convert timeFilter to hours
let timeWindow: number | null = null
switch (timeFilter) {
case "24h":
timeWindow = 24
break
case "7d":
timeWindow = 168
break
case "30d":
timeWindow = 720
break
case "all":
timeWindow = null
break
}

// Search articles
const articles = await searchArticlesByText(query, {
timeWindow,
limit: 20,
})

// Prepare response
const response = {
articles,
metadata: {
totalResults: articles.length,
timeFilter,
searchType: "text" as const,
query,
generatedAt: new Date().toISOString(),
},
}

return NextResponse.json(response, {
headers: {
"Cache-Control": "public, s-maxage=300, stale-while-revalidate=600",
"CDN-Cache-Control": "public, s-maxage=300",
"Vercel-CDN-Cache-Control": "public, s-maxage=300",
},
})
} catch (error) {
console.error("Error in GET /api/articles/search:", error)
return NextResponse.json(
{
error: "Failed to search articles",
details: error instanceof Error ? error.message : "Unknown error",
},
{ status: 500 }
)
}
}
13 changes: 11 additions & 2 deletions app/api/topics/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export async function GET(request: NextRequest) {
const timeWindow = parseInt(searchParams.get("timeWindow") || "48")
const limit = parseInt(searchParams.get("limit") || "10")
const entityType = searchParams.get("entityType") || undefined
const topicTypes =
searchParams.get("topicTypes")?.split(",").filter(Boolean) || undefined
const diverse = searchParams.get("diverse") === "true"
const randomize = searchParams.get("_t") !== null // If timestamp parameter exists, randomize

Expand All @@ -35,8 +37,14 @@ export async function GET(request: NextRequest) {

// Get topics (diverse or regular)
const topics = diverse
? await getDiverseTopics({ timeWindow, limit, entityType, randomize })
: await getTrendingTopics({ timeWindow, limit, entityType })
? await getDiverseTopics({
timeWindow,
limit,
entityType,
randomize,
topicTypes,
})
: await getTrendingTopics({ timeWindow, limit, entityType, topicTypes })

// Transform to API response format
const response = {
Expand All @@ -63,6 +71,7 @@ export async function GET(request: NextRequest) {
timeWindow,
limit,
entityType,
topicTypes,
diverse,
randomize,
totalTopics: topics.length,
Expand Down
Loading