a new start

This commit is contained in:
valknarness
2025-10-25 16:09:02 +02:00
commit b63592f153
94 changed files with 23058 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
import { NextResponse } from 'next/server';
import { readFileSync, existsSync, statSync } from 'fs';
import { join } from 'path';
import { createHash } from 'crypto';
export const dynamic = 'force-dynamic';
// Get database version and metadata
export async function GET() {
try {
// Use the database from the user's home directory
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
const dbPath = join(homeDir, '.awesome', 'awesome.db');
const metadataPath = join(homeDir, '.awesome', 'db-metadata.json');
if (!existsSync(dbPath)) {
return NextResponse.json(
{ error: 'Database not found' },
{ status: 404 }
);
}
// Get file stats
const stats = statSync(dbPath);
// Calculate hash for version
const buffer = readFileSync(dbPath);
const hash = createHash('sha256').update(buffer).digest('hex');
// Load metadata if available
let metadata = {};
if (existsSync(metadataPath)) {
metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
}
return NextResponse.json({
version: hash.substring(0, 16),
size: stats.size,
modified: stats.mtime.toISOString(),
...metadata,
});
} catch (error) {
console.error('Error getting DB version:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,50 @@
import { NextRequest, NextResponse } from 'next/server'
import { getRepositoriesByList, getAwesomeLists } from '@/lib/db'
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const listId = parseInt(id, 10)
if (isNaN(listId)) {
return NextResponse.json(
{ error: 'Invalid list ID' },
{ status: 400 }
)
}
// Get the list info
const lists = getAwesomeLists()
const list = lists.find(l => l.id === listId)
if (!list) {
return NextResponse.json(
{ error: 'List not found' },
{ status: 404 }
)
}
// Parse pagination
const searchParams = request.nextUrl.searchParams
const page = parseInt(searchParams.get('page') || '1', 10)
const limit = parseInt(searchParams.get('limit') || '50', 10)
const offset = (page - 1) * limit
// Get repositories
const repositories = getRepositoriesByList(listId, limit, offset)
return NextResponse.json({
list,
repositories
})
} catch (error) {
console.error('List detail API error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}

24
app/api/lists/route.ts Normal file
View File

@@ -0,0 +1,24 @@
import { NextRequest, NextResponse } from 'next/server'
import { getAwesomeLists, getCategories } from '@/lib/db'
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams
const category = searchParams.get('category') || undefined
const lists = getAwesomeLists(category)
const categories = getCategories()
return NextResponse.json({
lists,
categories,
total: lists.length
})
} catch (error) {
console.error('Lists API error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,36 @@
import { NextRequest, NextResponse } from 'next/server'
import { getRepositoryWithReadme } from '@/lib/db'
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const repositoryId = parseInt(id, 10)
if (isNaN(repositoryId)) {
return NextResponse.json(
{ error: 'Invalid repository ID' },
{ status: 400 }
)
}
const repository = getRepositoryWithReadme(repositoryId)
if (!repository) {
return NextResponse.json(
{ error: 'Repository not found' },
{ status: 404 }
)
}
return NextResponse.json(repository)
} catch (error) {
console.error('Repository API error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}

48
app/api/search/route.ts Normal file
View File

@@ -0,0 +1,48 @@
import { NextRequest, NextResponse } from 'next/server'
import { searchRepositories } from '@/lib/db'
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams
const query = searchParams.get('q')
if (!query) {
return NextResponse.json(
{ error: 'Query parameter "q" is required' },
{ status: 400 }
)
}
// Parse pagination
const page = parseInt(searchParams.get('page') || '1', 10)
const limit = parseInt(searchParams.get('limit') || '20', 10)
const offset = (page - 1) * limit
// Parse filters
const language = searchParams.get('language') || undefined
const category = searchParams.get('category') || undefined
const minStars = searchParams.get('minStars')
? parseInt(searchParams.get('minStars')!, 10)
: undefined
const sortBy = (searchParams.get('sortBy') || 'relevance') as 'relevance' | 'stars' | 'recent'
// Perform search
const results = searchRepositories({
query,
limit,
offset,
language,
minStars,
category,
sortBy
})
return NextResponse.json(results)
} catch (error) {
console.error('Search API error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}

24
app/api/stats/route.ts Normal file
View File

@@ -0,0 +1,24 @@
import { NextResponse } from 'next/server'
import { getStats, getLanguages, getCategories, getTrendingRepositories } from '@/lib/db'
export async function GET() {
try {
const stats = getStats()
const languages = getLanguages()
const categories = getCategories()
const trending = getTrendingRepositories(10)
return NextResponse.json({
stats,
languages,
categories,
trending
})
} catch (error) {
console.error('Stats API error:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}

60
app/api/webhook/route.ts Normal file
View File

@@ -0,0 +1,60 @@
import { NextRequest, NextResponse } from 'next/server';
import { createHmac } from 'crypto';
import { writeFileSync } from 'fs';
import { join } from 'path';
export const dynamic = 'force-dynamic';
// Verify webhook signature
function verifySignature(payload: string, signature: string, secret: string): boolean {
const hmac = createHmac('sha256', secret);
const digest = 'sha256=' + hmac.update(payload).digest('hex');
return digest === signature;
}
// Handle webhook from GitHub Actions
export async function POST(request: NextRequest) {
try {
const signature = request.headers.get('x-github-secret');
const body = await request.text();
// Verify signature if secret is configured
const webhookSecret = process.env.WEBHOOK_SECRET;
if (webhookSecret && signature) {
if (!verifySignature(body, signature, webhookSecret)) {
return NextResponse.json(
{ error: 'Invalid signature' },
{ status: 401 }
);
}
}
const data = JSON.parse(body);
console.log('📥 Webhook received:', {
version: data.version,
timestamp: data.timestamp,
lists: data.lists_count,
repos: data.repos_count,
});
// Save metadata
const metadataPath = join(process.cwd(), 'data', 'db-metadata.json');
writeFileSync(metadataPath, JSON.stringify(data, null, 2));
// TODO: Trigger database download from hosting
// const dbUrl = process.env.DB_URL;
// await downloadDatabase(dbUrl);
return NextResponse.json({
success: true,
message: 'Database metadata updated',
});
} catch (error) {
console.error('Webhook error:', error);
return NextResponse.json(
{ error: 'Webhook processing failed' },
{ status: 500 }
);
}
}