diff --git a/.gitignore b/.gitignore index 95fd55f..edaab24 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,10 @@ .nuxt .nitro .cache +.dev-test +.dev-downloads +.vitepress/cache +doc/node_modules dist # Agents @@ -22,6 +26,11 @@ logs .fleet .idea +# Secrets +CREDENTIALS.txt +secrets/ +docker/pgbouncer/userlist.txt + # Local env files .env .env.* diff --git a/.gitignore.public b/.gitignore.public index 95fd55f..edaab24 100644 --- a/.gitignore.public +++ b/.gitignore.public @@ -4,6 +4,10 @@ .nuxt .nitro .cache +.dev-test +.dev-downloads +.vitepress/cache +doc/node_modules dist # Agents @@ -22,6 +26,11 @@ logs .fleet .idea +# Secrets +CREDENTIALS.txt +secrets/ +docker/pgbouncer/userlist.txt + # Local env files .env .env.* diff --git a/README.md b/README.md index 0ad7a8c..a1d2371 100644 --- a/README.md +++ b/README.md @@ -391,18 +391,6 @@ Trackarr is built on the shoulders of giants. We'd like to thank the following o --- ---- - ---- - ---- - ---- - ---- - ---- - ## 👥 Contributors @@ -411,7 +399,8 @@ Thanks to all our contributors! Sorted by number of commits. | Avatar | Contributor | Commits | | :---------------------------------------------------------------------------------------------------------------: | --------------------------------------- | :-----: | -| | **[IkiaeM](https://github.com/IkiaeM)** | 2 | +| | **[Dim145](https://github.com/Dim145)** | 4 | +| | **[IkiaeM](https://github.com/IkiaeM)** | 4 | diff --git a/app/components/EditTorrentModal.vue b/app/components/EditTorrentModal.vue new file mode 100644 index 0000000..529c085 --- /dev/null +++ b/app/components/EditTorrentModal.vue @@ -0,0 +1,437 @@ + + + + + diff --git a/app/pages/torrents/[hash].vue b/app/pages/torrents/[hash].vue index ce6293e..9e64384 100644 --- a/app/pages/torrents/[hash].vue +++ b/app/pages/torrents/[hash].vue @@ -27,8 +27,34 @@

{{ torrent.name }}

+ +
+ + {{ torrent.category.name }} + +
-
+
+ + + +
+ + + + + + +
+
+
+ +

+ Confirm Delete +

+
+
+

+ Are you sure you want to delete this torrent? This action cannot + be undone. +

+

+ {{ torrent.name }} +

+
+ {{ deleteError }} +
+
+ + +
+
+
+
+
@@ -197,7 +292,7 @@ import { marked } from 'marked'; interface Peer { - ip: string; + id: string; port: number; isSeeder: boolean; uploaded: number; @@ -205,12 +300,21 @@ interface Peer { lastSeen: string; } +interface Category { + id: string; + name: string; + slug: string; +} + interface TorrentDetail { id: string; infoHash: string; name: string; size: number; description: string | null; + uploaderId: string | null; + categoryId: string | null; + category: Category | null; createdAt: string; stats: { seeders: number; @@ -223,9 +327,41 @@ interface TorrentDetail { const route = useRoute(); const hash = route.params.hash as string; -const { data: torrent, error } = await useFetch( - `/api/torrents/${hash}` -); +const { + data: torrent, + error, + refresh, +} = await useFetch(`/api/torrents/${hash}`); + +// Get current user session +const { loggedIn, user } = useUserSession(); + +// Edit/delete state +const showEditModal = ref(false); +const showDeleteConfirm = ref(false); +const isDeleting = ref(false); +const deleteError = ref(null); + +// Compute permissions +const canEdit = computed(() => { + if (!loggedIn.value || !user.value) return false; + const isOwner = torrent.value?.uploaderId === user.value.id; + return isOwner || user.value.isAdmin || user.value.isModerator; +}); + +const canDelete = computed(() => { + if (!loggedIn.value || !user.value) return false; + const isOwner = torrent.value?.uploaderId === user.value.id; + return isOwner || user.value.isAdmin || user.value.isModerator; +}); + +// Editable data for modal +const editableTorrent = computed(() => ({ + infoHash: torrent.value?.infoHash || '', + name: torrent.value?.name || '', + description: torrent.value?.description || null, + categoryId: torrent.value?.categoryId || null, +})); const renderedDescription = computed(() => { if (!torrent.value?.description) return ''; @@ -239,6 +375,37 @@ if (error.value || !torrent.value) { async function copyHash() { await navigator.clipboard.writeText(torrent.value!.infoHash); } + +function confirmDelete() { + deleteError.value = null; + showDeleteConfirm.value = true; +} + +async function deleteTorrent() { + isDeleting.value = true; + deleteError.value = null; + + try { + await $fetch(`/api/torrents/${torrent.value!.infoHash}`, { + method: 'DELETE', + }); + // Navigate back to torrents list + navigateTo('/torrents'); + } catch (err: unknown) { + const fetchError = err as { data?: { message?: string }; message?: string }; + deleteError.value = + fetchError.data?.message || + fetchError.message || + 'Failed to delete torrent'; + } finally { + isDeleting.value = false; + } +} + +async function handleSaved() { + // Refresh torrent data + await refresh(); +}