Browse Source

block git commands on uncloned repos

main
Silberengel 4 weeks ago
parent
commit
d56286adf9
  1. 27
      src/routes/api/repos/[npub]/[repo]/file/+server.ts
  2. 106
      src/routes/repos/[npub]/[repo]/+page.svelte

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

@ -189,8 +189,33 @@ export const POST: RequestHandler = async ({ params, url, request }: { params: {
return error(401, 'Authentication required. Please provide userPubkey.'); return error(401, 'Authentication required. Please provide userPubkey.');
} }
// Check if repo exists locally
if (!fileManager.repoExists(npub, repo)) { if (!fileManager.repoExists(npub, repo)) {
return error(404, 'Repository not found'); // Try to fetch announcement to see if repo exists in Nostr
let repoOwnerPubkey: string;
try {
repoOwnerPubkey = requireNpubHex(npub);
} catch {
return error(400, 'Invalid npub format');
}
// Fetch repository announcement from Nostr
const events = await nostrClient.fetchEvents([
{
kinds: [KIND.REPO_ANNOUNCEMENT],
authors: [repoOwnerPubkey],
'#d': [repo],
limit: 1
}
]);
if (events.length > 0) {
// Repository exists in Nostr but is not cloned locally
// For file editing, we need a local clone
return error(404, 'Repository is not cloned locally. To edit files, the repository must be cloned to the server first. Please use the "Clone to Server" button if you have unlimited access, or contact a server administrator.');
} else {
return error(404, 'Repository not found');
}
} }
// Check if user is a maintainer // Check if user is a maintainer

106
src/routes/repos/[npub]/[repo]/+page.svelte

@ -138,6 +138,10 @@
let checkingCloneStatus = $state(false); let checkingCloneStatus = $state(false);
let cloning = $state(false); let cloning = $state(false);
// Helper: Check if repo needs to be cloned for write operations
const needsClone = $derived(isRepoCloned === false);
const cloneTooltip = 'Please clone this repo to use this feature.';
// Verification status // Verification status
let verificationStatus = $state<{ verified: boolean; error?: string; message?: string } | null>(null); let verificationStatus = $state<{ verified: boolean; error?: string; message?: string } | null>(null);
let showVerificationDialog = $state(false); let showVerificationDialog = $state(false);
@ -1292,10 +1296,8 @@
await checkMaintainerStatus(); await checkMaintainerStatus();
await loadBookmarkStatus(); await loadBookmarkStatus();
// Check clone status if user has unlimited access // Check clone status (needed to disable write operations)
if (hasUnlimitedAccess($userStore.userLevel)) { await checkCloneStatus();
await checkCloneStatus();
}
await checkVerification(); await checkVerification();
await loadReadme(); await loadReadme();
await loadForkInfo(); await loadForkInfo();
@ -1766,8 +1768,9 @@
}); });
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json().catch(() => ({ message: response.statusText }));
throw new Error(errorData.message || 'Failed to save file'); const errorMessage = errorData.message || errorData.error || 'Failed to save file';
throw new Error(errorMessage);
} }
// Reload file to get updated content // Reload file to get updated content
@ -2400,10 +2403,15 @@
</button> </button>
{/if} {/if}
{#if isMaintainer} {#if isMaintainer}
<button onclick={() => { <button
if (!userPubkey || !isMaintainer) return; onclick={() => {
showCreateBranchDialog = true; if (!userPubkey || !isMaintainer || needsClone) return;
}} class="create-branch-button">+ New Branch</button> showCreateBranchDialog = true;
}}
class="create-branch-button"
disabled={needsClone}
title={needsClone ? cloneTooltip : 'Create a new branch'}
>+ New Branch</button>
{/if} {/if}
{/if} {/if}
</div> </div>
@ -2503,10 +2511,15 @@
<button onclick={handleBack} class="back-button">← Back</button> <button onclick={handleBack} class="back-button">← Back</button>
{/if} {/if}
{#if userPubkey && isMaintainer} {#if userPubkey && isMaintainer}
<button onclick={() => { <button
if (!userPubkey || !isMaintainer) return; onclick={() => {
showCreateFileDialog = true; if (!userPubkey || !isMaintainer || needsClone) return;
}} class="create-file-button">+ New File</button> showCreateFileDialog = true;
}}
class="create-file-button"
disabled={needsClone}
title={needsClone ? cloneTooltip : 'Create a new file'}
>+ New File</button>
{/if} {/if}
<button <button
onclick={() => showFileListOnMobile = !showFileListOnMobile} onclick={() => showFileListOnMobile = !showFileListOnMobile}
@ -2539,7 +2552,15 @@
{/if} {/if}
</button> </button>
{#if userPubkey && isMaintainer && file.type === 'file'} {#if userPubkey && isMaintainer && file.type === 'file'}
<button onclick={() => deleteFile(file.path)} class="delete-file-button" title="Delete file"> <button
onclick={() => {
if (needsClone) return;
deleteFile(file.path);
}}
class="delete-file-button"
disabled={needsClone}
title={needsClone ? cloneTooltip : 'Delete file'}
>
<img src="/icons/x.svg" alt="Delete" class="icon-small" /> <img src="/icons/x.svg" alt="Delete" class="icon-small" />
</button> </button>
{/if} {/if}
@ -2589,10 +2610,15 @@
<div class="tags-header"> <div class="tags-header">
<h2>Tags</h2> <h2>Tags</h2>
{#if userPubkey && isMaintainer} {#if userPubkey && isMaintainer}
<button onclick={() => { <button
if (!userPubkey || !isMaintainer) return; onclick={() => {
showCreateTagDialog = true; if (!userPubkey || !isMaintainer || needsClone) return;
}} class="create-tag-button">+ New Tag</button> showCreateTagDialog = true;
}}
class="create-tag-button"
disabled={needsClone}
title={needsClone ? cloneTooltip : 'Create a new tag'}
>+ New Tag</button>
{/if} {/if}
</div> </div>
{#if tags.length === 0} {#if tags.length === 0}
@ -2739,10 +2765,15 @@
<span class="unsaved-indicator">● Unsaved changes</span> <span class="unsaved-indicator">● Unsaved changes</span>
{/if} {/if}
{#if isMaintainer} {#if isMaintainer}
<button onclick={() => { <button
if (!userPubkey || !isMaintainer) return; onclick={() => {
showCommitDialog = true; if (!userPubkey || !isMaintainer || needsClone) return;
}} disabled={!hasChanges || saving} class="save-button"> showCommitDialog = true;
}}
disabled={!hasChanges || saving || needsClone}
class="save-button"
title={needsClone ? cloneTooltip : (hasChanges ? 'Save changes' : 'No changes to save')}
>
{saving ? 'Saving...' : 'Save'} {saving ? 'Saving...' : 'Save'}
</button> </button>
{:else if userPubkey} {:else if userPubkey}
@ -2771,6 +2802,7 @@
content={editedContent} content={editedContent}
language={fileLanguage} language={fileLanguage}
onChange={handleContentChange} onChange={handleContentChange}
readOnly={needsClone}
/> />
{:else} {:else}
<div class="read-only-editor"> <div class="read-only-editor">
@ -3207,7 +3239,12 @@
</label> </label>
<div class="modal-actions"> <div class="modal-actions">
<button onclick={() => showCreateFileDialog = false} class="cancel-button">Cancel</button> <button onclick={() => showCreateFileDialog = false} class="cancel-button">Cancel</button>
<button onclick={createFile} disabled={!newFileName.trim() || saving} class="save-button"> <button
onclick={createFile}
disabled={!newFileName.trim() || saving || needsClone}
class="save-button"
title={needsClone ? cloneTooltip : ''}
>
{saving ? 'Creating...' : 'Create'} {saving ? 'Creating...' : 'Create'}
</button> </button>
</div> </div>
@ -3249,7 +3286,12 @@
</label> </label>
<div class="modal-actions"> <div class="modal-actions">
<button onclick={() => showCreateBranchDialog = false} class="cancel-button">Cancel</button> <button onclick={() => showCreateBranchDialog = false} class="cancel-button">Cancel</button>
<button onclick={createBranch} disabled={!newBranchName.trim() || saving} class="save-button"> <button
onclick={createBranch}
disabled={!newBranchName.trim() || saving || needsClone}
class="save-button"
title={needsClone ? cloneTooltip : ''}
>
{saving ? 'Creating...' : 'Create Branch'} {saving ? 'Creating...' : 'Create Branch'}
</button> </button>
</div> </div>
@ -3290,7 +3332,12 @@
</label> </label>
<div class="modal-actions"> <div class="modal-actions">
<button onclick={() => showCreateTagDialog = false} class="cancel-button">Cancel</button> <button onclick={() => showCreateTagDialog = false} class="cancel-button">Cancel</button>
<button onclick={createTag} disabled={!newTagName.trim() || saving} class="save-button"> <button
onclick={createTag}
disabled={!newTagName.trim() || saving || needsClone}
class="save-button"
title={needsClone ? cloneTooltip : ''}
>
{saving ? 'Creating...' : 'Create Tag'} {saving ? 'Creating...' : 'Create Tag'}
</button> </button>
</div> </div>
@ -3506,7 +3553,12 @@
</label> </label>
<div class="modal-actions"> <div class="modal-actions">
<button onclick={() => showCommitDialog = false} class="cancel-button">Cancel</button> <button onclick={() => showCommitDialog = false} class="cancel-button">Cancel</button>
<button onclick={saveFile} disabled={!commitMessage.trim() || saving} class="save-button"> <button
onclick={saveFile}
disabled={!commitMessage.trim() || saving || needsClone}
class="save-button"
title={needsClone ? cloneTooltip : ''}
>
{saving ? 'Saving...' : 'Commit & Save'} {saving ? 'Saving...' : 'Commit & Save'}
</button> </button>
</div> </div>

Loading…
Cancel
Save