Skip to content

Commit ff9d5e9

Browse files
committed
Enhance GitHub integration and add advanced search API
Introduced advanced search functionality with caching and error handling. Improved GitHub integration by creating components for GitHub Authentication and data fetching. Refactored metadata configuration and updated the application's global configurations. Took 58 seconds
1 parent d9e0380 commit ff9d5e9

File tree

13 files changed

+938
-6
lines changed

13 files changed

+938
-6
lines changed

app/api/advanced-search/route.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { NextResponse } from 'next/server'
2+
import {
3+
SearXNGSearchResults,
4+
} from '@/lib/types/index'
5+
import { CacheService } from '@/lib/services/cache-service'
6+
import { advancedSearchXNGSearch } from '@/lib/search/searxng'
7+
8+
/**
9+
* Maximum number of results to fetch from SearXNG.
10+
* Increasing this value can improve result quality but may impact performance.
11+
* In advanced search mode, this is multiplied by SEARXNG_CRAWL_MULTIPLIER for initial fetching.
12+
*/
13+
const SEARXNG_MAX_RESULTS = Math.max(
14+
10,
15+
Math.min(100, parseInt(process.env.SEARXNG_MAX_RESULTS || '50', 10))
16+
)
17+
18+
export async function POST(request: Request) {
19+
const { query, maxResults, searchDepth, includeDomains, excludeDomains } =
20+
await request.json()
21+
22+
const SEARXNG_DEFAULT_DEPTH = process.env.SEARXNG_DEFAULT_DEPTH || 'basic'
23+
const cacheService = CacheService.getInstance()
24+
25+
try {
26+
const cacheKey = `search:${query}:${maxResults}:${searchDepth}:${
27+
Array.isArray(includeDomains) ? includeDomains.join(',') : ''
28+
}:${Array.isArray(excludeDomains) ? excludeDomains.join(',') : ''}`
29+
30+
// Try to get cached results
31+
const cachedResults = await cacheService.get<SearXNGSearchResults>(cacheKey)
32+
if (cachedResults) {
33+
return NextResponse.json(cachedResults)
34+
}
35+
36+
// If not cached, perform the search
37+
const results = await advancedSearchXNGSearch(
38+
query,
39+
Math.min(maxResults, SEARXNG_MAX_RESULTS),
40+
searchDepth || SEARXNG_DEFAULT_DEPTH,
41+
Array.isArray(includeDomains) ? includeDomains : [],
42+
Array.isArray(excludeDomains) ? excludeDomains : []
43+
)
44+
45+
// Cache the results
46+
await cacheService.set(cacheKey, results)
47+
48+
return NextResponse.json(results)
49+
} catch (error) {
50+
console.error('Advanced search error:', error)
51+
return NextResponse.json(
52+
{
53+
message: 'Internal Server Error',
54+
error: error instanceof Error ? error.message : String(error),
55+
query: query,
56+
results: [],
57+
images: [],
58+
number_of_results: 0
59+
},
60+
{ status: 500 }
61+
)
62+
}
63+
}

app/api/article/route.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { MarkdownEnhancer } from '@/lib/content/markdownEnhancer'
3+
import { validateApiKey, validateSubscription } from '@/lib/services/auth-service'
4+
import type { ApiKeyWithRelations } from '@/lib/services/auth-service'
5+
import { CacheService } from '@/lib/services/cache-service'
6+
import { getEndpointPricing, trackEndpointUsage, logApiRequest } from '@/lib/services/billing-service'
7+
import { withRateLimit } from '@/middleware/rateLimiter'
8+
9+
10+
async function handleRequest(request: NextRequest) {
11+
12+
// Validate and type the API key
13+
const TAVILY_API_KEY = process.env.TAVILY_API_KEY as string
14+
if (!TAVILY_API_KEY) {
15+
throw new Error('TAVILY_API_KEY environment variable is not set')
16+
}
17+
const startTime = Date.now()
18+
const cacheService = CacheService.getInstance()
19+
let keyData: ApiKeyWithRelations | null = null
20+
21+
try {
22+
const apiKey = request.headers.get('x-api-key')
23+
keyData = await validateApiKey(apiKey)
24+
25+
if (!keyData) {
26+
return NextResponse.json({ error: 'Invalid API key' }, { status: 401 })
27+
}
28+
29+
const activeSubscription = await validateSubscription(keyData)
30+
const endpointPricing = await getEndpointPricing('/api/article', 'POST')
31+
32+
if (!endpointPricing) {
33+
return NextResponse.json(
34+
{ error: 'Endpoint pricing not configured' },
35+
{ status: 500 }
36+
)
37+
}
38+
39+
// Parse request body
40+
const body = await request.json()
41+
const { content, title } = body
42+
43+
if (!content || typeof content !== 'string' || !title || typeof title !== 'string') {
44+
return NextResponse.json(
45+
{ error: 'Content and title are required and must be strings' },
46+
{ status: 400 }
47+
)
48+
}
49+
50+
// Check cache
51+
const cacheKey = `article:${Buffer.from(title + content).toString('base64')}`
52+
const cachedResult = await cacheService.get<any>(cacheKey)
53+
54+
if (cachedResult) {
55+
await logApiRequest(
56+
keyData.id,
57+
'/api/article',
58+
'POST',
59+
200,
60+
Date.now() - startTime,
61+
endpointPricing.id,
62+
endpointPricing.pricePerRequest
63+
)
64+
return NextResponse.json(cachedResult)
65+
}
66+
67+
// Process content
68+
const enhancer = new MarkdownEnhancer(TAVILY_API_KEY)
69+
const result = await enhancer.enhance(content, title)
70+
71+
// Cache result
72+
await cacheService.set(cacheKey, result)
73+
74+
// Track usage and billing
75+
await trackEndpointUsage(
76+
activeSubscription.id,
77+
endpointPricing.id,
78+
endpointPricing.pricePerRequest
79+
)
80+
81+
await logApiRequest(
82+
keyData.id,
83+
'/api/article',
84+
'POST',
85+
200,
86+
Date.now() - startTime,
87+
endpointPricing.id,
88+
endpointPricing.pricePerRequest
89+
)
90+
91+
return NextResponse.json(result)
92+
93+
} catch (error) {
94+
const status = error instanceof Error && error.message.includes('Rate limit') ? 429 : 500
95+
96+
if (keyData?.id) {
97+
await logApiRequest(
98+
keyData.id,
99+
'/api/article',
100+
'POST',
101+
status,
102+
Date.now() - startTime,
103+
undefined,
104+
undefined,
105+
error instanceof Error ? error.message : 'Unknown error'
106+
)
107+
}
108+
109+
return NextResponse.json(
110+
{ error: error instanceof Error ? error.message : 'Unknown error' },
111+
{ status }
112+
)
113+
}
114+
}
115+
116+
export async function POST(request: NextRequest) {
117+
// Get rate limit from API key
118+
const apiKey = request.headers.get('x-api-key')
119+
const keyData = await validateApiKey(apiKey)
120+
121+
if (!keyData) {
122+
return NextResponse.json({ error: 'Invalid API key' }, { status: 401 })
123+
}
124+
125+
return withRateLimit(
126+
request,
127+
handleRequest,
128+
{ perMinute: keyData.requestPerMinute ?? 60 }
129+
)
130+
}
131+
132+
export const runtime = 'nodejs'
133+
export const maxDuration = 60 // in seconds
134+
export const dynamic = 'force-dynamic'

app/api/auth/[...nextauth]/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ import { authOptions } from "@/lib/auth"
55
const handler = NextAuth(authOptions)
66

77
export { handler as GET, handler as POST }
8+
File renamed without changes.

app/dashboard/globalVariables/[variableId]/charts/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { authOptions } from "@/lib/auth"
55
import { getCurrentUser } from "@/lib/session"
66
import { GlobalVariableCharts } from "@/components/globalVariables/global-variable-charts"
77
import { Shell } from "@/components/layout/shell"
8-
import { DashboardHeader } from "@/components/pages/dashboard/dashboard-header"
98

109
export const metadata: Metadata = {
1110
title: "Global Variable Charts",

0 commit comments

Comments
 (0)