Browse Source

implement pino logger

main
Silberengel 4 weeks ago
parent
commit
24e1c11b62
  1. 3
      src/hooks.server.ts
  2. 25
      src/lib/services/git/file-manager.ts
  3. 11
      src/lib/services/git/repo-manager.ts
  4. 22
      src/lib/services/logger.ts
  5. 3
      src/lib/services/nostr/branch-protection-service.ts
  6. 3
      src/lib/services/nostr/fork-count-service.ts
  7. 3
      src/lib/services/nostr/maintainer-service.ts
  8. 3
      src/lib/services/nostr/nostr-client.ts
  9. 5
      src/lib/services/nostr/ownership-transfer-service.ts
  10. 15
      src/lib/services/nostr/repo-polling.ts
  11. 4
      src/lib/services/nostr/user-relays.ts
  12. 19
      src/lib/services/security/audit-logger.ts
  13. 10
      src/routes/api/git/[...path]/+server.ts
  14. 5
      src/routes/api/repos/[npub]/[repo]/branch-protection/+server.ts
  15. 5
      src/routes/api/repos/[npub]/[repo]/branches/+server.ts
  16. 3
      src/routes/api/repos/[npub]/[repo]/commits/+server.ts
  17. 3
      src/routes/api/repos/[npub]/[repo]/diff/+server.ts
  18. 3
      src/routes/api/repos/[npub]/[repo]/download/+server.ts
  19. 11
      src/routes/api/repos/[npub]/[repo]/file/+server.ts
  20. 49
      src/routes/api/repos/[npub]/[repo]/fork/+server.ts
  21. 5
      src/routes/api/repos/[npub]/[repo]/highlights/+server.ts
  22. 5
      src/routes/api/repos/[npub]/[repo]/issues/+server.ts
  23. 3
      src/routes/api/repos/[npub]/[repo]/maintainers/+server.ts
  24. 5
      src/routes/api/repos/[npub]/[repo]/prs/+server.ts
  25. 3
      src/routes/api/repos/[npub]/[repo]/raw/+server.ts
  26. 3
      src/routes/api/repos/[npub]/[repo]/readme/+server.ts
  27. 5
      src/routes/api/repos/[npub]/[repo]/settings/+server.ts
  28. 5
      src/routes/api/repos/[npub]/[repo]/tags/+server.ts
  29. 5
      src/routes/api/repos/[npub]/[repo]/transfer/+server.ts
  30. 3
      src/routes/api/repos/[npub]/[repo]/tree/+server.ts
  31. 3
      src/routes/api/repos/[npub]/[repo]/verify/+server.ts
  32. 3
      src/routes/api/search/+server.ts
  33. 3
      src/routes/docs/nip34/+page.server.ts

3
src/hooks.server.ts

@ -9,6 +9,7 @@ import { RepoPollingService } from './lib/services/nostr/repo-polling.js';
import { GIT_DOMAIN, DEFAULT_NOSTR_RELAYS } from './lib/config.js'; import { GIT_DOMAIN, DEFAULT_NOSTR_RELAYS } from './lib/config.js';
import { rateLimiter } from './lib/services/security/rate-limiter.js'; import { rateLimiter } from './lib/services/security/rate-limiter.js';
import { auditLogger } from './lib/services/security/audit-logger.js'; import { auditLogger } from './lib/services/security/audit-logger.js';
import logger from './lib/services/logger.js';
// Initialize polling service // Initialize polling service
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
@ -19,7 +20,7 @@ let pollingService: RepoPollingService | null = null;
if (typeof process !== 'undefined') { if (typeof process !== 'undefined') {
pollingService = new RepoPollingService(DEFAULT_NOSTR_RELAYS, repoRoot, domain); pollingService = new RepoPollingService(DEFAULT_NOSTR_RELAYS, repoRoot, domain);
pollingService.start(); pollingService.start();
console.log('Started repo polling service'); logger.info('Started repo polling service');
} }
export const handle: Handle = async ({ event, resolve }) => { export const handle: Handle = async ({ event, resolve }) => {

25
src/lib/services/git/file-manager.ts

@ -10,6 +10,7 @@ import { existsSync } from 'fs';
import { RepoManager } from './repo-manager.js'; import { RepoManager } from './repo-manager.js';
import { createGitCommitSignature } from './commit-signer.js'; import { createGitCommitSignature } from './commit-signer.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';
import logger from '../logger.js';
export interface FileEntry { export interface FileEntry {
name: string; name: string;
@ -229,7 +230,7 @@ export class FileManager {
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}); });
} catch (error) { } catch (error) {
console.error('Error listing files:', error); logger.error({ error, repoPath, ref }, 'Error listing files');
throw new Error(`Failed to list files: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Failed to list files: ${error instanceof Error ? error.message : String(error)}`);
} }
} }
@ -275,7 +276,7 @@ export class FileManager {
size size
}; };
} catch (error) { } catch (error) {
console.error('Error reading file:', error); logger.error({ error, repoPath, filePath, ref }, 'Error reading file');
throw new Error(`Failed to read file: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Failed to read file: ${error instanceof Error ? error.message : String(error)}`);
} }
} }
@ -414,7 +415,7 @@ export class FileManager {
} catch (err) { } catch (err) {
// Security: Sanitize error messages (never log private keys) // Security: Sanitize error messages (never log private keys)
const sanitizedErr = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : String(err); const sanitizedErr = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : String(err);
console.warn('Failed to sign commit:', sanitizedErr); logger.warn({ error: sanitizedErr, repoPath, filePath }, 'Failed to sign commit');
// Continue without signature if signing fails // Continue without signature if signing fails
} }
} }
@ -430,7 +431,7 @@ export class FileManager {
// Clean up work directory // Clean up work directory
await rm(workDir, { recursive: true, force: true }); await rm(workDir, { recursive: true, force: true });
} catch (error) { } catch (error) {
console.error('Error writing file:', error); logger.error({ error, repoPath, filePath, npub }, 'Error writing file');
throw new Error(`Failed to write file: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Failed to write file: ${error instanceof Error ? error.message : String(error)}`);
} }
} }
@ -453,7 +454,7 @@ export class FileManager {
.map(b => b.replace(/^origin\//, '')) .map(b => b.replace(/^origin\//, ''))
.filter(b => !b.includes('HEAD')); .filter(b => !b.includes('HEAD'));
} catch (error) { } catch (error) {
console.error('Error getting branches:', error); logger.error({ error, repoPath }, 'Error getting branches');
return ['main', 'master']; // Default branches return ['main', 'master']; // Default branches
} }
} }
@ -585,7 +586,7 @@ export class FileManager {
} catch (err) { } catch (err) {
// Security: Sanitize error messages (never log private keys) // Security: Sanitize error messages (never log private keys)
const sanitizedErr = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : String(err); const sanitizedErr = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : String(err);
console.warn('Failed to sign commit:', sanitizedErr); logger.warn({ error: sanitizedErr, repoPath, filePath }, 'Failed to sign commit');
// Continue without signature if signing fails // Continue without signature if signing fails
} }
} }
@ -601,7 +602,7 @@ export class FileManager {
// Clean up // Clean up
await rm(workDir, { recursive: true, force: true }); await rm(workDir, { recursive: true, force: true });
} catch (error) { } catch (error) {
console.error('Error deleting file:', error); logger.error({ error, repoPath, filePath, npub }, 'Error deleting file');
throw new Error(`Failed to delete file: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Failed to delete file: ${error instanceof Error ? error.message : String(error)}`);
} }
} }
@ -646,7 +647,7 @@ export class FileManager {
// Clean up // Clean up
await rm(workDir, { recursive: true, force: true }); await rm(workDir, { recursive: true, force: true });
} catch (error) { } catch (error) {
console.error('Error creating branch:', error); logger.error({ error, repoPath, branchName, npub }, 'Error creating branch');
throw new Error(`Failed to create branch: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Failed to create branch: ${error instanceof Error ? error.message : String(error)}`);
} }
} }
@ -689,7 +690,7 @@ export class FileManager {
files: commit.diff?.files?.map((f: any) => f.file) || [] files: commit.diff?.files?.map((f: any) => f.file) || []
})); }));
} catch (error) { } catch (error) {
console.error('Error getting commit history:', error); logger.error({ error, repoPath, branch, limit }, 'Error getting commit history');
throw new Error(`Failed to get commit history: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Failed to get commit history: ${error instanceof Error ? error.message : String(error)}`);
} }
} }
@ -777,7 +778,7 @@ export class FileManager {
return files; return files;
} catch (error) { } catch (error) {
console.error('Error getting diff:', error); logger.error({ error, repoPath, fromRef, toRef }, 'Error getting diff');
throw new Error(`Failed to get diff: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Failed to get diff: ${error instanceof Error ? error.message : String(error)}`);
} }
} }
@ -821,7 +822,7 @@ export class FileManager {
} }
} }
} catch (error) { } catch (error) {
console.error('Error creating tag:', error); logger.error({ error, repoPath, tagName, ref, message }, 'Error creating tag');
throw new Error(`Failed to create tag: ${error instanceof Error ? error.message : String(error)}`); throw new Error(`Failed to create tag: ${error instanceof Error ? error.message : String(error)}`);
} }
} }
@ -866,7 +867,7 @@ export class FileManager {
return tagList; return tagList;
} catch (error) { } catch (error) {
console.error('Error getting tags:', error); logger.error({ error, repoPath }, 'Error getting tags');
return []; return [];
} }
} }

11
src/lib/services/git/repo-manager.ts

@ -12,6 +12,7 @@ import type { NostrEvent } from '../../types/nostr.js';
import { GIT_DOMAIN } from '../../config.js'; import { GIT_DOMAIN } from '../../config.js';
import { generateVerificationFile, VERIFICATION_FILE_PATH } from '../nostr/repo-verification.js'; import { generateVerificationFile, VERIFICATION_FILE_PATH } from '../nostr/repo-verification.js';
import simpleGit, { type SimpleGit } from 'simple-git'; import simpleGit, { type SimpleGit } from 'simple-git';
import logger from '../logger.js';
const execAsync = promisify(exec); const execAsync = promisify(exec);
@ -99,7 +100,7 @@ export class RepoManager {
// But we should be careful not to overwrite existing history // But we should be careful not to overwrite existing history
// For now, we'll just ensure the verification file exists // For now, we'll just ensure the verification file exists
// The self-transfer event should already be published to relays // The self-transfer event should already be published to relays
console.log(`Existing repo ${repoPath.fullPath} - self-transfer event should be published to relays`); logger.info({ repoPath: repoPath.fullPath }, 'Existing repo - self-transfer event should be published to relays');
} }
} }
@ -119,7 +120,7 @@ export class RepoManager {
// Update all branches // Update all branches
await execAsync(`cd "${repoPath}" && git remote set-head ${remoteName} -a`); await execAsync(`cd "${repoPath}" && git remote set-head ${remoteName} -a`);
} catch (error) { } catch (error) {
console.error(`Failed to sync from ${url}:`, error); logger.error({ error, url, repoPath }, 'Failed to sync from remote');
// Continue with other remotes // Continue with other remotes
} }
} }
@ -136,7 +137,7 @@ export class RepoManager {
await execAsync(`cd "${repoPath}" && git push ${remoteName} --all --force`); await execAsync(`cd "${repoPath}" && git push ${remoteName} --all --force`);
await execAsync(`cd "${repoPath}" && git push ${remoteName} --tags --force`); await execAsync(`cd "${repoPath}" && git push ${remoteName} --tags --force`);
} catch (error) { } catch (error) {
console.error(`Failed to sync to ${url}:`, error); logger.error({ error, url, repoPath }, 'Failed to sync to remote');
// Continue with other remotes // Continue with other remotes
} }
} }
@ -311,7 +312,7 @@ export class RepoManager {
); );
commitMessage = signedMessage; commitMessage = signedMessage;
} catch (err) { } catch (err) {
console.warn('Failed to sign initial commit:', err); logger.warn({ error: err, repoPath: repoPath.fullPath }, 'Failed to sign initial commit');
// Continue without signature if signing fails // Continue without signature if signing fails
} }
} }
@ -331,7 +332,7 @@ export class RepoManager {
// Clean up // Clean up
await rm(workDir, { recursive: true, force: true }); await rm(workDir, { recursive: true, force: true });
} catch (error) { } catch (error) {
console.error('Failed to create verification file:', error); logger.error({ error, repoPath: repoPath.fullPath }, 'Failed to create verification file');
// Don't throw - verification file creation is important but shouldn't block provisioning // Don't throw - verification file creation is important but shouldn't block provisioning
} }
} }

22
src/lib/services/logger.ts

@ -0,0 +1,22 @@
/**
* Pino logger service
* Provides structured logging with pino-pretty for development
*/
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
...(process.env.NODE_ENV === 'development' && {
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname'
}
}
})
});
export default logger;

3
src/lib/services/nostr/branch-protection-service.ts

@ -6,6 +6,7 @@
import { NostrClient } from './nostr-client.js'; import { NostrClient } from './nostr-client.js';
import { KIND } from '../../types/nostr.js'; import { KIND } from '../../types/nostr.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';
import logger from '../logger.js';
export interface BranchProtectionRule { export interface BranchProtectionRule {
branch: string; branch: string;
@ -51,7 +52,7 @@ export class BranchProtectionService {
const event = events[0]; const event = events[0];
return this.parseProtectionEvent(event); return this.parseProtectionEvent(event);
} catch (error) { } catch (error) {
console.error('Error fetching branch protection:', error); logger.error({ error, ownerPubkey, repoName }, 'Error fetching branch protection');
return null; return null;
} }
} }

3
src/lib/services/nostr/fork-count-service.ts

@ -5,6 +5,7 @@
import { NostrClient } from './nostr-client.js'; import { NostrClient } from './nostr-client.js';
import { KIND } from '../../types/nostr.js'; import { KIND } from '../../types/nostr.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';
import logger from '../logger.js';
export class ForkCountService { export class ForkCountService {
private nostrClient: NostrClient; private nostrClient: NostrClient;
@ -52,7 +53,7 @@ export class ForkCountService {
return count; return count;
} catch (error) { } catch (error) {
console.error(`[ForkCount] Error counting forks for ${originalOwnerPubkey}/${originalRepoName}:`, error); logger.error({ error, originalOwnerPubkey, originalRepoName }, '[ForkCount] Error counting forks');
// Return cached value if available, otherwise 0 // Return cached value if available, otherwise 0
return cached?.count || 0; return cached?.count || 0;
} }

3
src/lib/services/nostr/maintainer-service.ts

@ -8,6 +8,7 @@ import { KIND } from '../../types/nostr.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { OwnershipTransferService } from './ownership-transfer-service.js'; import { OwnershipTransferService } from './ownership-transfer-service.js';
import logger from '../logger.js';
export interface RepoPrivacyInfo { export interface RepoPrivacyInfo {
isPrivate: boolean; isPrivate: boolean;
@ -109,7 +110,7 @@ export class MaintainerService {
this.cache.set(cacheKey, { ...result, timestamp: Date.now() }); this.cache.set(cacheKey, { ...result, timestamp: Date.now() });
return result; return result;
} catch (error) { } catch (error) {
console.error('Error fetching maintainers:', error); logger.error({ error, ownerPubkey, repoName }, 'Error fetching maintainers');
// Fallback: only owner is maintainer, repo is public by default // Fallback: only owner is maintainer, repo is public by default
const result = { owner: repoOwnerPubkey, maintainers: [repoOwnerPubkey], isPrivate: false }; const result = { owner: repoOwnerPubkey, maintainers: [repoOwnerPubkey], isPrivate: false };
this.cache.set(cacheKey, { ...result, timestamp: Date.now() }); this.cache.set(cacheKey, { ...result, timestamp: Date.now() });

3
src/lib/services/nostr/nostr-client.ts

@ -4,6 +4,7 @@
import type { NostrEvent, NostrFilter } from '../../types/nostr.js'; import type { NostrEvent, NostrFilter } from '../../types/nostr.js';
import { createRequire } from 'module'; import { createRequire } from 'module';
import logger from '../logger.js';
// Polyfill WebSocket for Node.js environments (lazy initialization) // Polyfill WebSocket for Node.js environments (lazy initialization)
let wsPolyfillInitialized = false; let wsPolyfillInitialized = false;
@ -22,7 +23,7 @@ function initializeWebSocketPolyfill() {
wsPolyfillInitialized = true; wsPolyfillInitialized = true;
} catch { } catch {
// ws package not available, will fail at runtime in Node.js // ws package not available, will fail at runtime in Node.js
console.warn('WebSocket polyfill not available. Install "ws" package for Node.js support.'); logger.warn('WebSocket polyfill not available. Install "ws" package for Node.js support.');
wsPolyfillInitialized = true; // Mark as initialized to avoid repeated warnings wsPolyfillInitialized = true; // Mark as initialized to avoid repeated warnings
} }
} }

5
src/lib/services/nostr/ownership-transfer-service.ts

@ -8,6 +8,7 @@ import { KIND } from '../../types/nostr.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';
import { verifyEvent } from 'nostr-tools'; import { verifyEvent } from 'nostr-tools';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import logger from '../logger.js';
export interface OwnershipTransfer { export interface OwnershipTransfer {
event: NostrEvent; event: NostrEvent;
@ -100,7 +101,7 @@ export class OwnershipTransferService {
this.cache.set(cacheKey, { owner: currentOwner, timestamp: Date.now() }); this.cache.set(cacheKey, { owner: currentOwner, timestamp: Date.now() });
return currentOwner; return currentOwner;
} catch (error) { } catch (error) {
console.error('Error fetching ownership transfers:', error); logger.error({ error, originalOwnerPubkey, repoName }, 'Error fetching ownership transfers');
// Fallback to original owner // Fallback to original owner
return originalOwnerPubkey; return originalOwnerPubkey;
} }
@ -305,7 +306,7 @@ export class OwnershipTransferService {
transfers.sort((a, b) => b.timestamp - a.timestamp); transfers.sort((a, b) => b.timestamp - a.timestamp);
return transfers; return transfers;
} catch (error) { } catch (error) {
console.error('Error fetching transfer history:', error); logger.error({ error, ownerPubkey, repoName }, 'Error fetching transfer history');
return []; return [];
} }
} }

15
src/lib/services/nostr/repo-polling.ts

@ -7,6 +7,7 @@ import { KIND } from '../../types/nostr.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';
import { RepoManager } from '../git/repo-manager.js'; import { RepoManager } from '../git/repo-manager.js';
import { OwnershipTransferService } from './ownership-transfer-service.js'; import { OwnershipTransferService } from './ownership-transfer-service.js';
import logger from '../logger.js';
export class RepoPollingService { export class RepoPollingService {
private nostrClient: NostrClient; private nostrClient: NostrClient;
@ -80,7 +81,7 @@ export class RepoPollingService {
// Extract repo ID from d-tag // Extract repo ID from d-tag
const dTag = event.tags.find(t => t[0] === 'd')?.[1]; const dTag = event.tags.find(t => t[0] === 'd')?.[1];
if (!dTag) { if (!dTag) {
console.warn(`Repo announcement ${event.id} missing d-tag`); logger.warn({ eventId: event.id }, 'Repo announcement missing d-tag');
continue; continue;
} }
@ -136,7 +137,7 @@ export class RepoPollingService {
if (isExistingRepo && !selfTransferEvent) { if (isExistingRepo && !selfTransferEvent) {
// Security: Truncate pubkey in logs // Security: Truncate pubkey in logs
const truncatedPubkey = event.pubkey.length > 16 ? `${event.pubkey.slice(0, 8)}...${event.pubkey.slice(-4)}` : event.pubkey; const truncatedPubkey = event.pubkey.length > 16 ? `${event.pubkey.slice(0, 8)}...${event.pubkey.slice(-4)}` : event.pubkey;
console.log(`Existing repo ${dTag} from ${truncatedPubkey} has no self-transfer event. Creating template for owner to sign and publish.`); logger.info({ repoId: dTag, pubkey: truncatedPubkey }, 'Existing repo has no self-transfer event. Creating template for owner to sign and publish.');
try { try {
// Create a self-transfer event template for the existing repo // Create a self-transfer event template for the existing repo
@ -155,21 +156,21 @@ export class RepoPollingService {
// Use the template (even though it's unsigned, it will be included in the repo) // Use the template (even though it's unsigned, it will be included in the repo)
selfTransferEvent = selfTransferTemplate; selfTransferEvent = selfTransferTemplate;
console.warn(`Self-transfer event template created for ${dTag}. Owner ${event.pubkey} should sign and publish it to relays.`); logger.warn({ repoId: dTag, pubkey: event.pubkey }, 'Self-transfer event template created. Owner should sign and publish it to relays.');
} catch (err) { } catch (err) {
console.error(`Failed to create self-transfer event template for ${dTag}:`, err); logger.error({ error: err, repoId: dTag }, 'Failed to create self-transfer event template');
} }
} }
// Provision the repo with self-transfer event if available // Provision the repo with self-transfer event if available
await this.repoManager.provisionRepo(event, selfTransferEvent, isExistingRepo); await this.repoManager.provisionRepo(event, selfTransferEvent, isExistingRepo);
console.log(`Provisioned repo from announcement ${event.id}${isExistingRepo ? ' (existing)' : ' (new)'}`); logger.info({ eventId: event.id, isExistingRepo }, 'Provisioned repo from announcement');
} catch (error) { } catch (error) {
console.error(`Failed to provision repo from ${event.id}:`, error); logger.error({ error, eventId: event.id }, 'Failed to provision repo from announcement');
} }
} }
} catch (error) { } catch (error) {
console.error('Error polling for repo announcements:', error); logger.error({ error }, 'Error polling for repo announcements');
} }
} }

4
src/lib/services/nostr/user-relays.ts

@ -5,6 +5,8 @@
import { NostrClient } from './nostr-client.js'; import { NostrClient } from './nostr-client.js';
import type { NostrEvent } from '../../types/nostr.js'; import type { NostrEvent } from '../../types/nostr.js';
import { KIND } from '../../types/nostr.js'; import { KIND } from '../../types/nostr.js';
import logger from '../logger.js';
import { truncatePubkey } from '../../utils/security.js';
export async function getUserRelays( export async function getUserRelays(
pubkey: string, pubkey: string,
@ -68,7 +70,7 @@ export async function getUserRelays(
} }
} }
} catch (error) { } catch (error) {
console.error('Failed to fetch user relays:', error); logger.error({ error, pubkey: truncatePubkey(pubkey) }, 'Failed to fetch user relays');
} }
return { inbox, outbox }; return { inbox, outbox };

19
src/lib/services/security/audit-logger.ts

@ -15,6 +15,7 @@ import { appendFile, mkdir, readdir, unlink, stat } from 'fs/promises';
import { join, dirname } from 'path'; import { join, dirname } from 'path';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { truncatePubkey, sanitizeError, redactSensitiveData } from '../../utils/security.js'; import { truncatePubkey, sanitizeError, redactSensitiveData } from '../../utils/security.js';
import logger from '../logger.js';
export interface AuditLogEntry { export interface AuditLogEntry {
timestamp: string; timestamp: string;
@ -72,7 +73,7 @@ export class AuditLogger {
await mkdir(this.logDir, { recursive: true }); await mkdir(this.logDir, { recursive: true });
} }
} catch (error) { } catch (error) {
console.error('[AUDIT] Failed to create log directory:', error); logger.error({ error }, '[AUDIT] Failed to create log directory');
} }
} }
@ -98,13 +99,13 @@ export class AuditLogger {
// Run cleanup daily // Run cleanup daily
this.cleanupInterval = setInterval(() => { this.cleanupInterval = setInterval(() => {
this.cleanupOldLogs().catch(err => { this.cleanupOldLogs().catch(err => {
console.error('[AUDIT] Failed to cleanup old logs:', err); logger.error({ error: err }, '[AUDIT] Failed to cleanup old logs');
}); });
}, 24 * 60 * 60 * 1000); // 24 hours }, 24 * 60 * 60 * 1000); // 24 hours
// Run initial cleanup // Run initial cleanup
this.cleanupOldLogs().catch(err => { this.cleanupOldLogs().catch(err => {
console.error('[AUDIT] Failed to cleanup old logs:', err); logger.error({ error: err }, '[AUDIT] Failed to cleanup old logs');
}); });
} }
@ -128,14 +129,14 @@ export class AuditLogger {
const stats = await stat(filePath); const stats = await stat(filePath);
if (stats.mtime.getTime() < cutoffTime) { if (stats.mtime.getTime() < cutoffTime) {
await unlink(filePath); await unlink(filePath);
console.log(`[AUDIT] Deleted old log file: ${file}`); logger.info({ file }, '[AUDIT] Deleted old log file');
} }
} catch (err) { } catch (err) {
// Ignore errors for individual files // Ignore errors for individual files
} }
} }
} catch (error) { } catch (error) {
console.error('[AUDIT] Error during log cleanup:', error); logger.error({ error }, '[AUDIT] Error during log cleanup');
} }
} }
@ -158,7 +159,7 @@ export class AuditLogger {
await appendFile(join(this.logDir, this.currentLogFile), content, 'utf8'); await appendFile(join(this.logDir, this.currentLogFile), content, 'utf8');
} }
} catch (error) { } catch (error) {
console.error('[AUDIT] Failed to write to log file:', error); logger.error({ error }, '[AUDIT] Failed to write to log file');
// Put items back in queue (but limit queue size to prevent memory issues) // Put items back in queue (but limit queue size to prevent memory issues)
this.writeQueue = [...this.writeQueue, ...this.writeQueue].slice(0, 1000); this.writeQueue = [...this.writeQueue, ...this.writeQueue].slice(0, 1000);
} finally { } finally {
@ -191,14 +192,14 @@ export class AuditLogger {
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}; };
// Log to console (structured JSON) // Log using pino (structured JSON)
const logLine = JSON.stringify(sanitizedEntry); const logLine = JSON.stringify(sanitizedEntry);
console.log(`[AUDIT] ${logLine}`); logger.info(sanitizedEntry, '[AUDIT]');
// Write to file if configured (async, non-blocking) // Write to file if configured (async, non-blocking)
if (this.logFile) { if (this.logFile) {
this.writeToFile(logLine).catch(err => { this.writeToFile(logLine).catch(err => {
console.error('[AUDIT] Failed to write log entry:', sanitizeError(err)); logger.error({ error: sanitizeError(err) }, '[AUDIT] Failed to write log entry');
}); });
} }
} }

10
src/routes/api/git/[...path]/+server.ts

@ -18,6 +18,7 @@ import { verifyNIP98Auth } from '$lib/services/nostr/nip98-auth.js';
import { OwnershipTransferService } from '$lib/services/nostr/ownership-transfer-service.js'; import { OwnershipTransferService } from '$lib/services/nostr/ownership-transfer-service.js';
import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js';
import { BranchProtectionService } from '$lib/services/nostr/branch-protection-service.js'; import { BranchProtectionService } from '$lib/services/nostr/branch-protection-service.js';
import logger from '$lib/services/logger.js';
import { auditLogger } from '$lib/services/security/audit-logger.js'; import { auditLogger } from '$lib/services/security/audit-logger.js';
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
@ -412,11 +413,12 @@ export const POST: RequestHandler = async ({ params, url, request }) => {
// Note: We need to extract the target branch from the git push request // Note: We need to extract the target branch from the git push request
// This is a simplified check - in production, you'd parse the git protocol // This is a simplified check - in production, you'd parse the git protocol
// to determine the exact branch being pushed // to determine the exact branch being pushed
let targetBranch = 'main'; // Default to main if can't determine
try { try {
// Try to extract branch from request body (git protocol) // Try to extract branch from request body (git protocol)
const bodyText = bodyBuffer.toString('utf-8', 0, Math.min(bodyBuffer.length, 1000)); const bodyText = bodyBuffer.toString('utf-8', 0, Math.min(bodyBuffer.length, 1000));
const branchMatch = bodyText.match(/refs\/heads\/([^\s\n]+)/); const branchMatch = bodyText.match(/refs\/heads\/([^\s\n]+)/);
const targetBranch = branchMatch ? branchMatch[1] : 'main'; // Default to main if can't determine targetBranch = branchMatch ? branchMatch[1] : 'main'; // Default to main if can't determine
const protectionCheck = await branchProtectionService.canPushToBranch( const protectionCheck = await branchProtectionService.canPushToBranch(
authResult.pubkey || '', authResult.pubkey || '',
@ -433,7 +435,7 @@ export const POST: RequestHandler = async ({ params, url, request }) => {
// If we can't check protection, log but don't block (fail open for now) // If we can't check protection, log but don't block (fail open for now)
// Security: Sanitize error messages // Security: Sanitize error messages
const sanitizedError = error instanceof Error ? error.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : String(error); const sanitizedError = error instanceof Error ? error.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : String(error);
console.warn('Failed to check branch protection:', sanitizedError); logger.warn({ error: sanitizedError, npub, repoName, targetBranch }, 'Failed to check branch protection');
} }
} }
@ -545,14 +547,14 @@ export const POST: RequestHandler = async ({ params, url, request }) => {
repoManager.syncToRemotes(repoPath, otherUrls).catch(err => { repoManager.syncToRemotes(repoPath, otherUrls).catch(err => {
// Security: Sanitize error messages // Security: Sanitize error messages
const sanitizedErr = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : String(err); const sanitizedErr = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : String(err);
console.error('Failed to sync to remotes after push:', sanitizedErr); logger.error({ error: sanitizedErr, npub, repoName }, 'Failed to sync to remotes after push');
}); });
} }
} }
} catch (err) { } catch (err) {
// Security: Sanitize error messages // Security: Sanitize error messages
const sanitizedErr = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : String(err); const sanitizedErr = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : String(err);
console.error('Failed to sync to remotes:', sanitizedErr); logger.error({ error: sanitizedErr, npub, repoName }, 'Failed to sync to remotes');
// Don't fail the request if sync fails // Don't fail the request if sync fails
} }
} }

5
src/routes/api/repos/[npub]/[repo]/branch-protection/+server.ts

@ -12,6 +12,7 @@ import { signEventWithNIP07 } from '$lib/services/nostr/nip07-signer.js';
import { NostrClient } from '$lib/services/nostr/nostr-client.js'; import { NostrClient } from '$lib/services/nostr/nostr-client.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import type { BranchProtectionRule } from '$lib/services/nostr/branch-protection-service.js'; import type { BranchProtectionRule } from '$lib/services/nostr/branch-protection-service.js';
import logger from '$lib/services/logger.js';
const branchProtectionService = new BranchProtectionService(DEFAULT_NOSTR_RELAYS); const branchProtectionService = new BranchProtectionService(DEFAULT_NOSTR_RELAYS);
const ownershipTransferService = new OwnershipTransferService(DEFAULT_NOSTR_RELAYS); const ownershipTransferService = new OwnershipTransferService(DEFAULT_NOSTR_RELAYS);
@ -51,7 +52,7 @@ export const GET: RequestHandler = async ({ params }: { params: { npub?: string;
} catch (err) { } catch (err) {
// Security: Sanitize error messages // Security: Sanitize error messages
const sanitizedError = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : 'Failed to get branch protection'; const sanitizedError = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : 'Failed to get branch protection';
console.error('Error getting branch protection:', sanitizedError); logger.error({ error: sanitizedError, npub, repo }, 'Error getting branch protection');
return error(500, sanitizedError); return error(500, sanitizedError);
} }
}; };
@ -142,7 +143,7 @@ export const POST: RequestHandler = async ({ params, request }: { params: { npub
} catch (err) { } catch (err) {
// Security: Sanitize error messages // Security: Sanitize error messages
const sanitizedError = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : 'Failed to update branch protection'; const sanitizedError = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : 'Failed to update branch protection';
console.error('Error updating branch protection:', sanitizedError); logger.error({ error: sanitizedError, npub, repo }, 'Error updating branch protection');
return error(500, sanitizedError); return error(500, sanitizedError);
} }
}; };

5
src/routes/api/repos/[npub]/[repo]/branches/+server.ts

@ -9,6 +9,7 @@ import { FileManager } from '$lib/services/git/file-manager.js';
import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js';
import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import logger from '$lib/services/logger.js';
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
const fileManager = new FileManager(repoRoot); const fileManager = new FileManager(repoRoot);
@ -29,7 +30,7 @@ export const GET: RequestHandler = async ({ params }: { params: { npub?: string;
const branches = await fileManager.getBranches(npub, repo); const branches = await fileManager.getBranches(npub, repo);
return json(branches); return json(branches);
} catch (err) { } catch (err) {
console.error('Error getting branches:', err); logger.error({ error: err, npub, repo }, 'Error getting branches');
return error(500, err instanceof Error ? err.message : 'Failed to get branches'); return error(500, err instanceof Error ? err.message : 'Failed to get branches');
} }
}; };
@ -90,7 +91,7 @@ export const POST: RequestHandler = async ({ params, request }: { params: { npub
await fileManager.createBranch(npub, repo, branchName, fromBranch || 'main'); await fileManager.createBranch(npub, repo, branchName, fromBranch || 'main');
return json({ success: true, message: 'Branch created successfully' }); return json({ success: true, message: 'Branch created successfully' });
} catch (err) { } catch (err) {
console.error('Error creating branch:', err); logger.error({ error: err, npub, repo, branchName }, 'Error creating branch');
return error(500, err instanceof Error ? err.message : 'Failed to create branch'); return error(500, err instanceof Error ? err.message : 'Failed to create branch');
} }
}; };

3
src/routes/api/repos/[npub]/[repo]/commits/+server.ts

@ -5,6 +5,7 @@
import { json, error } from '@sveltejs/kit'; import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
import { FileManager } from '$lib/services/git/file-manager.js'; import { FileManager } from '$lib/services/git/file-manager.js';
import logger from '$lib/services/logger.js';
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
const fileManager = new FileManager(repoRoot); const fileManager = new FileManager(repoRoot);
@ -35,7 +36,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => {
const commits = await fileManager.getCommitHistory(npub, repo, branch, limit, path); const commits = await fileManager.getCommitHistory(npub, repo, branch, limit, path);
return json(commits); return json(commits);
} catch (err) { } catch (err) {
console.error('Error getting commit history:', err); logger.error({ error: err, npub, repo, branch }, 'Error getting commit history');
return error(500, err instanceof Error ? err.message : 'Failed to get commit history'); return error(500, err instanceof Error ? err.message : 'Failed to get commit history');
} }
}; };

3
src/routes/api/repos/[npub]/[repo]/diff/+server.ts

@ -5,6 +5,7 @@
import { json, error } from '@sveltejs/kit'; import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
import { FileManager } from '$lib/services/git/file-manager.js'; import { FileManager } from '$lib/services/git/file-manager.js';
import logger from '$lib/services/logger.js';
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
const fileManager = new FileManager(repoRoot); const fileManager = new FileManager(repoRoot);
@ -35,7 +36,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => {
const diffs = await fileManager.getDiff(npub, repo, fromRef, toRef, filePath); const diffs = await fileManager.getDiff(npub, repo, fromRef, toRef, filePath);
return json(diffs); return json(diffs);
} catch (err) { } catch (err) {
console.error('Error getting diff:', err); logger.error({ error: err, npub, repo, fromRef, toRef }, 'Error getting diff');
return error(500, err instanceof Error ? err.message : 'Failed to get diff'); return error(500, err instanceof Error ? err.message : 'Failed to get diff');
} }
}; };

3
src/routes/api/repos/[npub]/[repo]/download/+server.ts

@ -13,6 +13,7 @@ import { promisify } from 'util';
import { existsSync, readFileSync } from 'fs'; import { existsSync, readFileSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { createReadStream } from 'fs'; import { createReadStream } from 'fs';
import logger from '$lib/services/logger.js';
const execAsync = promisify(exec); const execAsync = promisify(exec);
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
@ -104,7 +105,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => {
throw archiveError; throw archiveError;
} }
} catch (err) { } catch (err) {
console.error('Error creating repository archive:', err); logger.error({ error: err, npub, repo }, 'Error creating repository archive');
return error(500, err instanceof Error ? err.message : 'Failed to create repository archive'); return error(500, err instanceof Error ? err.message : 'Failed to create repository archive');
} }
}; };

11
src/routes/api/repos/[npub]/[repo]/file/+server.ts

@ -11,6 +11,7 @@ import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { verifyNIP98Auth } from '$lib/services/nostr/nip98-auth.js'; import { verifyNIP98Auth } from '$lib/services/nostr/nip98-auth.js';
import { auditLogger } from '$lib/services/security/audit-logger.js'; import { auditLogger } from '$lib/services/security/audit-logger.js';
import logger from '$lib/services/logger.js';
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
const fileManager = new FileManager(repoRoot); const fileManager = new FileManager(repoRoot);
@ -86,7 +87,7 @@ export const GET: RequestHandler = async ({ params, url, request }: { params: {
} catch (err) { } catch (err) {
// Security: Sanitize error messages to prevent leaking sensitive data // Security: Sanitize error messages to prevent leaking sensitive data
const sanitizedError = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : 'Failed to read file'; const sanitizedError = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : 'Failed to read file';
console.error('Error reading file:', sanitizedError); logger.error({ error: sanitizedError, npub, repo, filePath }, 'Error reading file');
return error(500, sanitizedError); return error(500, sanitizedError);
} }
}; };
@ -98,9 +99,11 @@ export const POST: RequestHandler = async ({ params, url, request }: { params: {
return error(400, 'Missing npub or repo parameter'); return error(400, 'Missing npub or repo parameter');
} }
let path: string | undefined;
try { try {
const body = await request.json(); const body = await request.json();
const { path, content, commitMessage, authorName, authorEmail, branch, action, userPubkey, useNIP07, nsecKey } = body; path = body.path;
const { content, commitMessage, authorName, authorEmail, branch, action, userPubkey, useNIP07, nsecKey } = body;
// Check for NIP-98 authentication (for git operations) // Check for NIP-98 authentication (for git operations)
const authHeader = request.headers.get('Authorization'); const authHeader = request.headers.get('Authorization');
@ -175,7 +178,7 @@ export const POST: RequestHandler = async ({ params, url, request }: { params: {
if (nsecKey) { if (nsecKey) {
// Security: Log warning but never log the actual key value // Security: Log warning but never log the actual key value
const clientIp = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown'; const clientIp = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown';
console.warn(`[SECURITY] Client attempted to send nsecKey in request from IP ${clientIp}. This is not allowed for security reasons.`); logger.warn({ clientIp, npub, repo }, '[SECURITY] Client attempted to send nsecKey in request. This is not allowed for security reasons.');
auditLogger.log({ auditLogger.log({
user: userPubkeyHex || undefined, user: userPubkeyHex || undefined,
ip: clientIp, ip: clientIp,
@ -265,7 +268,7 @@ export const POST: RequestHandler = async ({ params, url, request }: { params: {
} catch (err) { } catch (err) {
// Security: Sanitize error messages to prevent leaking sensitive data // Security: Sanitize error messages to prevent leaking sensitive data
const sanitizedError = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : 'Failed to write file'; const sanitizedError = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : 'Failed to write file';
console.error('Error writing file:', sanitizedError); logger.error({ error: sanitizedError, npub, repo, path }, 'Error writing file');
return error(500, sanitizedError); return error(500, sanitizedError);
} }
}; };

49
src/routes/api/repos/[npub]/[repo]/fork/+server.ts

@ -19,6 +19,7 @@ import { join } from 'path';
import { ResourceLimits } from '$lib/services/security/resource-limits.js'; import { ResourceLimits } from '$lib/services/security/resource-limits.js';
import { auditLogger } from '$lib/services/security/audit-logger.js'; import { auditLogger } from '$lib/services/security/audit-logger.js';
import { ForkCountService } from '$lib/services/nostr/fork-count-service.js'; import { ForkCountService } from '$lib/services/nostr/fork-count-service.js';
import logger from '$lib/services/logger.js';
const execAsync = promisify(exec); const execAsync = promisify(exec);
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
@ -45,29 +46,27 @@ async function publishEventWithRetry(
const logContext = context || `[event:${eventId}]`; const logContext = context || `[event:${eventId}]`;
for (let attempt = 1; attempt <= maxAttempts; attempt++) { for (let attempt = 1; attempt <= maxAttempts; attempt++) {
console.log(`[Fork] ${logContext} Publishing ${eventName} - Attempt ${attempt}/${maxAttempts}...`); logger.info({ logContext, eventName, attempt, maxAttempts }, `[Fork] Publishing ${eventName} - Attempt ${attempt}/${maxAttempts}...`);
lastResult = await nostrClient.publishEvent(event, relays); lastResult = await nostrClient.publishEvent(event, relays);
if (lastResult.success.length > 0) { if (lastResult.success.length > 0) {
console.log(`[Fork] ${logContext}${eventName} published successfully to ${lastResult.success.length} relay(s): ${lastResult.success.join(', ')}`); logger.info({ logContext, eventName, successCount: lastResult.success.length, relays: lastResult.success }, `[Fork] ${eventName} published successfully`);
if (lastResult.failed.length > 0) { if (lastResult.failed.length > 0) {
console.warn(`[Fork] ${logContext} ⚠ Some relays failed: ${lastResult.failed.map(f => `${f.relay}: ${f.error}`).join(', ')}`); logger.warn({ logContext, eventName, failed: lastResult.failed }, `[Fork] Some relays failed`);
} }
return lastResult; return lastResult;
} }
if (attempt < maxAttempts) { if (attempt < maxAttempts) {
const delayMs = Math.pow(2, attempt - 1) * 1000; // 1s, 2s, 4s const delayMs = Math.pow(2, attempt - 1) * 1000; // 1s, 2s, 4s
console.warn(`[Fork] ${logContext}${eventName} failed on attempt ${attempt}. Retrying in ${delayMs}ms...`); logger.warn({ logContext, eventName, attempt, delayMs, failed: lastResult.failed }, `[Fork] ${eventName} failed on attempt ${attempt}. Retrying...`);
console.warn(`[Fork] ${logContext} Failed relays: ${lastResult.failed.map(f => `${f.relay}: ${f.error}`).join(', ')}`);
await new Promise(resolve => setTimeout(resolve, delayMs)); await new Promise(resolve => setTimeout(resolve, delayMs));
} }
} }
// All attempts failed // All attempts failed
console.error(`[Fork] ${logContext}${eventName} failed after ${maxAttempts} attempts`); logger.error({ logContext, eventName, maxAttempts, failed: lastResult?.failed }, `[Fork] ${eventName} failed after ${maxAttempts} attempts`);
console.error(`[Fork] ${logContext} All relay failures: ${lastResult?.failed.map(f => `${f.relay}: ${f.error}`).join(', ')}`);
return lastResult!; return lastResult!;
} }
@ -233,8 +232,8 @@ export const POST: RequestHandler = async ({ params, request }) => {
const truncatedOriginalNpub = npub.length > 16 ? `${npub.slice(0, 12)}...` : npub; const truncatedOriginalNpub = npub.length > 16 ? `${npub.slice(0, 12)}...` : npub;
const context = `[${truncatedOriginalNpub}/${repo}${truncatedNpub}/${forkRepoName}]`; const context = `[${truncatedOriginalNpub}/${repo}${truncatedNpub}/${forkRepoName}]`;
console.log(`[Fork] ${context} Starting fork process`); const forkLogger = logger.child({ operation: 'fork', originalRepo: `${npub}/${repo}`, forkRepo: `${userNpub}/${forkRepoName}` });
console.log(`[Fork] ${context} Using ${combinedRelays.length} relay(s): ${combinedRelays.join(', ')}`); forkLogger.info({ relayCount: combinedRelays.length, relays: combinedRelays }, 'Starting fork process');
const publishResult = await publishEventWithRetry( const publishResult = await publishEventWithRetry(
signedForkAnnouncement, signedForkAnnouncement,
@ -246,7 +245,7 @@ export const POST: RequestHandler = async ({ params, request }) => {
if (publishResult.success.length === 0) { if (publishResult.success.length === 0) {
// Clean up repo if announcement failed // Clean up repo if announcement failed
console.error(`[Fork] ${context} ✗ Fork announcement failed after all retries. Cleaning up repository.`); forkLogger.error({ failed: publishResult.failed }, 'Fork announcement failed after all retries. Cleaning up repository.');
await execAsync(`rm -rf "${forkRepoPath}"`).catch(() => {}); await execAsync(`rm -rf "${forkRepoPath}"`).catch(() => {});
const errorDetails = `All relays failed: ${publishResult.failed.map(f => `${f.relay}: ${f.error}`).join('; ')}`; const errorDetails = `All relays failed: ${publishResult.failed.map(f => `${f.relay}: ${f.error}`).join('; ')}`;
return json({ return json({
@ -273,11 +272,11 @@ export const POST: RequestHandler = async ({ params, request }) => {
if (ownershipPublishResult.success.length === 0) { if (ownershipPublishResult.success.length === 0) {
// Clean up repo if ownership proof failed // Clean up repo if ownership proof failed
console.error(`[Fork] ${context}Ownership transfer event failed after all retries. Cleaning up repository and publishing deletion request.`); forkLogger.error({ failed: ownershipPublishResult.failed }, 'Ownership transfer event failed after all retries. Cleaning up repository and publishing deletion request.');
await execAsync(`rm -rf "${forkRepoPath}"`).catch(() => {}); await execAsync(`rm -rf "${forkRepoPath}"`).catch(() => {});
// Publish deletion request (NIP-09) for the announcement since it's invalid without ownership proof // Publish deletion request (NIP-09) for the announcement since it's invalid without ownership proof
console.log(`[Fork] ${context} Publishing deletion request for invalid fork announcement...`); forkLogger.info('Publishing deletion request for invalid fork announcement...');
const deletionRequest = { const deletionRequest = {
kind: KIND.DELETION_REQUEST, // NIP-09: Event Deletion Request kind: KIND.DELETION_REQUEST, // NIP-09: Event Deletion Request
pubkey: userPubkeyHex, pubkey: userPubkeyHex,
@ -299,9 +298,9 @@ export const POST: RequestHandler = async ({ params, request }) => {
); );
if (deletionResult.success.length > 0) { if (deletionResult.success.length > 0) {
console.log(`[Fork] ${context} ✓ Deletion request published successfully`); forkLogger.info('Deletion request published successfully');
} else { } else {
console.error(`[Fork] ${context} ✗ Failed to publish deletion request: ${deletionResult.failed.map(f => `${f.relay}: ${f.error}`).join(', ')}`); forkLogger.error({ failed: deletionResult.failed }, 'Failed to publish deletion request');
} }
const errorDetails = `Fork is invalid without ownership proof. All relays failed: ${ownershipPublishResult.failed.map(f => `${f.relay}: ${f.error}`).join('; ')}. Deletion request ${deletionResult.success.length > 0 ? 'published' : 'failed to publish'}.`; const errorDetails = `Fork is invalid without ownership proof. All relays failed: ${ownershipPublishResult.failed.map(f => `${f.relay}: ${f.error}`).join('; ')}. Deletion request ${deletionResult.success.length > 0 ? 'published' : 'failed to publish'}.`;
@ -314,17 +313,15 @@ export const POST: RequestHandler = async ({ params, request }) => {
} }
// Provision the fork repo (this will create verification file and include self-transfer) // Provision the fork repo (this will create verification file and include self-transfer)
console.log(`[Fork] Provisioning fork repository...`); forkLogger.info('Provisioning fork repository...');
await repoManager.provisionRepo(signedForkAnnouncement, signedOwnershipEvent, false); await repoManager.provisionRepo(signedForkAnnouncement, signedOwnershipEvent, false);
console.log(`[Fork] ✓ Fork completed successfully!`); forkLogger.info({
// Security: Truncate npub in logs announcementId: signedForkAnnouncement.id,
const truncatedNpub2 = userNpub.length > 16 ? `${userNpub.slice(0, 12)}...` : userNpub; ownershipTransferId: signedOwnershipEvent.id,
console.log(`[Fork] - Repository: ${truncatedNpub2}/${forkRepoName}`); announcementRelays: publishResult.success.length,
console.log(`[Fork] - Announcement ID: ${signedForkAnnouncement.id}`); ownershipRelays: ownershipPublishResult.success.length
console.log(`[Fork] - Ownership transfer ID: ${signedOwnershipEvent.id}`); }, 'Fork completed successfully');
console.log(`[Fork] - Published to ${publishResult.success.length} relay(s) for announcement`);
console.log(`[Fork] - Published to ${ownershipPublishResult.success.length} relay(s) for ownership transfer`);
return json({ return json({
success: true, success: true,
@ -345,7 +342,7 @@ export const POST: RequestHandler = async ({ params, request }) => {
// Security: Sanitize error messages to prevent leaking sensitive data // Security: Sanitize error messages to prevent leaking sensitive data
const sanitizedError = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : 'Failed to fork repository'; const sanitizedError = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : 'Failed to fork repository';
const context = npub && repo ? `[${npub}/${repo}]` : '[unknown]'; const context = npub && repo ? `[${npub}/${repo}]` : '[unknown]';
console.error(`[Fork] ${context} Error forking repository:`, sanitizedError); logger.error({ error: sanitizedError, npub, repo }, `[Fork] ${context} Error forking repository`);
return error(500, sanitizedError); return error(500, sanitizedError);
} }
}; };
@ -418,7 +415,7 @@ export const GET: RequestHandler = async ({ params }) => {
} catch (err) { } catch (err) {
// Log but don't fail the request // Log but don't fail the request
const context = npub && repo ? `[${npub}/${repo}]` : '[unknown]'; const context = npub && repo ? `[${npub}/${repo}]` : '[unknown]';
console.warn(`[Fork] ${context} Failed to get fork count:`, err instanceof Error ? err.message : String(err)); logger.warn({ error: err, npub, repo }, `[Fork] ${context} Failed to get fork count`);
} }
} }
@ -431,7 +428,7 @@ export const GET: RequestHandler = async ({ params }) => {
// Security: Sanitize error messages // Security: Sanitize error messages
const sanitizedError = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : 'Failed to get fork information'; const sanitizedError = err instanceof Error ? err.message.replace(/nsec[0-9a-z]+/gi, '[REDACTED]').replace(/[0-9a-f]{64}/g, '[REDACTED]') : 'Failed to get fork information';
const context = npub && repo ? `[${npub}/${repo}]` : '[unknown]'; const context = npub && repo ? `[${npub}/${repo}]` : '[unknown]';
console.error(`[Fork] ${context} Error getting fork information:`, sanitizedError); logger.error({ error: sanitizedError, npub, repo }, `[Fork] ${context} Error getting fork information`);
return error(500, sanitizedError); return error(500, sanitizedError);
} }
}; };

5
src/routes/api/repos/[npub]/[repo]/highlights/+server.ts

@ -12,6 +12,7 @@ import type { NostrEvent } from '$lib/types/nostr.js';
import { combineRelays } from '$lib/config.js'; import { combineRelays } from '$lib/config.js';
import { getUserRelays } from '$lib/services/nostr/user-relays.js'; import { getUserRelays } from '$lib/services/nostr/user-relays.js';
import { NostrClient } from '$lib/services/nostr/nostr-client.js'; import { NostrClient } from '$lib/services/nostr/nostr-client.js';
import logger from '$lib/services/logger.js';
const highlightsService = new HighlightsService(DEFAULT_NOSTR_RELAYS); const highlightsService = new HighlightsService(DEFAULT_NOSTR_RELAYS);
const nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS); const nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS);
@ -74,7 +75,7 @@ export const GET: RequestHandler = async ({ params, url }) => {
comments: prComments comments: prComments
}); });
} catch (err) { } catch (err) {
console.error('Error fetching highlights:', err); logger.error({ error: err, npub, repo }, 'Error fetching highlights');
return error(500, err instanceof Error ? err.message : 'Failed to fetch highlights'); return error(500, err instanceof Error ? err.message : 'Failed to fetch highlights');
} }
}; };
@ -123,7 +124,7 @@ export const POST: RequestHandler = async ({ params, request }) => {
return json({ success: true, event, published: result }); return json({ success: true, event, published: result });
} catch (err) { } catch (err) {
console.error('Error creating highlight/comment:', err); logger.error({ error: err, npub, repo }, 'Error creating highlight/comment');
return error(500, err instanceof Error ? err.message : 'Failed to create highlight/comment'); return error(500, err instanceof Error ? err.message : 'Failed to create highlight/comment');
} }
}; };

5
src/routes/api/repos/[npub]/[repo]/issues/+server.ts

@ -7,6 +7,7 @@ import type { RequestHandler } from './$types';
import { IssuesService } from '$lib/services/nostr/issues-service.js'; import { IssuesService } from '$lib/services/nostr/issues-service.js';
import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import logger from '$lib/services/logger.js';
export const GET: RequestHandler = async ({ params, url, request }) => { export const GET: RequestHandler = async ({ params, url, request }) => {
const { npub, repo } = params; const { npub, repo } = params;
@ -36,7 +37,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => {
return json(issues); return json(issues);
} catch (err) { } catch (err) {
console.error('Error fetching issues:', err); logger.error({ error: err, npub, repo }, 'Error fetching issues');
return error(500, err instanceof Error ? err.message : 'Failed to fetch issues'); return error(500, err instanceof Error ? err.message : 'Failed to fetch issues');
} }
}; };
@ -73,7 +74,7 @@ export const POST: RequestHandler = async ({ params, request }) => {
return json({ success: true, event, published: result }); return json({ success: true, event, published: result });
} catch (err) { } catch (err) {
console.error('Error creating issue:', err); logger.error({ error: err, npub, repo }, 'Error creating issue');
return error(500, err instanceof Error ? err.message : 'Failed to create issue'); return error(500, err instanceof Error ? err.message : 'Failed to create issue');
} }
}; };

3
src/routes/api/repos/[npub]/[repo]/maintainers/+server.ts

@ -8,6 +8,7 @@ import type { RequestHandler } from './$types';
import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js';
import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import logger from '$lib/services/logger.js';
const maintainerService = new MaintainerService(DEFAULT_NOSTR_RELAYS); const maintainerService = new MaintainerService(DEFAULT_NOSTR_RELAYS);
@ -60,7 +61,7 @@ export const GET: RequestHandler = async ({ params, url }: { params: { npub?: st
return json({ maintainers, owner }); return json({ maintainers, owner });
} catch (err) { } catch (err) {
console.error('Error checking maintainers:', err); logger.error({ error: err, npub, repo }, 'Error checking maintainers');
return error(500, err instanceof Error ? err.message : 'Failed to check maintainers'); return error(500, err instanceof Error ? err.message : 'Failed to check maintainers');
} }
}; };

5
src/routes/api/repos/[npub]/[repo]/prs/+server.ts

@ -8,6 +8,7 @@ import type { RequestHandler } from './$types';
import { PRsService } from '$lib/services/nostr/prs-service.js'; import { PRsService } from '$lib/services/nostr/prs-service.js';
import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import logger from '$lib/services/logger.js';
export const GET: RequestHandler = async ({ params, url, request }: { params: { npub?: string; repo?: string }; url: URL; request: Request }) => { export const GET: RequestHandler = async ({ params, url, request }: { params: { npub?: string; repo?: string }; url: URL; request: Request }) => {
const { npub, repo } = params; const { npub, repo } = params;
@ -43,7 +44,7 @@ export const GET: RequestHandler = async ({ params, url, request }: { params: {
return json(prs); return json(prs);
} catch (err) { } catch (err) {
console.error('Error fetching pull requests:', err); logger.error({ error: err, npub, repo }, 'Error fetching pull requests');
return error(500, err instanceof Error ? err.message : 'Failed to fetch pull requests'); return error(500, err instanceof Error ? err.message : 'Failed to fetch pull requests');
} }
}; };
@ -79,7 +80,7 @@ export const POST: RequestHandler = async ({ params, request }: { params: { npub
return json({ success: true, event, published: result }); return json({ success: true, event, published: result });
} catch (err) { } catch (err) {
console.error('Error creating pull request:', err); logger.error({ error: err, npub, repo }, 'Error creating pull request');
return error(500, err instanceof Error ? err.message : 'Failed to create pull request'); return error(500, err instanceof Error ? err.message : 'Failed to create pull request');
} }
}; };

3
src/routes/api/repos/[npub]/[repo]/raw/+server.ts

@ -8,6 +8,7 @@ import { FileManager } from '$lib/services/git/file-manager.js';
import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js';
import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import logger from '$lib/services/logger.js';
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
const fileManager = new FileManager(repoRoot); const fileManager = new FileManager(repoRoot);
@ -82,7 +83,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => {
} }
}); });
} catch (err) { } catch (err) {
console.error('Error getting raw file:', err); logger.error({ error: err, npub, repo, filePath }, 'Error getting raw file');
return error(500, err instanceof Error ? err.message : 'Failed to get raw file'); return error(500, err instanceof Error ? err.message : 'Failed to get raw file');
} }
}; };

3
src/routes/api/repos/[npub]/[repo]/readme/+server.ts

@ -8,6 +8,7 @@ import { FileManager } from '$lib/services/git/file-manager.js';
import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js';
import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import logger from '$lib/services/logger.js';
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
const fileManager = new FileManager(repoRoot); const fileManager = new FileManager(repoRoot);
@ -91,7 +92,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => {
isMarkdown: readmePath?.toLowerCase().endsWith('.md') || readmePath?.toLowerCase().endsWith('.markdown') isMarkdown: readmePath?.toLowerCase().endsWith('.md') || readmePath?.toLowerCase().endsWith('.markdown')
}); });
} catch (err) { } catch (err) {
console.error('Error getting README:', err); logger.error({ error: err, npub, repo }, 'Error getting README');
return error(500, err instanceof Error ? err.message : 'Failed to get README'); return error(500, err instanceof Error ? err.message : 'Failed to get README');
} }
}; };

5
src/routes/api/repos/[npub]/[repo]/settings/+server.ts

@ -11,6 +11,7 @@ import { KIND } from '$lib/types/nostr.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { signEventWithNIP07 } from '$lib/services/nostr/nip07-signer.js'; import { signEventWithNIP07 } from '$lib/services/nostr/nip07-signer.js';
import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js';
import logger from '$lib/services/logger.js';
import { OwnershipTransferService } from '$lib/services/nostr/ownership-transfer-service.js'; import { OwnershipTransferService } from '$lib/services/nostr/ownership-transfer-service.js';
const nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS); const nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS);
@ -100,7 +101,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => {
npub npub
}); });
} catch (err) { } catch (err) {
console.error('Error getting repository settings:', err); logger.error({ error: err, npub, repo }, 'Error getting repository settings');
return error(500, err instanceof Error ? err.message : 'Failed to get repository settings'); return error(500, err instanceof Error ? err.message : 'Failed to get repository settings');
} }
}; };
@ -214,7 +215,7 @@ export const POST: RequestHandler = async ({ params, request }) => {
return json({ success: true, event: signedEvent }); return json({ success: true, event: signedEvent });
} catch (err) { } catch (err) {
console.error('Error updating repository settings:', err); logger.error({ error: err, npub, repo }, 'Error updating repository settings');
return error(500, err instanceof Error ? err.message : 'Failed to update repository settings'); return error(500, err instanceof Error ? err.message : 'Failed to update repository settings');
} }
}; };

5
src/routes/api/repos/[npub]/[repo]/tags/+server.ts

@ -9,6 +9,7 @@ import { FileManager } from '$lib/services/git/file-manager.js';
import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js';
import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import logger from '$lib/services/logger.js';
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
const fileManager = new FileManager(repoRoot); const fileManager = new FileManager(repoRoot);
@ -37,7 +38,7 @@ export const GET: RequestHandler = async ({ params, url, request }: { params: {
const tags = await fileManager.getTags(npub, repo); const tags = await fileManager.getTags(npub, repo);
return json(tags); return json(tags);
} catch (err) { } catch (err) {
console.error('Error getting tags:', err); logger.error({ error: err, npub, repo }, 'Error getting tags');
return error(500, err instanceof Error ? err.message : 'Failed to get tags'); return error(500, err instanceof Error ? err.message : 'Failed to get tags');
} }
}; };
@ -98,7 +99,7 @@ export const POST: RequestHandler = async ({ params, request }: { params: { npub
await fileManager.createTag(npub, repo, tagName, ref || 'HEAD', message); await fileManager.createTag(npub, repo, tagName, ref || 'HEAD', message);
return json({ success: true, message: 'Tag created successfully' }); return json({ success: true, message: 'Tag created successfully' });
} catch (err) { } catch (err) {
console.error('Error creating tag:', err); logger.error({ error: err, npub, repo, tagName }, 'Error creating tag');
return error(500, err instanceof Error ? err.message : 'Failed to create tag'); return error(500, err instanceof Error ? err.message : 'Failed to create tag');
} }
}; };

5
src/routes/api/repos/[npub]/[repo]/transfer/+server.ts

@ -12,6 +12,7 @@ import { nip19 } from 'nostr-tools';
import { verifyEvent } from 'nostr-tools'; import { verifyEvent } from 'nostr-tools';
import type { NostrEvent } from '$lib/types/nostr.js'; import type { NostrEvent } from '$lib/types/nostr.js';
import { getUserRelays } from '$lib/services/nostr/user-relays.js'; import { getUserRelays } from '$lib/services/nostr/user-relays.js';
import logger from '$lib/services/logger.js';
const ownershipTransferService = new OwnershipTransferService(DEFAULT_NOSTR_RELAYS); const ownershipTransferService = new OwnershipTransferService(DEFAULT_NOSTR_RELAYS);
const nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS); const nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS);
@ -72,7 +73,7 @@ export const GET: RequestHandler = async ({ params }) => {
}) })
}); });
} catch (err) { } catch (err) {
console.error('Error fetching ownership info:', err); logger.error({ error: err, npub, repo }, 'Error fetching ownership info');
return error(500, err instanceof Error ? err.message : 'Failed to fetch ownership info'); return error(500, err instanceof Error ? err.message : 'Failed to fetch ownership info');
} }
}; };
@ -166,7 +167,7 @@ export const POST: RequestHandler = async ({ params, request }) => {
message: 'Ownership transfer initiated successfully' message: 'Ownership transfer initiated successfully'
}); });
} catch (err) { } catch (err) {
console.error('Error transferring ownership:', err); logger.error({ error: err, npub, repo }, 'Error transferring ownership');
return error(500, err instanceof Error ? err.message : 'Failed to transfer ownership'); return error(500, err instanceof Error ? err.message : 'Failed to transfer ownership');
} }
}; };

3
src/routes/api/repos/[npub]/[repo]/tree/+server.ts

@ -8,6 +8,7 @@ import { FileManager } from '$lib/services/git/file-manager.js';
import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; import { MaintainerService } from '$lib/services/nostr/maintainer-service.js';
import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import logger from '$lib/services/logger.js';
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
const fileManager = new FileManager(repoRoot); const fileManager = new FileManager(repoRoot);
@ -49,7 +50,7 @@ export const GET: RequestHandler = async ({ params, url, request }) => {
const files = await fileManager.listFiles(npub, repo, ref, path); const files = await fileManager.listFiles(npub, repo, ref, path);
return json(files); return json(files);
} catch (err) { } catch (err) {
console.error('Error listing files:', err); logger.error({ error: err, npub, repo, path, branch }, 'Error listing files');
return error(500, err instanceof Error ? err.message : 'Failed to list files'); return error(500, err instanceof Error ? err.message : 'Failed to list files');
} }
}; };

3
src/routes/api/repos/[npub]/[repo]/verify/+server.ts

@ -13,6 +13,7 @@ import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js';
import { KIND } from '$lib/types/nostr.js'; import { KIND } from '$lib/types/nostr.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import logger from '$lib/services/logger.js';
import { join } from 'path'; import { join } from 'path';
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
@ -149,7 +150,7 @@ export const GET: RequestHandler = async ({ params }: { params: { npub?: string;
}); });
} }
} catch (err) { } catch (err) {
console.error('Error verifying repository:', err); logger.error({ error: err, npub, repo }, 'Error verifying repository');
return error(500, err instanceof Error ? err.message : 'Failed to verify repository'); return error(500, err instanceof Error ? err.message : 'Failed to verify repository');
} }
}; };

3
src/routes/api/search/+server.ts

@ -11,6 +11,7 @@ import { FileManager } from '$lib/services/git/file-manager.js';
import { nip19 } from 'nostr-tools'; import { nip19 } from 'nostr-tools';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import logger from '$lib/services/logger.js';
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
const fileManager = new FileManager(repoRoot); const fileManager = new FileManager(repoRoot);
@ -154,7 +155,7 @@ export const GET: RequestHandler = async ({ url }) => {
total: results.repos.length + results.code.length total: results.repos.length + results.code.length
}); });
} catch (err) { } catch (err) {
console.error('Error searching:', err); logger.error({ error: err, query }, 'Error searching');
return error(500, err instanceof Error ? err.message : 'Failed to search'); return error(500, err instanceof Error ? err.message : 'Failed to search');
} }
}; };

3
src/routes/docs/nip34/+page.server.ts

@ -5,6 +5,7 @@
import { readFile } from 'fs/promises'; import { readFile } from 'fs/promises';
import { join } from 'path'; import { join } from 'path';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import logger from '$lib/services/logger.js';
export const load: PageServerLoad = async () => { export const load: PageServerLoad = async () => {
try { try {
@ -13,7 +14,7 @@ export const load: PageServerLoad = async () => {
const content = await readFile(filePath, 'utf-8'); const content = await readFile(filePath, 'utf-8');
return { content }; return { content };
} catch (error) { } catch (error) {
console.error('Error loading NIP-34.md:', error); logger.error({ error }, 'Error loading NIP-34.md');
return { content: null, error: 'Failed to load documentation' }; return { content: null, error: 'Failed to load documentation' };
} }
}; };

Loading…
Cancel
Save