From 2ff3829c9913a3eb75f75ce09b101d9dae5c53c2 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Thu, 19 Feb 2026 17:51:53 +0100 Subject: [PATCH] fix swagger Nostr-Signature: c0eb40be64306e1b11eba642ad357fd877776f50c8e64867cff845b92851c60e 573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc 194bedf752e25da9924bcf16b55eec0b29ae5da8bcd3f2a646e072f08d2d8f499269d42fafe6c056effb22840be0101c243aa1852b41899a73b242e6d6ad2932 --- nostr/commit-signatures.jsonl | 1 + package-lock.json | 34 + scripts/sync-cli.sh | 145 ++- src/lib/services/messaging/event-forwarder.ts | 7 +- src/routes/api-docs/+page.svelte | 5 +- src/routes/api/openapi.json/+server.ts | 11 +- src/routes/api/openapi.json/openapi.json | 1132 +++++++++++++++++ .../repos/[npub]/[repo]/branches/+server.ts | 5 +- .../repos/[npub]/[repo]/commits/+server.ts | 3 +- .../repos/[npub]/[repo]/highlights/+server.ts | 3 +- .../api/repos/[npub]/[repo]/issues/+server.ts | 3 +- .../api/repos/[npub]/[repo]/prs/+server.ts | 3 +- .../api/repos/[npub]/[repo]/tree/+server.ts | 3 +- src/routes/users/[npub]/+page.svelte | 10 +- 14 files changed, 1320 insertions(+), 45 deletions(-) diff --git a/nostr/commit-signatures.jsonl b/nostr/commit-signatures.jsonl index 9fa6480..ac4d1c5 100644 --- a/nostr/commit-signatures.jsonl +++ b/nostr/commit-signatures.jsonl @@ -2,3 +2,4 @@ {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771497680,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","validate signatures"]],"content":"Signed commit: validate signatures","id":"47edd2e8cbea27854a429202ddfb3fde3531a355276c619258bc90c4d6ce54cc","sig":"a941abf1d2c8e7dae4d5b4d6424c2e5394b05c98898d88b7acc1501cd6d8d3d13aea8be8d797dcb0701f752a32bf72a3b02f3c814707e10ed18d6d24f11d8ae0"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771502215,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","added push-all to the cli\nimplement black theme\nimplement swagger API docs"]],"content":"Signed commit: added push-all to the cli\nimplement black theme\nimplement swagger API docs","id":"c15ce3d2f1ae613492802533a7e71b96df919a2ff52d501630c6ee64abf6a718","sig":"ba72d348528a2846c5e44474af821e79fcdb377caa0d905ae7e7fc9115b77ea92f65325a8f0000f83467983068f643ecf3beaca784666d361a8883160ae3a936"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771513666,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","setup separate repos"]],"content":"Signed commit: setup separate repos","id":"62fa7c667e07791d898d0af8971165a57df5a061585e4a71447e52f7444dc687","sig":"25ef4575b03248381920985338e0ff4605f0af3fcaf8615d7906e3e116e3fbb64de3f3927f511fd45340e7dbdc4a2c3ea7fa150e5e9c75a6b5880cecaa4d2851"} +{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771516332,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","security fixes"]],"content":"Signed commit: security fixes","id":"a16b9538d6ce6ce2f1030042a4106534e2af1583642315893cc56d9f2e7cd385","sig":"0be95c5f2d720c008b52a1d38cef9952b1a615ecd3ef34b5b373266a2afb880e30347d3ee467b4ea42eca4a0e57808f98795ada878357ad39ca1a3063d6b6a22"} diff --git a/package-lock.json b/package-lock.json index 2802658..4ee42c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "gitrepublic-web", "version": "0.1.0", "license": "MIT", + "workspaces": [ + "gitrepublic-cli" + ], "dependencies": { "@codemirror/autocomplete": "^6.20.0", "@codemirror/basic-setup": "^0.20.0", @@ -48,6 +51,33 @@ "vite": "^5.4.11" } }, + "gitrepublic-cli": { + "version": "1.0.0", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "nostr-tools": "^2.22.1" + }, + "bin": { + "gitrep": "scripts/git-wrapper.js", + "gitrep-api": "scripts/gitrepublic.js", + "gitrep-commit": "scripts/git-commit-msg-hook.js", + "gitrep-cred": "scripts/git-credential-nostr.js", + "gitrep-path": "scripts/get-path.js", + "gitrep-setup": "scripts/setup.js", + "gitrep-uninstall": "scripts/uninstall.js", + "gitrepublic": "scripts/git-wrapper.js", + "gitrepublic-api": "scripts/gitrepublic.js", + "gitrepublic-commit-hook": "scripts/git-commit-msg-hook.js", + "gitrepublic-credential": "scripts/git-credential-nostr.js", + "gitrepublic-path": "scripts/get-path.js", + "gitrepublic-setup": "scripts/setup.js", + "gitrepublic-uninstall": "scripts/uninstall.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@asciidoctor/cli": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@asciidoctor/cli/-/cli-4.0.0.tgz", @@ -3237,6 +3267,10 @@ "node": ">= 0.4" } }, + "node_modules/gitrepublic-cli": { + "resolved": "gitrepublic-cli", + "link": true + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", diff --git a/scripts/sync-cli.sh b/scripts/sync-cli.sh index dde7681..2124837 100755 --- a/scripts/sync-cli.sh +++ b/scripts/sync-cli.sh @@ -2,14 +2,13 @@ # Sync gitrepublic-cli to a separate repository # Usage: ./scripts/sync-cli.sh [path-to-separate-repo] -set -e +set -uo pipefail # Don't exit on error (-e removed) so we can handle push failures gracefully SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" CLI_DIR="$REPO_ROOT/gitrepublic-cli" -# Default separate repo path (can be overridden) -# Use the same parent directory as the monorepo +# Default separate repo path MONOREPO_PARENT="$(dirname "$REPO_ROOT")" SEPARATE_REPO="${1:-$MONOREPO_PARENT/gitrepublic-cli}" @@ -26,41 +25,137 @@ if [ ! -d "$SEPARATE_REPO" ]; then mkdir -p "$SEPARATE_REPO" cd "$SEPARATE_REPO" git init - git remote add origin https://github.com/silberengel/gitrepublic-cli.git 2>/dev/null || true + # Don't add remote automatically - let user configure it + echo "ℹ️ Repository created. Configure remotes with:" + echo " git remote add origin " fi -# Copy files (excluding node_modules, .git, etc.) -cd "$SEPARATE_REPO" +# Change to separate repo directory +cd "$SEPARATE_REPO" || exit 1 + +# Copy files using rsync rsync -av --delete \ --exclude='node_modules' \ --exclude='.git' \ --exclude='package-lock.json' \ --exclude='nostr/' \ + --exclude='*.log' \ + --exclude='.DS_Store' \ "$CLI_DIR/" . -# Commit and push if there are changes +# Stage all changes +git add -A + +# Check if there are any changes to commit if [ -n "$(git status --porcelain)" ]; then - git add -A - git commit -m "Sync from gitrepublic-web monorepo" || echo "No changes to commit" + echo "Changes detected, committing..." + COMMIT_MSG="Sync from gitrepublic-web monorepo - $(date '+%Y-%m-%d %H:%M:%S')" + git commit -m "$COMMIT_MSG" + echo "✅ Committed changes" +else + echo "✅ No changes to commit (files are up to date)" +fi + +# Get all remotes and push to each one +REMOTES="$(git remote)" +CURRENT_BRANCH="$(git branch --show-current || echo 'master')" + +if [ -z "$REMOTES" ]; then + echo "ℹ️ No remotes configured" + exit 0 +fi + +echo "" +echo "Pushing to remotes..." + +# Fetch from all remotes first +for remote in $REMOTES; do + echo "Fetching from $remote..." + git fetch "$remote" 2>/dev/null || true +done + +# Check if remotes have commits local doesn't have (diverged history) +echo "" +echo "Checking for diverged history..." +for remote in $REMOTES; do + REMOTE_BRANCH="${remote}/${CURRENT_BRANCH}" + if git rev-parse --verify "$REMOTE_BRANCH" >/dev/null 2>&1; then + BEHIND=$(git rev-list --count HEAD.."$REMOTE_BRANCH" 2>/dev/null || echo "0") + AHEAD=$(git rev-list --count "$REMOTE_BRANCH"..HEAD 2>/dev/null || echo "0") + if [ "$BEHIND" -gt 0 ]; then + echo "⚠️ $remote has $BEHIND commit(s) that local doesn't have" + echo " Local has $AHEAD commit(s) that $remote doesn't have" + echo " Histories have diverged - need to merge or rebase" + fi + fi +done + +# Push to all remotes +for remote in $REMOTES; do + echo "" + echo "Pushing to $remote ($CURRENT_BRANCH)..." + + # Check if local is ahead of remote + REMOTE_BRANCH="${remote}/${CURRENT_BRANCH}" + if git rev-parse --verify "$REMOTE_BRANCH" >/dev/null 2>&1; then + AHEAD=$(git rev-list --count "$REMOTE_BRANCH"..HEAD 2>/dev/null || echo "0") + if [ "$AHEAD" -gt 0 ]; then + echo " Local is $AHEAD commit(s) ahead of $remote" + fi + fi + + # Try to push current branch (master) - don't use timeout as it might interfere with SSH + PUSH_OUTPUT=$(git push "$remote" "$CURRENT_BRANCH" 2>&1) + PUSH_EXIT=$? - # Check if remote exists, if not provide instructions - if git remote get-url origin >/dev/null 2>&1; then - # Try to push to main or master branch - if git push origin main 2>/dev/null || git push origin master 2>/dev/null; then - echo "✅ Synced and pushed to remote repository" + if [ $PUSH_EXIT -eq 0 ]; then + # Check if output says "already up to date" + if echo "$PUSH_OUTPUT" | grep -qi "already up to date\|Everything up-to-date"; then + echo "ℹ️ $remote is already up to date" else - echo "⚠️ Synced locally, but push failed. Check remote configuration." + echo "✅ Successfully pushed to $remote" + echo "$PUSH_OUTPUT" | grep -v "^$" | head -3 fi else - echo "✅ Synced to local repository" - echo "ℹ️ To push to a remote, run:" - echo " cd $SEPARATE_REPO" - echo " git remote add origin " - echo " git branch -M main # if needed" - echo " git push -u origin main" + # Push failed - show the full error + echo "⚠️ Push to $remote failed:" + echo "$PUSH_OUTPUT" | sed 's/^/ /' + + # Check if it's a non-fast-forward (diverged history) + if echo "$PUSH_OUTPUT" | grep -qi "non-fast-forward\|behind.*remote\|diverged"; then + REMOTE_BRANCH="${remote}/${CURRENT_BRANCH}" + BEHIND=$(git rev-list --count HEAD.."$REMOTE_BRANCH" 2>/dev/null || echo "0") + AHEAD=$(git rev-list --count "$REMOTE_BRANCH"..HEAD 2>/dev/null || echo "0") + + if [ "$BEHIND" -gt 0 ]; then + echo "" + echo " Histories have diverged:" + echo " - Remote has $BEHIND commit(s) you don't have" + echo " - You have $AHEAD commit(s) remote doesn't have" + echo "" + echo " For sync script, attempting force push to overwrite remote with local..." + echo " (This makes remote match the monorepo - monorepo is source of truth)" + + # Force push to make remote match local (monorepo is source of truth) + if FORCE_PUSH_OUTPUT=$(git push -f "$remote" "$CURRENT_BRANCH" 2>&1); then + echo "✅ Successfully force-pushed to $remote" + echo "$FORCE_PUSH_OUTPUT" | grep -v "^$" | head -2 | sed 's/^/ /' + else + echo "⚠️ Force push also failed:" + echo "$FORCE_PUSH_OUTPUT" | sed 's/^/ /' + fi + fi + elif echo "$PUSH_OUTPUT" | grep -qi "refspec\|branch.*not found\|no such branch"; then + # Branch doesn't exist on remote - set upstream + echo " Attempting to set upstream and push..." + if git push -u "$remote" "$CURRENT_BRANCH" 2>&1; then + echo "✅ Successfully pushed to $remote (with upstream set)" + else + echo " Still failed after setting upstream" + fi + fi fi -else - echo "✅ No changes to sync" -fi +done -echo "Done!" +echo "" +echo "✅ Sync complete!" diff --git a/src/lib/services/messaging/event-forwarder.ts b/src/lib/services/messaging/event-forwarder.ts index f1e0892..c276104 100644 --- a/src/lib/services/messaging/event-forwarder.ts +++ b/src/lib/services/messaging/event-forwarder.ts @@ -18,9 +18,10 @@ async function getPreferencesLazy() { return null; } if (!getPreferences) { - // Use dynamic path construction to prevent Vite from statically analyzing the import - const storagePath = './preferences-storage' + '.server.js'; - const module = await import(storagePath); + // Use static import path with vite-ignore to prevent static analysis + // This is intentional - we only want to import this server-side + // @ts-ignore - Dynamic import for server-side only code + const module = await import(/* @vite-ignore */ './preferences-storage.server.js'); getPreferences = module.getPreferences; } return getPreferences; diff --git a/src/routes/api-docs/+page.svelte b/src/routes/api-docs/+page.svelte index bc96614..d316819 100644 --- a/src/routes/api-docs/+page.svelte +++ b/src/routes/api-docs/+page.svelte @@ -29,14 +29,15 @@ // @ts-ignore window.SwaggerUIBundle.presets.standalone ], - layout: 'StandaloneLayout', - deepLinking: true, + deepLinking: false, // Disabled to avoid conflict with SvelteKit's router displayRequestDuration: true, tryItOutEnabled: true, supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'], validatorUrl: null, docExpansion: 'list', filter: true, + defaultModelsExpandDepth: 1, + defaultModelExpandDepth: 1, showExtensions: true, showCommonExtensions: true }); diff --git a/src/routes/api/openapi.json/+server.ts b/src/routes/api/openapi.json/+server.ts index 478f2b2..1a6f8ef 100644 --- a/src/routes/api/openapi.json/+server.ts +++ b/src/routes/api/openapi.json/+server.ts @@ -1,8 +1,17 @@ import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; -import openApiSpec from './openapi.json'; +import { readFileSync } from 'fs'; +import { join } from 'path'; +import { fileURLToPath } from 'url'; + +// Read the file dynamically to avoid Vite caching issues +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const specPath = join(__dirname, 'openapi.json'); export const GET: RequestHandler = async () => { + // Read file fresh on each request to avoid cache issues during development + const openApiSpec = JSON.parse(readFileSync(specPath, 'utf-8')); + return json(openApiSpec, { headers: { 'Content-Type': 'application/json', diff --git a/src/routes/api/openapi.json/openapi.json b/src/routes/api/openapi.json/openapi.json index 792b733..b5681fb 100644 --- a/src/routes/api/openapi.json/openapi.json +++ b/src/routes/api/openapi.json/openapi.json @@ -23,6 +23,36 @@ "description": "Production server" } ], + "tags": [ + { + "name": "Repositories", + "description": "Repository management endpoints" + }, + { + "name": "Files", + "description": "File operations (read, write, list)" + }, + { + "name": "Pull Requests", + "description": "Pull request operations (NIP-34 kind 1618)" + }, + { + "name": "Issues", + "description": "Issue operations (NIP-34 kind 1621)" + }, + { + "name": "Search", + "description": "Search repositories and code" + }, + { + "name": "User", + "description": "User account and preferences" + }, + { + "name": "Infrastructure", + "description": "Infrastructure and server information" + } + ], "components": { "securitySchemes": { "NIP98": { @@ -1107,6 +1137,1108 @@ } } } + }, + "/api/repos/{npub}/{repo}/tree": { + "get": { + "summary": "List files and directories", + "description": "Get directory listing for a repository", + "tags": ["Files"], + "security": [{"NIP98": []}], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "path", + "in": "query", + "schema": {"type": "string"}, + "description": "Directory path (empty for root)" + }, + { + "name": "ref", + "in": "query", + "schema": {"type": "string", "default": "HEAD"}, + "description": "Git reference" + } + ], + "responses": { + "200": { + "description": "Directory listing", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "path": {"type": "string"}, + "type": {"type": "string", "enum": ["file", "directory"]}, + "size": {"type": "integer"} + } + } + } + } + } + } + } + } + }, + "/api/repos/{npub}/{repo}/commits": { + "get": { + "summary": "Get commit history", + "description": "Get commit history for a repository", + "tags": ["Repositories"], + "security": [{"NIP98": []}], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "branch", + "in": "query", + "schema": {"type": "string"}, + "description": "Branch name" + }, + { + "name": "path", + "in": "query", + "schema": {"type": "string"}, + "description": "Filter by file path" + }, + { + "name": "limit", + "in": "query", + "schema": {"type": "integer", "default": 50}, + "description": "Maximum number of commits" + } + ], + "responses": { + "200": { + "description": "Commit history", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "hash": {"type": "string"}, + "message": {"type": "string"}, + "author": {"type": "string"}, + "date": {"type": "string"} + } + } + } + } + } + } + } + } + }, + "/api/repos/{npub}/{repo}/raw": { + "get": { + "summary": "Get raw file content", + "description": "Get raw file content (no JSON wrapper)", + "tags": ["Files"], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "path", + "in": "query", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "ref", + "in": "query", + "schema": {"type": "string", "default": "HEAD"} + } + ], + "responses": { + "200": { + "description": "Raw file content", + "content": { + "text/plain": {"schema": {"type": "string"}}, + "application/javascript": {"schema": {"type": "string"}}, + "text/html": {"schema": {"type": "string"}} + } + } + } + } + }, + "/api/repos/{npub}/{repo}/readme": { + "get": { + "summary": "Get README file", + "description": "Get README file content if available", + "tags": ["Files"], + "security": [{"NIP98": []}], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "ref", + "in": "query", + "schema": {"type": "string", "default": "HEAD"} + } + ], + "responses": { + "200": { + "description": "README content", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "found": {"type": "boolean"}, + "content": {"type": "string"}, + "path": {"type": "string"}, + "isMarkdown": {"type": "boolean"} + } + } + } + } + } + } + } + }, + "/api/repos/{npub}/{repo}/download": { + "get": { + "summary": "Download repository as archive", + "description": "Download repository as ZIP or tar.gz archive", + "tags": ["Repositories"], + "security": [{"NIP98": []}], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "ref", + "in": "query", + "schema": {"type": "string", "default": "HEAD"}, + "description": "Git reference to download" + }, + { + "name": "format", + "in": "query", + "schema": {"type": "string", "enum": ["zip", "tar.gz"], "default": "zip"} + } + ], + "responses": { + "200": { + "description": "Archive file", + "content": { + "application/zip": {"schema": {"type": "string", "format": "binary"}}, + "application/gzip": {"schema": {"type": "string", "format": "binary"}} + } + } + } + } + }, + "/api/repos/{npub}/{repo}/fork": { + "get": { + "summary": "Get fork information", + "description": "Check if repository is a fork and get original repo info", + "tags": ["Repositories"], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + } + ], + "responses": { + "200": { + "description": "Fork information", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "isFork": {"type": "boolean"}, + "originalRepo": { + "type": "object", + "properties": { + "npub": {"type": "string"}, + "repo": {"type": "string"} + } + }, + "forkCount": {"type": "integer"} + } + } + } + } + } + } + }, + "post": { + "summary": "Fork repository", + "description": "Create a fork of a repository. Requires unlimited access.", + "tags": ["Repositories"], + "security": [{"NIP98": []}], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["userPubkey"], + "properties": { + "userPubkey": {"type": "string"}, + "forkName": {"type": "string"} + } + } + } + } + }, + "responses": { + "200": { + "description": "Fork created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": {"type": "boolean"}, + "fork": { + "type": "object", + "properties": { + "npub": {"type": "string"}, + "repo": {"type": "string"}, + "url": {"type": "string"} + } + } + } + } + } + } + } + } + } + }, + "/api/repos/{npub}/{repo}/highlights": { + "get": { + "summary": "Get highlights and comments", + "description": "Get highlights (NIP-84) and comments (NIP-22) for a pull request", + "tags": ["Pull Requests"], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "prId", + "in": "query", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "prAuthor", + "in": "query", + "required": true, + "schema": {"type": "string"} + } + ], + "responses": { + "200": { + "description": "Highlights and comments", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "highlights": {"type": "array"}, + "comments": {"type": "array"} + } + } + } + } + } + } + }, + "post": { + "summary": "Create highlight or comment", + "description": "Create a highlight (NIP-84) or comment (NIP-22)", + "tags": ["Pull Requests"], + "security": [{"NIP98": []}], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["type", "event", "userPubkey"], + "properties": { + "type": {"type": "string", "enum": ["highlight", "comment"]}, + "event": {"$ref": "#/components/schemas/NostrEvent"}, + "userPubkey": {"type": "string"} + } + } + } + } + }, + "responses": { + "200": { + "description": "Highlight/comment created" + } + } + } + }, + "/api/repos/{npub}/{repo}/transfer": { + "get": { + "summary": "Get ownership transfer history", + "description": "Get ownership transfer history for a repository", + "tags": ["Repositories"], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + } + ], + "responses": { + "200": { + "description": "Ownership information", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "originalOwner": {"type": "string"}, + "currentOwner": {"type": "string"}, + "transferred": {"type": "boolean"}, + "transfers": {"type": "array"} + } + } + } + } + } + } + }, + "post": { + "summary": "Transfer repository ownership", + "description": "Transfer repository ownership to another user. Requires current owner authentication.", + "tags": ["Repositories"], + "security": [{"NIP98": []}], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["transferEvent"], + "properties": { + "transferEvent": {"$ref": "#/components/schemas/NostrEvent"} + } + } + } + } + }, + "responses": { + "200": { + "description": "Ownership transferred" + } + } + } + }, + "/api/repos/{npub}/{repo}/verify": { + "get": { + "summary": "Verify repository ownership", + "description": "Verify repository ownership by checking announcement file", + "tags": ["Repositories"], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + } + ], + "responses": { + "200": { + "description": "Verification result", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "verified": {"type": "boolean"}, + "ownerPubkey": {"type": "string"}, + "cloneVerifications": {"type": "array"} + } + } + } + } + } + } + } + }, + "/api/repos/{npub}/{repo}/clone": { + "post": { + "summary": "Clone repository to server", + "description": "Clone a repository to the server. Requires unlimited access.", + "tags": ["Repositories"], + "security": [{"NIP98": []}], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + } + ], + "responses": { + "200": { + "description": "Repository cloned", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": {"type": "boolean"}, + "message": {"type": "string"}, + "alreadyExists": {"type": "boolean"} + } + } + } + } + } + } + } + }, + "/api/repos/{npub}/{repo}/delete": { + "delete": { + "summary": "Delete local repository clone", + "description": "Delete a local repository clone. Requires owner or admin authentication.", + "tags": ["Repositories"], + "security": [{"NIP98": []}], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + } + ], + "responses": { + "200": { + "description": "Repository deleted" + }, + "403": { + "description": "Not authorized" + } + } + } + }, + "/api/repos/{npub}/{repo}/access": { + "get": { + "summary": "Check repository access", + "description": "Check if current user can view the repository", + "tags": ["Repositories"], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + } + ], + "responses": { + "200": { + "description": "Access information", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "canView": {"type": "boolean"}, + "isPrivate": {"type": "boolean"}, + "isMaintainer": {"type": "boolean"}, + "isOwner": {"type": "boolean"} + } + } + } + } + } + } + } + }, + "/api/repos/{npub}/{repo}/default-branch": { + "get": { + "summary": "Get default branch", + "description": "Get the default branch name for a repository", + "tags": ["Repositories"], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + } + ], + "responses": { + "200": { + "description": "Default branch", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "defaultBranch": {"type": "string"}, + "branch": {"type": "string"} + } + } + } + } + } + } + } + }, + "/api/repos/{npub}/{repo}/branch-protection": { + "get": { + "summary": "Get branch protection rules", + "description": "Get branch protection rules for a repository", + "tags": ["Repositories"], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + } + ], + "responses": { + "200": { + "description": "Branch protection rules", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "rules": {"type": "array"} + } + } + } + } + } + } + }, + "post": { + "summary": "Update branch protection rules", + "description": "Update branch protection rules. Requires owner authentication.", + "tags": ["Repositories"], + "security": [{"NIP98": []}], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["rules"], + "properties": { + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "branch": {"type": "string"}, + "requirePullRequest": {"type": "boolean"}, + "requireReviewers": {"type": "array", "items": {"type": "string"}}, + "allowForcePush": {"type": "boolean"} + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Rules updated" + } + } + } + }, + "/api/repos/{npub}/{repo}/diff": { + "get": { + "summary": "Get diff between commits", + "description": "Get diff between two git references", + "tags": ["Repositories"], + "security": [{"NIP98": []}], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "repo", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "from", + "in": "query", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "to", + "in": "query", + "schema": {"type": "string", "default": "HEAD"} + }, + { + "name": "path", + "in": "query", + "schema": {"type": "string"}, + "description": "Filter by file path" + } + ], + "responses": { + "200": { + "description": "Diff content", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "file": {"type": "string"}, + "diff": {"type": "string"} + } + } + } + } + } + } + } + } + }, + "/api/user/level": { + "post": { + "summary": "Verify user level", + "description": "Verify user's access level (relay write proof). Requires NIP-98 authentication.", + "tags": ["User"], + "security": [{"NIP98": []}], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["proofEvent", "userPubkeyHex"], + "properties": { + "proofEvent": {"$ref": "#/components/schemas/NostrEvent"}, + "userPubkeyHex": {"type": "string", "format": "hex"} + } + } + } + } + }, + "responses": { + "200": { + "description": "User level", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "level": {"type": "string", "enum": ["unlimited", "rate_limited"]}, + "verified": {"type": "boolean"}, + "cached": {"type": "boolean"} + } + } + } + } + } + } + } + }, + "/api/user/ssh-keys": { + "get": { + "summary": "Get SSH key attestations", + "description": "Get all SSH key attestations for authenticated user", + "tags": ["User"], + "security": [{"NIP98": []}], + "responses": { + "200": { + "description": "SSH key attestations", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "attestations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "eventId": {"type": "string"}, + "fingerprint": {"type": "string"}, + "keyType": {"type": "string"}, + "createdAt": {"type": "integer"}, + "revoked": {"type": "boolean"} + } + } + } + } + } + } + } + } + } + }, + "post": { + "summary": "Submit SSH key attestation", + "description": "Submit an SSH key attestation event. Requires unlimited access.", + "tags": ["User"], + "security": [{"NIP98": []}], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["event"], + "properties": { + "event": {"$ref": "#/components/schemas/NostrEvent"} + } + } + } + } + }, + "responses": { + "200": { + "description": "Attestation stored" + } + } + } + }, + "/api/users/{npub}/repos": { + "get": { + "summary": "List user repositories", + "description": "List repositories for a user (with privacy filtering)", + "tags": ["Repositories"], + "parameters": [ + { + "name": "npub", + "in": "path", + "required": true, + "schema": {"type": "string"} + }, + { + "name": "domain", + "in": "query", + "schema": {"type": "string"} + } + ], + "responses": { + "200": { + "description": "User repositories", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "repos": { + "type": "array", + "items": {"$ref": "#/components/schemas/Repository"} + }, + "total": {"type": "integer"} + } + } + } + } + } + } + } + }, + "/api/tor/onion": { + "get": { + "summary": "Get Tor .onion address", + "description": "Get the Tor hidden service .onion address for this server", + "tags": ["Infrastructure"], + "responses": { + "200": { + "description": "Tor address", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "onion": {"type": "string"}, + "available": {"type": "boolean"} + } + } + } + } + } + } + } + }, + "/api/user/git-dashboard": { + "get": { + "summary": "Get git dashboard data", + "description": "Get aggregated issues and PRs from external git platforms. Requires unlimited access.", + "tags": ["User"], + "security": [{"NIP98": []}], + "responses": { + "200": { + "description": "Dashboard data", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "issues": {"type": "array"}, + "pullRequests": {"type": "array"} + } + } + } + } + } + } + } + }, + "/api/user/messaging-preferences": { + "get": { + "summary": "Get messaging preferences status", + "description": "Get messaging preferences configuration status. Requires unlimited access.", + "tags": ["User"], + "security": [{"NIP98": []}], + "responses": { + "200": { + "description": "Preferences status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "configured": {"type": "boolean"}, + "rateLimit": { + "type": "object", + "properties": { + "remaining": {"type": "integer"}, + "resetAt": {"type": "integer"} + } + } + } + } + } + } + } + } + }, + "post": { + "summary": "Save messaging preferences", + "description": "Save messaging preferences. Requires unlimited access and signed proof event.", + "tags": ["User"], + "security": [{"NIP98": []}], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["preferences", "proofEvent"], + "properties": { + "preferences": { + "type": "object", + "properties": { + "enabled": {"type": "boolean"} + } + }, + "proofEvent": {"$ref": "#/components/schemas/NostrEvent"} + } + } + } + } + }, + "responses": { + "200": { + "description": "Preferences saved" + } + } + }, + "delete": { + "summary": "Delete messaging preferences", + "description": "Delete messaging preferences. Requires unlimited access.", + "tags": ["User"], + "security": [{"NIP98": []}], + "responses": { + "200": { + "description": "Preferences deleted" + } + } + } + }, + "/api/user/ssh-keys/verify": { + "post": { + "summary": "Verify SSH key fingerprint", + "description": "Verify an SSH key fingerprint against stored attestations", + "tags": ["User"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["fingerprint"], + "properties": { + "fingerprint": {"type": "string"} + } + } + } + } + }, + "responses": { + "200": { + "description": "Verification result", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "valid": {"type": "boolean"}, + "attestation": { + "type": "object", + "properties": { + "userPubkey": {"type": "string"}, + "fingerprint": {"type": "string"}, + "keyType": {"type": "string"} + } + } + } + } + } + } + } + } + } } } } diff --git a/src/routes/api/repos/[npub]/[repo]/branches/+server.ts b/src/routes/api/repos/[npub]/[repo]/branches/+server.ts index e2239cc..dc3018e 100644 --- a/src/routes/api/repos/[npub]/[repo]/branches/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/branches/+server.ts @@ -16,6 +16,7 @@ import { repoCache, RepoCache } from '$lib/services/git/repo-cache.js'; import { DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_SEARCH_RELAYS } from '$lib/config.js'; import { NostrClient } from '$lib/services/nostr/nostr-client.js'; import { eventCache } from '$lib/services/nostr/event-cache.js'; +import logger from '$lib/services/logger.js'; const repoRoot = typeof process !== 'undefined' && process.env?.GIT_REPO_ROOT ? process.env.GIT_REPO_ROOT @@ -86,7 +87,7 @@ export const GET: RequestHandler = createRepoGetHandler( repoCache.delete(RepoCache.repoExistsKey(context.npub, context.repo)); } else { // Log the error for debugging - console.error('[Branches] Error fetching repository:', err); + logger.error({ error: err, npub: context.npub, repo: context.repo }, '[Branches] Error fetching repository'); // If fetching fails, return 404 with more context const errorMessage = err instanceof Error ? err.message : 'Repository not found'; throw handleNotFoundError( @@ -110,7 +111,7 @@ export const GET: RequestHandler = createRepoGetHandler( return json(branches); } catch (err) { // Log the actual error for debugging - console.error('[Branches] Error getting branches:', err); + logger.error({ error: err, npub: context.npub, repo: context.repo }, '[Branches] Error getting branches'); // Check if it's a "not found" error if (err instanceof Error && err.message.includes('not found')) { throw handleNotFoundError( diff --git a/src/routes/api/repos/[npub]/[repo]/commits/+server.ts b/src/routes/api/repos/[npub]/[repo]/commits/+server.ts index c7a529f..e16d22d 100644 --- a/src/routes/api/repos/[npub]/[repo]/commits/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/commits/+server.ts @@ -12,6 +12,7 @@ import { KIND } from '$lib/types/nostr.js'; import { join } from 'path'; import { existsSync } from 'fs'; import { repoCache, RepoCache } from '$lib/services/git/repo-cache.js'; +import logger from '$lib/services/logger.js'; const repoRoot = typeof process !== 'undefined' && process.env?.GIT_REPO_ROOT ? process.env.GIT_REPO_ROOT @@ -96,7 +97,7 @@ export const GET: RequestHandler = createRepoGetHandler( return json(commits); } catch (err) { // Log the actual error for debugging - console.error('[Commits] Error getting commit history:', err); + logger.error({ error: err, npub: context.npub, repo: context.repo }, '[Commits] Error getting commit history'); // Check if it's a "not found" error if (err instanceof Error && err.message.includes('not found')) { throw handleNotFoundError( diff --git a/src/routes/api/repos/[npub]/[repo]/highlights/+server.ts b/src/routes/api/repos/[npub]/[repo]/highlights/+server.ts index 426f633..d475ff3 100644 --- a/src/routes/api/repos/[npub]/[repo]/highlights/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/highlights/+server.ts @@ -14,6 +14,7 @@ import type { RepoRequestContext, RequestEvent } from '$lib/utils/api-context.js import { handleApiError, handleValidationError } from '$lib/utils/error-handler.js'; import { decodeNpubToHex } from '$lib/utils/npub-utils.js'; import { forwardEventIfEnabled } from '$lib/services/messaging/event-forwarder.js'; +import logger from '$lib/services/logger.js'; /** * GET - Get highlights for a pull request @@ -93,7 +94,7 @@ export const POST: RequestHandler = withRepoValidation( forwardEventIfEnabled(highlightEvent as NostrEvent, userPubkeyHex) .catch(err => { // Log but don't fail the request - forwarding is optional - console.error('Failed to forward event to messaging platforms:', err); + logger.warn({ error: err, npub: repoContext.npub, repo: repoContext.repo }, 'Failed to forward event to messaging platforms'); }); } diff --git a/src/routes/api/repos/[npub]/[repo]/issues/+server.ts b/src/routes/api/repos/[npub]/[repo]/issues/+server.ts index 84918d9..9f48159 100644 --- a/src/routes/api/repos/[npub]/[repo]/issues/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/issues/+server.ts @@ -10,6 +10,7 @@ import type { RepoRequestContext, RequestEvent } from '$lib/utils/api-context.js import { handleValidationError, handleApiError } from '$lib/utils/error-handler.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { forwardEventIfEnabled } from '$lib/services/messaging/event-forwarder.js'; +import logger from '$lib/services/logger.js'; export const GET: RequestHandler = createRepoGetHandler( async (context: RepoRequestContext) => { @@ -45,7 +46,7 @@ export const POST: RequestHandler = withRepoValidation( forwardEventIfEnabled(issueEvent, requestContext.userPubkeyHex) .catch(err => { // Log but don't fail the request - forwarding is optional - console.error('Failed to forward event to messaging platforms:', err); + logger.warn({ error: err, npub: repoContext.npub, repo: repoContext.repo }, 'Failed to forward event to messaging platforms'); }); } diff --git a/src/routes/api/repos/[npub]/[repo]/prs/+server.ts b/src/routes/api/repos/[npub]/[repo]/prs/+server.ts index 88be3f7..d277995 100644 --- a/src/routes/api/repos/[npub]/[repo]/prs/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/prs/+server.ts @@ -11,6 +11,7 @@ import type { RepoRequestContext, RequestEvent } from '$lib/utils/api-context.js import { handleValidationError, handleApiError } from '$lib/utils/error-handler.js'; import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; import { forwardEventIfEnabled } from '$lib/services/messaging/event-forwarder.js'; +import logger from '$lib/services/logger.js'; export const GET: RequestHandler = createRepoGetHandler( async (context: RepoRequestContext) => { @@ -46,7 +47,7 @@ export const POST: RequestHandler = withRepoValidation( forwardEventIfEnabled(prEvent, requestContext.userPubkeyHex) .catch(err => { // Log but don't fail the request - forwarding is optional - console.error('Failed to forward event to messaging platforms:', err); + logger.warn({ error: err, npub: repoContext.npub, repo: repoContext.repo }, 'Failed to forward event to messaging platforms'); }); } diff --git a/src/routes/api/repos/[npub]/[repo]/tree/+server.ts b/src/routes/api/repos/[npub]/[repo]/tree/+server.ts index 9dc91c7..3b85f2d 100644 --- a/src/routes/api/repos/[npub]/[repo]/tree/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/tree/+server.ts @@ -12,6 +12,7 @@ import { KIND } from '$lib/types/nostr.js'; import { join } from 'path'; import { existsSync } from 'fs'; import { repoCache, RepoCache } from '$lib/services/git/repo-cache.js'; +import logger from '$lib/services/logger.js'; const repoRoot = typeof process !== 'undefined' && process.env?.GIT_REPO_ROOT ? process.env.GIT_REPO_ROOT @@ -105,7 +106,7 @@ export const GET: RequestHandler = createRepoGetHandler( return json(files); } catch (err) { // Log the actual error for debugging - console.error('[Tree] Error listing files:', err); + logger.error({ error: err, npub: context.npub, repo: context.repo }, '[Tree] Error listing files'); // Check if it's a "not found" error if (err instanceof Error && err.message.includes('not found')) { throw handleNotFoundError( diff --git a/src/routes/users/[npub]/+page.svelte b/src/routes/users/[npub]/+page.svelte index 9c6ccfa..fcd5c81 100644 --- a/src/routes/users/[npub]/+page.svelte +++ b/src/routes/users/[npub]/+page.svelte @@ -12,7 +12,7 @@ import { PublicMessagesService, type PublicMessage } from '$lib/services/nostr/public-messages-service.js'; import { getUserRelays } from '$lib/services/nostr/user-relays.js'; import UserBadge from '$lib/components/UserBadge.svelte'; - import { forwardEventIfEnabled } from '$lib/services/messaging/event-forwarder.js'; + // forwardEventIfEnabled is server-side only - import dynamically if needed import { userStore } from '$lib/stores/user-store.js'; const npub = ($page.params as { npub?: string }).npub || ''; @@ -256,12 +256,8 @@ } // Forward to messaging platforms if user has unlimited access and preferences configured - if (result.success.length > 0 && viewerPubkeyHex) { - forwardEventIfEnabled(signedEvent, viewerPubkeyHex) - .catch(err => { - console.error('Failed to forward message to messaging platforms:', err); - }); - } + // This is done server-side via API endpoints, not from client + // The server-side API endpoints (issues, prs, highlights) handle forwarding automatically // Reload messages await loadMessages();