diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index ddabff1..68cf787 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -111,14 +111,14 @@ jobs: run: bun build server/organize-worker.ts --target node --outfile organize-worker.js - name: Package archive (Windows) - if: matrix.target == 'windows-x64' - run: Compress-Archive -Path ${{ matrix.binary }}, organize-worker.js, public -DestinationPath ${{ matrix.archive }} + if: matrix.target == 'windows-x64' || matrix.target == 'windows-x64-legacy' + run: Compress-Archive -Path ${{ matrix.binary }}, organize-worker.js, public, version.txt -DestinationPath ${{ matrix.archive }} shell: pwsh - name: Package archive (Unix) - if: matrix.target != 'windows-x64' + if: matrix.target != 'windows-x64' && matrix.target != 'windows-x64-legacy' run: | - zip -r ${{ matrix.archive }} ${{ matrix.binary }} organize-worker.js public/ + zip -r ${{ matrix.archive }} ${{ matrix.binary }} organize-worker.js public/ version.txt - name: Upload archive to Release uses: softprops/action-gh-release@v2 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 35866e9..77b193c 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -9,8 +9,28 @@ name: release-please jobs: release-please: runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} steps: - uses: googleapis/release-please-action@v4 + id: release with: token: ${{ secrets.FINED_TOKEN }} - release-type: simple \ No newline at end of file + release-type: simple + + # Quand une release est créée, on écrit version.txt et on le committe + - uses: actions/checkout@v4 + if: ${{ steps.release.outputs.release_created }} + with: + token: ${{ secrets.FINED_TOKEN }} + + - name: Write version.txt + if: ${{ steps.release.outputs.release_created }} + run: | + echo "${{ steps.release.outputs.tag_name }}" > version.txt + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add version.txt + git commit -m "chore: update version.txt to ${{ steps.release.outputs.tag_name }}" + git push \ No newline at end of file diff --git a/server/index.ts b/server/index.ts index eea00af..6680408 100644 --- a/server/index.ts +++ b/server/index.ts @@ -480,6 +480,18 @@ app.get('/api/system', requireAuth, (_req, res) => { res.json({ isDocker: fs.existsSync('/.dockerenv') }) }) +app.get('/api/version', (_req, res) => { + try { + const versionPath = path.join(BASE_DIR, 'version.txt') + const version = fs.existsSync(versionPath) + ? fs.readFileSync(versionPath, 'utf-8').trim() + : 'dev' + res.json({ version }) + } catch { + res.json({ version: 'dev' }) + } +}) + app.get('/api/browse', requireAuth, (req, res) => { const dirPath = (req.query.path as string) || '/' try { diff --git a/src/components/ManualImportModal.vue b/src/components/ManualImportModal.vue index 0c5604d..822fee1 100644 --- a/src/components/ManualImportModal.vue +++ b/src/components/ManualImportModal.vue @@ -138,7 +138,7 @@ @@ -153,10 +153,11 @@ import { useToast } from '@/composables/useToast' const { add: toast } = useToast() const props = defineProps<{ - serieId : number - serieName: string - seasons : any[] - organized: Record // episode_id → entry + serieId : number + serieName : string + seasons : any[] + organized : Record + initialPath?: string }>() const emit = defineEmits<{ @@ -165,10 +166,10 @@ const emit = defineEmits<{ }>() // ─── State ──────────────────────────────────────────────────── -const step = ref<'folder' | 'match' | 'done'>('folder') -const pickerOpen = ref(false) -const selectedFolder = ref('') -const scanning = ref(false) +const step = ref<'folder' | 'match' | 'done'>('folder') +const pickerOpen = ref(false) +const selectedFolder = ref(props.initialPath || '') +const scanning = ref(false) const importing = ref(false) const importError = ref('') const importResult = ref({ done: 0, errors: [] as { file: string; error: string }[] }) diff --git a/src/components/nav/SidebarNav.vue b/src/components/nav/SidebarNav.vue index 3cabda2..ce3fdf1 100644 --- a/src/components/nav/SidebarNav.vue +++ b/src/components/nav/SidebarNav.vue @@ -27,6 +27,32 @@ + + +
+
+

Mise à jour disponible

+

{{ latestVersion }} est disponible

+
+ + + + + +
+ + +
+

{{ currentVersion }}

+
+ @@ -34,6 +60,7 @@ \ No newline at end of file diff --git a/src/views/SerieView.vue b/src/views/SerieView.vue index ce38b86..4982020 100644 --- a/src/views/SerieView.vue +++ b/src/views/SerieView.vue @@ -43,7 +43,21 @@ Séries -

{{ data.serie.title }}

+ +
+

{{ data.serie.title }}

+ +
{{ data.serie.year }} @@ -61,25 +75,7 @@

{{ data.serie.plot }}

-
- +
{{ ep.title || `Épisode ${ep.episode_number}` }} -
+ + + + +
@@ -260,11 +269,10 @@ import { RouterLink, useRoute } from 'vue-router' import { ArrowLeft, Tv, ChevronUp, Download, Loader, Check, X } from 'lucide-vue-next' import { useSeriesStore } from '@/stores/series' import { useToast } from '@/composables/useToast' +import ManualImportModal from '@/components/ManualImportModal.vue' import type { Season } from '@/stores/series' -import ManualImportModal from "@/components/ManualImportModal.vue"; // ── Icône d'état du bouton ──────────────────────────────────── -// states: 'idle' | 'loading' | 'done' | 'unavailable' const EpStateIcon = defineComponent({ props: { state: { type: String, default: 'idle' }, @@ -300,9 +308,12 @@ const route = useRoute() const store = useSeriesStore() const { add: toast } = useToast() -const collapsedSeasons = ref>(new Set()) -const downloading = ref([]) -const downloaded = ref([]) +const collapsedSeasons = ref>(new Set()) +const downloading = ref([]) +const downloaded = ref([]) +const manualImportOpen = ref(false) +const organizedByEpisode = ref>({}) +const mediaPath = ref('/') function isDownloading(key: string) { return downloading.value.includes(key) } function isDownloaded(key: string) { return downloaded.value.includes(key) } @@ -316,17 +327,28 @@ let pollTimer: ReturnType | null = null const data = computed(() => store.currentSerie) - -const manualImportOpen = ref(false) -const organizedByEpisode = ref>({}) - -// Dans onMounted, après load() : async function fetchOrganized() { try { - const res = await fetch('/api/organized', { credentials: 'include' }) + const res = await fetch(`/api/organized/${route.params.id}`, { credentials: 'include' }) if (res.ok) organizedByEpisode.value = await res.json() } catch {} } + +async function fetchSettings() { + try { + const res = await fetch('/api/settings', { credentials: 'include' }) + if (res.ok) { + const s = await res.json() + mediaPath.value = s.mediaPath || '/' + } + } catch {} +} + +function openManualImport() { + fetchOrganized() + manualImportOpen.value = true +} + // ── Polling downloads ───────────────────────────────────────── async function fetchActiveDownloads() { try { @@ -362,28 +384,28 @@ function isAlreadyQueued(torrent: { magnet?: string | null } | null | undefined) type BtnState = 'idle' | 'loading' | 'done' | 'unavailable' function epState(ep: any): BtnState { - if (ep.organized) return 'done' - if (isDownloaded(`ep-${ep.id}`)) return 'done' - if (isAlreadyQueued(ep.torrent)) return 'done' + if (ep.organized) return 'done' + if (isDownloaded(`ep-${ep.id}`)) return 'done' + if (isAlreadyQueued(ep.torrent)) return 'done' const prog = torrentProgress(extractHash(ep.torrent)) - if (prog && prog.progress < 100) return 'loading' - if (!ep.torrent || !ep.available) return 'unavailable' - if (isDownloading(`ep-${ep.id}`)) return 'loading' + if (prog && prog.progress < 100) return 'loading' + if (!ep.torrent || !ep.available) return 'unavailable' + if (isDownloading(`ep-${ep.id}`)) return 'loading' return 'idle' } function epBtnState(key: string, torrent: any): BtnState { - if (isDownloaded(key) || isAlreadyQueued(torrent)) return 'done' - if (isDownloading(key)) return 'loading' + if (isDownloaded(key) || isAlreadyQueued(torrent)) return 'done' + if (isDownloading(key)) return 'loading' return 'idle' } function seasonBtnState(season: any): BtnState { - if (season.organized_state === 'complete') return 'done' - if (isDownloaded(`season-${season.id}`)) return 'done' - if (isAlreadyQueued(season.torrent)) return 'done' - if (isDownloading(`season-${season.id}`)) return 'loading' - if (!season.torrent) return 'unavailable' + if (season.organized_state === 'complete') return 'done' + if (isDownloaded(`season-${season.id}`)) return 'done' + if (isAlreadyQueued(season.torrent)) return 'done' + if (isDownloading(`season-${season.id}`)) return 'loading' + if (!season.torrent) return 'unavailable' return 'idle' } @@ -438,7 +460,7 @@ function formatDuration(seconds: number): string { onMounted(() => { load() - // fetchOrganized() + fetchSettings() fetchActiveDownloads() pollTimer = setInterval(fetchActiveDownloads, 5000) })