Add topics endpoint and refactor to topic-based discovery

- Add GET /topics endpoint with pagination and peer counts
- Refactor offers to support multiple topics per offer
- Add stateless authentication with AES-256-GCM
- Add bloom filter support for peer exclusion
- Update database schema for topic-based discovery
- Add comprehensive API documentation to README

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-14 18:31:17 +01:00
parent 377d12b820
commit 685692dee1
13 changed files with 1660 additions and 440 deletions

View File

@@ -1,12 +1,19 @@
import { createApp } from './app.ts';
import { D1Storage } from './storage/d1.ts';
import { generateSecretKey } from './crypto.ts';
import { Config } from './config.ts';
/**
* Cloudflare Workers environment bindings
*/
export interface Env {
DB: D1Database;
OFFER_TIMEOUT?: string;
AUTH_SECRET?: string;
OFFER_DEFAULT_TTL?: string;
OFFER_MAX_TTL?: string;
OFFER_MIN_TTL?: string;
MAX_OFFERS_PER_REQUEST?: string;
MAX_TOPICS_PER_OFFER?: string;
CORS_ORIGINS?: string;
VERSION?: string;
}
@@ -19,21 +26,29 @@ export default {
// Initialize D1 storage
const storage = new D1Storage(env.DB);
// Parse configuration
const offerTimeout = env.OFFER_TIMEOUT
? parseInt(env.OFFER_TIMEOUT, 10)
: 60000; // 1 minute default
// Generate or use provided auth secret
const authSecret = env.AUTH_SECRET || generateSecretKey();
const corsOrigins = env.CORS_ORIGINS
? env.CORS_ORIGINS.split(',').map(o => o.trim())
: ['*'];
// Build config from environment
const config: Config = {
port: 0, // Not used in Workers
storageType: 'sqlite', // D1 is SQLite-compatible
storagePath: '', // Not used with D1
corsOrigins: env.CORS_ORIGINS
? env.CORS_ORIGINS.split(',').map(o => o.trim())
: ['*'],
version: env.VERSION || 'unknown',
authSecret,
offerDefaultTtl: env.OFFER_DEFAULT_TTL ? parseInt(env.OFFER_DEFAULT_TTL, 10) : 60000,
offerMaxTtl: env.OFFER_MAX_TTL ? parseInt(env.OFFER_MAX_TTL, 10) : 86400000,
offerMinTtl: env.OFFER_MIN_TTL ? parseInt(env.OFFER_MIN_TTL, 10) : 60000,
cleanupInterval: 60000, // Not used in Workers (scheduled handler instead)
maxOffersPerRequest: env.MAX_OFFERS_PER_REQUEST ? parseInt(env.MAX_OFFERS_PER_REQUEST, 10) : 100,
maxTopicsPerOffer: env.MAX_TOPICS_PER_OFFER ? parseInt(env.MAX_TOPICS_PER_OFFER, 10) : 50,
};
// Create Hono app
const app = createApp(storage, {
offerTimeout,
corsOrigins,
version: env.VERSION || 'unknown',
});
const app = createApp(storage, config);
// Handle request
return app.fetch(request, env, ctx);
@@ -41,15 +56,15 @@ export default {
/**
* Scheduled handler for cron triggers
* Runs every minute to clean up expired offers
* Runs periodically to clean up expired offers
*/
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
const storage = new D1Storage(env.DB);
const now = Date.now();
try {
// Delete expired offers using the storage method
const deletedCount = await storage.cleanupExpiredOffers();
// Delete expired offers
const deletedCount = await storage.deleteExpiredOffers(now);
console.log(`Cleaned up ${deletedCount} expired offers at ${new Date(now).toISOString()}`);
} catch (error) {