Compare commits

...
14 Commits
6 changed files with 113 additions and 43 deletions

No files matched your search

+5 -2
View File
@@ -97,16 +97,19 @@ jobs:
bun build server/index.ts --compile --target ${{ matrix.bun-target }} --outfile ${{ matrix.binary }} bun build server/index.ts --compile --target ${{ matrix.bun-target }} --outfile ${{ matrix.binary }}
shell: pwsh shell: pwsh
- name: Build worker
run: bun build server/organize-worker.ts --outfile organize-worker.js
- name: Package archive (Windows) - name: Package archive (Windows)
if: matrix.target == 'windows-x64' if: matrix.target == 'windows-x64'
run: Compress-Archive -Path ${{ matrix.binary }}, public -DestinationPath ${{ matrix.archive }} run: Compress-Archive -Path ${{ matrix.binary }}, organize-worker.js, public -DestinationPath ${{ matrix.archive }}
shell: pwsh shell: pwsh
- name: Package archive (Unix) - name: Package archive (Unix)
if: matrix.target != 'windows-x64' if: matrix.target != 'windows-x64'
run: | run: |
chmod +x ${{ matrix.binary }} chmod +x ${{ matrix.binary }}
zip -r ${{ matrix.archive }} ${{ matrix.binary }} public/ zip -r ${{ matrix.archive }} ${{ matrix.binary }} organize-worker.js public/
- name: Upload archive to Release - name: Upload archive to Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
+66
View File
@@ -1,5 +1,71 @@
# Changelog # Changelog
## [1.10.0](https://github.com/Masutayunikon/FanKarr/compare/v1.9.5...v1.10.0) (2026-03-13)
### Features
* add favicon ([dc4a2d4](https://github.com/Masutayunikon/FanKarr/commit/dc4a2d48f5c0cddd8e431ce33a69fc9146143fb6))
* add README.md ([4eb487a](https://github.com/Masutayunikon/FanKarr/commit/4eb487a96beed8c82cb19d9a9b63c3b69d841386))
* add windows / linux / macos binaries on build ([f17a73b](https://github.com/Masutayunikon/FanKarr/commit/f17a73b9591fb49a75bd5fcdadf1970585877dab))
* **auth:** génération automatique du JWT secret au démarrage ([eaa52e9](https://github.com/Masutayunikon/FanKarr/commit/eaa52e90900ff0e198beae190d7ad0298a702482))
* cache images ([da460de](https://github.com/Masutayunikon/FanKarr/commit/da460de60c4eec6851ea58ce0ae646a1208f6018))
* initial project ([eaf01cf](https://github.com/Masutayunikon/FanKarr/commit/eaf01cfc591dd72f9b093b533ced6c12a6c490a1))
* make the organizer threaded to not consumme api main thread ([14c0e56](https://github.com/Masutayunikon/FanKarr/commit/14c0e567525e179da82c6b58d9886cc3d544a98d))
* **organize:** scan initial mediaPath + logs fichier centralisés ([807f786](https://github.com/Masutayunikon/FanKarr/commit/807f786b038579ca9d7cd136998f759cecae2255))
* **organize:** worker thread + badges statut + notifications ([b7242bc](https://github.com/Masutayunikon/FanKarr/commit/b7242bc95f59d259ab1a636aa304bf149f895041))
* **serie:** badges état organisation sur épisodes et saisons ([4d3a6bd](https://github.com/Masutayunikon/FanKarr/commit/4d3a6bd276907f793452429d62693fafc1d804db))
* **ui:** états catalogue + logs + badges erreurs downloads ([5326828](https://github.com/Masutayunikon/FanKarr/commit/5326828d89aa10a53bbe1ac4016a5eee4d5dbc97))
* UX improvements, folder picker, download state & NFO support ([20771fc](https://github.com/Masutayunikon/FanKarr/commit/20771fc31c187fd1fea8aa9b07093b15621a5b85))
### Bug Fixes
* add log for debug ([6876c5e](https://github.com/Masutayunikon/FanKarr/commit/6876c5ee6c7734e664a6da4e4bc721cc7c5abdf7))
* add log for debug ([d8cdeae](https://github.com/Masutayunikon/FanKarr/commit/d8cdeae675bc73b5842d18d01eff9d5bb0e0a7e8))
* add user permissions when the scripts is launched ([501bbfc](https://github.com/Masutayunikon/FanKarr/commit/501bbfcf18c236f7ac382c50d90482b1f51244c1))
* CatalogView.vue badge deleted ([d897192](https://github.com/Masutayunikon/FanKarr/commit/d8971920462efd8d1d99eb867016c96713dfef58))
* docker errors ([2702764](https://github.com/Masutayunikon/FanKarr/commit/27027642eb55b75adb04446a88e5007e88f75948))
* docker is able to change directory ([af08f7d](https://github.com/Masutayunikon/FanKarr/commit/af08f7d9d4fb2e23c5feac6d33b2ef495f9d91b2))
* empty docker-compose.yml ([303dfd8](https://github.com/Masutayunikon/FanKarr/commit/303dfd85b5d033e2b247fd064ae1ae612fdb49e1))
* logger third args ([5abac1e](https://github.com/Masutayunikon/FanKarr/commit/5abac1e11236ed1e8f7f0de7f0cff881d1b60f10))
* nfo settings and add json file when not existing ([0b6bfa6](https://github.com/Masutayunikon/FanKarr/commit/0b6bfa6a81d99353be877ed6d99687ad3d55adbc))
* organize file ([d6550e9](https://github.com/Masutayunikon/FanKarr/commit/d6550e9961789073be1583e03d599afb6d295004))
* organize use torrent hash and not name ([61901f9](https://github.com/Masutayunikon/FanKarr/commit/61901f9014a759e2950311cd96266ea8201fa902))
* path ([314f817](https://github.com/Masutayunikon/FanKarr/commit/314f817280f39d10eb21c4784f2aea41b9a6d32c))
* remove user creation in entrypoint.sh ([92d9fa6](https://github.com/Masutayunikon/FanKarr/commit/92d9fa6b01f1e2de1ceb13528afd309b21f9d4cb))
* rename if the torrent name is not the same as the api ([b8fcf71](https://github.com/Masutayunikon/FanKarr/commit/b8fcf71f54ab14e5572a7553d8554b617b4981b7))
* season fallback ([e9b5091](https://github.com/Masutayunikon/FanKarr/commit/e9b5091242d73be77bf6fb525ce2865554e336cc))
* skip excluded folders ([edf806d](https://github.com/Masutayunikon/FanKarr/commit/edf806d9bc48d4de1c3609a4b0405442305d546e))
* title readme align ([8ba3b37](https://github.com/Masutayunikon/FanKarr/commit/8ba3b370d1e2cd1565f9c2453be8706e3c198243))
* use bcryptjs for compilation ([6a6981e](https://github.com/Masutayunikon/FanKarr/commit/6a6981e10a3a9019f49982d7680e562872ee95a5))
* worker outside the exe ([c2ab58e](https://github.com/Masutayunikon/FanKarr/commit/c2ab58e7b2c34aae09b50dbdfde3277da4c276ff))
* worker outside the exe ([a47c3a4](https://github.com/Masutayunikon/FanKarr/commit/a47c3a43fb5dbbedd87c9ac2d584c3290fcc6097))
* workflow directory ([baebd8f](https://github.com/Masutayunikon/FanKarr/commit/baebd8fae146db70ee1cce0c876d2d3bc0f9bf4e))
* workflow directory ([d8405c1](https://github.com/Masutayunikon/FanKarr/commit/d8405c1589f277cd8fdd3e1fd15cc02d98834aa6))
* workflow permmisions ([3620bfb](https://github.com/Masutayunikon/FanKarr/commit/3620bfb71d8c2774b23c097e5e01326099c2f180))
## [1.9.5](https://github.com/Masutayunikon/FanKarr/compare/v1.9.4...v1.9.5) (2026-03-13)
### Bug Fixes
* worker outside the exe ([c2ab58e](https://github.com/Masutayunikon/FanKarr/commit/c2ab58e7b2c34aae09b50dbdfde3277da4c276ff))
## [1.9.4](https://github.com/Masutayunikon/FanKarr/compare/v1.9.3...v1.9.4) (2026-03-13)
### Bug Fixes
* worker outside the exe ([a47c3a4](https://github.com/Masutayunikon/FanKarr/commit/a47c3a43fb5dbbedd87c9ac2d584c3290fcc6097))
## [1.9.3](https://github.com/Masutayunikon/FanKarr/compare/v1.9.2...v1.9.3) (2026-03-13)
### Bug Fixes
* use bcryptjs for compilation ([6a6981e](https://github.com/Masutayunikon/FanKarr/commit/6a6981e10a3a9019f49982d7680e562872ee95a5))
## [1.9.2](https://github.com/Masutayunikon/FanKarr/compare/v1.9.1...v1.9.2) (2026-03-13) ## [1.9.2](https://github.com/Masutayunikon/FanKarr/compare/v1.9.1...v1.9.2) (2026-03-13)
+7 -7
View File
@@ -8,16 +8,15 @@
"dev:server": "tsx watch server/index.ts", "dev:server": "tsx watch server/index.ts",
"dev:client": "vite", "dev:client": "vite",
"build": "vite build && tsc -p tsconfig.server.json", "build": "vite build && tsc -p tsconfig.server.json",
"build:bun:linux": "bun build server/index.ts --compile --target bun-linux-x64 --outfile binaries/fankarr-linux-x64", "build:bun:linux": "bun build server/index.ts --compile --target bun-linux-x64 --outfile binaries/fankarr-linux-x64 && bun build server/organize-worker.ts --outfile binaries/organize-worker.js",
"build:bun:windows": "bun build server/index.ts --compile --target bun-windows-x64 --outfile binaries/fankarr-windows-x64.exe", "build:bun:windows": "bun build server/index.ts --compile --target bun-windows-x64 --outfile binaries/fankarr-windows-x64.exe && bun build server/organize-worker.ts --outfile binaries/organize-worker.js",
"build:bun:macos": "bun build server/index.ts --compile --target bun-darwin-arm64 --outfile binaries/fankarr-macos", "build:bun:macos": "bun build server/index.ts --compile --target bun-darwin-arm64 --outfile binaries/fankarr-macos && bun build server/organize-worker.ts --outfile binaries/organize-worker.js",
"build:bun:all": "pnpm build:bun:linux && pnpm build:bun:windows && pnpm build:bun:macos", "build:bun:all": "pnpm build:bun:linux && pnpm build:bun:windows && pnpm build:bun:macos",
"start": "node dist/server/index.js", "start": "node dist/server/index.js",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.2.1", "@tailwindcss/vite": "^4.2.1",
"bcrypt": "^6.0.0",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"express": "^5.2.1", "express": "^5.2.1",
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.3",
@@ -25,10 +24,10 @@
"pinia": "^3.0.4", "pinia": "^3.0.4",
"tailwindcss": "^4.2.1", "tailwindcss": "^4.2.1",
"vue": "^3.5.25", "vue": "^3.5.25",
"vue-router": "^5.0.3" "vue-router": "^5.0.3",
"bcryptjs": "^3.0.3"
}, },
"devDependencies": { "devDependencies": {
"@types/bcrypt": "^5.0.2",
"@types/cookie-parser": "^1.4.8", "@types/cookie-parser": "^1.4.8",
"@types/express": "^5.0.6", "@types/express": "^5.0.6",
"@types/jsonwebtoken": "^9.0.9", "@types/jsonwebtoken": "^9.0.9",
@@ -39,6 +38,7 @@
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"vite": "^7.3.1", "vite": "^7.3.1",
"vue-tsc": "^3.1.5" "vue-tsc": "^3.1.5",
"@types/bcrypt": "^5.0.2"
} }
} }
+7 -22
View File
@@ -11,9 +11,9 @@ importers:
'@tailwindcss/vite': '@tailwindcss/vite':
specifier: ^4.2.1 specifier: ^4.2.1
version: 4.2.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) version: 4.2.1(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))
bcrypt: bcryptjs:
specifier: ^6.0.0 specifier: ^3.0.3
version: 6.0.0 version: 3.0.3
cookie-parser: cookie-parser:
specifier: ^1.4.7 specifier: ^1.4.7
version: 1.4.7 version: 1.4.7
@@ -650,9 +650,9 @@ packages:
resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==} resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
bcrypt@6.0.0: bcryptjs@3.0.3:
resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==}
engines: {node: '>= 18'} hasBin: true
birpc@2.9.0: birpc@2.9.0:
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
@@ -1087,10 +1087,6 @@ packages:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
node-addon-api@8.6.0:
resolution: {integrity: sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==}
engines: {node: ^18 || ^20 || >= 21}
node-domexception@1.0.0: node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'} engines: {node: '>=10.5.0'}
@@ -1100,10 +1096,6 @@ packages:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
node-gyp-build@4.8.4:
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
hasBin: true
object-inspect@1.13.4: object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -1919,10 +1911,7 @@ snapshots:
'@babel/parser': 7.29.0 '@babel/parser': 7.29.0
ast-kit: 2.2.0 ast-kit: 2.2.0
bcrypt@6.0.0: bcryptjs@3.0.3: {}
dependencies:
node-addon-api: 8.6.0
node-gyp-build: 4.8.4
birpc@2.9.0: {} birpc@2.9.0: {}
@@ -2351,8 +2340,6 @@ snapshots:
negotiator@1.0.0: {} negotiator@1.0.0: {}
node-addon-api@8.6.0: {}
node-domexception@1.0.0: {} node-domexception@1.0.0: {}
node-fetch@3.3.2: node-fetch@3.3.2:
@@ -2361,8 +2348,6 @@ snapshots:
fetch-blob: 3.2.0 fetch-blob: 3.2.0
formdata-polyfill: 4.0.10 formdata-polyfill: 4.0.10
node-gyp-build@4.8.4: {}
object-inspect@1.13.4: {} object-inspect@1.13.4: {}
on-finished@2.4.1: on-finished@2.4.1:
+1 -1
View File
@@ -1,6 +1,6 @@
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import bcrypt from 'bcrypt' import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
import type { Request, Response, NextFunction } from 'express' import type { Request, Response, NextFunction } from 'express'
import { JWT_SECRET } from './secret.js' import { JWT_SECRET } from './secret.js'
+27 -11
View File
@@ -13,6 +13,18 @@ import { logger } from './logger.js'
// Un seul worker à la fois // Un seul worker à la fois
let workerRunning = false let workerRunning = false
// Résolution du chemin du worker
// En binaire Bun compilé : le worker est à côté de l'exécutable
// En Docker/dev : à côté du fichier courant
function resolveWorkerPath(): string {
const _isBunBinary = typeof (globalThis as any).Bun !== 'undefined'
&& path.dirname((process as any).execPath) !== process.cwd()
if (_isBunBinary) {
return path.join(path.dirname((process as any).execPath), 'organize-worker.js')
}
return path.join(path.dirname(new URL(import.meta.url).pathname), 'organize-worker.js')
}
export interface OrganizeResult { export interface OrganizeResult {
total : number total : number
skipped : number skipped : number
@@ -112,7 +124,7 @@ export async function autoOrganizeAll(
onResult?: (r: { hash: string; name: string; done: number; skipped: number; errors: number; errorFiles: { file: string; error: string }[] }) => void onResult?: (r: { hash: string; name: string; done: number; skipped: number; errors: number; errorFiles: { file: string; error: string }[] }) => void
): Promise<void> { ): Promise<void> {
if (workerRunning) { if (workerRunning) {
logger.debug('organize', 'Worker déjà en cours, skip') console.log('[organize] Worker déjà en cours, skip')
return return
} }
@@ -120,7 +132,7 @@ export async function autoOrganizeAll(
try { try {
torrents = await listFn() torrents = await listFn()
} catch (err) { } catch (err) {
logger.error('organize', "Impossible de récupérer la liste des torrents: " + (err instanceof Error ? err.message : String(err))) console.error('[organize] Impossible de récupérer la liste des torrents:', err)
return return
} }
@@ -129,18 +141,20 @@ export async function autoOrganizeAll(
const seedingCount = torrents.filter(t => t.state === 'seeding').length const seedingCount = torrents.filter(t => t.state === 'seeding').length
if (seedingCount === 0) return if (seedingCount === 0) return
logger.info('organize', `Lancement worker (${seedingCount} torrents en seeding)`) console.log(`[organize] Lancement worker (${seedingCount} torrents en seeding)`)
workerRunning = true workerRunning = true
const workerPath = path.join(path.dirname(new URL(import.meta.url).pathname), 'organize-worker.js') const workerPath = resolveWorkerPath()
const worker = new Worker(workerPath) const worker = new Worker(workerPath)
worker.on('message', (msg: any) => { worker.on('message', (msg: any) => {
if (msg.type === 'log') { if (msg.type === 'log') {
logger[msg.level as 'debug'|'info'|'warn'|'error']('organize', msg.msg) if (msg.level === 'error') console.error(msg.msg)
else if (msg.level === 'warn') console.warn(msg.msg)
else console.log(msg.msg)
} else if (msg.type === 'result') { } else if (msg.type === 'result') {
logger.info('organize', `${msg.name} → ${msg.done} OK, ${msg.skipped} skippés, ${msg.errors.length} erreurs`) console.log(`[organize] ${msg.name} → ${msg.done} OK, ${msg.skipped} skippés, ${msg.errors.length} erreurs`)
onResult?.({ onResult?.({
hash : msg.hash, hash : msg.hash,
name : msg.name, name : msg.name,
@@ -150,19 +164,19 @@ export async function autoOrganizeAll(
errorFiles : msg.errors, errorFiles : msg.errors,
}) })
} else if (msg.type === 'done') { } else if (msg.type === 'done') {
logger.debug('organize', 'Worker terminé') console.log('[organize] Worker terminé')
workerRunning = false workerRunning = false
worker.terminate() worker.terminate()
} }
}) })
worker.on('error', (err) => { worker.on('error', (err) => {
logger.error('organize', `Worker erreur: ${err instanceof Error ? err.message : err}`) console.error('[organize] Worker erreur:', err)
workerRunning = false workerRunning = false
}) })
worker.on('exit', (code) => { worker.on('exit', (code) => {
if (code !== 0) logger.error('organize', `Worker exit code ${code}`) if (code !== 0) console.error(`[organize] Worker exit code ${code}`)
workerRunning = false workerRunning = false
}) })
@@ -180,7 +194,7 @@ export async function organizeTorrent(
savePath: string savePath: string
): Promise<OrganizeResult> { ): Promise<OrganizeResult> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const workerPath = path.join(path.dirname(new URL(import.meta.url).pathname), 'organize-worker.js') const workerPath = resolveWorkerPath()
const worker = new Worker(workerPath) const worker = new Worker(workerPath)
// On envoie un seul torrent "fictif seeding" // On envoie un seul torrent "fictif seeding"
@@ -189,7 +203,9 @@ export async function organizeTorrent(
worker.on('message', (msg: any) => { worker.on('message', (msg: any) => {
if (msg.type === 'log') { if (msg.type === 'log') {
logger[msg.level as 'debug'|'info'|'warn'|'error']('organize', msg.msg) if (msg.level === 'error') console.error(msg.msg)
else if (msg.level === 'warn') console.warn(msg.msg)
else console.log(msg.msg)
} else if (msg.type === 'result') { } else if (msg.type === 'result') {
result.total = msg.total result.total = msg.total
result.skipped = msg.skipped result.skipped = msg.skipped