diff --git a/app/Http/Controllers/BrowseController.php b/app/Http/Controllers/BrowseController.php index 56f4a6b74..532d1fdea 100644 --- a/app/Http/Controllers/BrowseController.php +++ b/app/Http/Controllers/BrowseController.php @@ -84,21 +84,39 @@ class BrowseController extends BasePageController $catname = 'All'; } elseif ($category !== -1 && $grp === -1) { $catname = $id; + + // Determine the root category ID - either from the category's root_categories_id + // or the category itself if it IS a root category + $rootCategoryId = null; $cdata = Category::find($category); if ($cdata !== null) { - if ($cdata->root_categories_id === Category::GAME_ROOT) { - $covgroup = 'console'; - } elseif ($cdata->root_categories_id === Category::MOVIE_ROOT) { - $covgroup = 'movies'; - } elseif ($cdata->root_categories_id === Category::PC_ROOT) { - $covgroup = 'games'; - } elseif ($cdata->root_categories_id === Category::MUSIC_ROOT) { - $covgroup = 'music'; - } elseif ($cdata->root_categories_id === Category::BOOKS_ROOT) { - $covgroup = 'books'; - } elseif ($cdata->root_categories_id === Category::TV_ROOT) { - $shows = true; - } + $rootCategoryId = $cdata->root_categories_id ?? $category; + } else { + // Category not found in categories table, might be a root category + // Check if it matches a known root category ID + $rootCategoryId = $category; + } + + // Also check RootCategory table for parent categories (when $id is 'All') + if ($id === 'All' && $parentId !== null) { + $rootCategoryId = $parentId; + } + + // Set covgroup based on root category + if ($rootCategoryId === Category::GAME_ROOT) { + $covgroup = 'console'; + } elseif ($rootCategoryId === Category::MOVIE_ROOT) { + $covgroup = 'movies'; + } elseif ($rootCategoryId === Category::PC_ROOT) { + $covgroup = 'games'; + } elseif ($rootCategoryId === Category::MUSIC_ROOT) { + $covgroup = 'music'; + } elseif ($rootCategoryId === Category::BOOKS_ROOT) { + $covgroup = 'books'; + } elseif ($rootCategoryId === Category::XXX_ROOT) { + $covgroup = 'xxx'; + } elseif ($rootCategoryId === Category::TV_ROOT) { + $shows = true; } } else { $catname = $grp; diff --git a/app/Services/ConsoleService.php b/app/Services/ConsoleService.php index 1c4f051e7..f5240e89e 100644 --- a/app/Services/ConsoleService.php +++ b/app/Services/ConsoleService.php @@ -168,18 +168,34 @@ class ConsoleService Cache::put(md5($calcSql.$page), $consoles, $expiresAt); } - $consoleIDs = $releaseIDs = false; + $consoleIDs = $releaseIDs = []; if (\is_array($consoles['result'])) { foreach ($consoles['result'] as $console => $id) { - $consoleIDs = [$id->id]; - $releaseIDs = [$id->grp_release_id]; + $consoleIDs[] = $id->id; + $releaseIDs[] = $id->grp_release_id; } } $sql = sprintf( - ' + " SELECT - r.id, r.rarinnerfilecount, r.grabs, r.comments, r.totalpart, r.size, r.postdate, r.searchname, r.haspreview, r.passwordstatus, r.guid, rn.releases_id, g.name AS group_name, df.failed AS failed, + GROUP_CONCAT(r.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_id, + GROUP_CONCAT(r.rarinnerfilecount ORDER BY r.postdate DESC SEPARATOR ',') AS grp_rarinnerfilecount, + GROUP_CONCAT(r.haspreview ORDER BY r.postdate DESC SEPARATOR ',') AS grp_haspreview, + GROUP_CONCAT(r.passwordstatus ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_password, + GROUP_CONCAT(r.guid ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_guid, + GROUP_CONCAT(rn.releases_id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_nfoid, + GROUP_CONCAT(g.name ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grpname, + GROUP_CONCAT(r.searchname ORDER BY r.postdate DESC SEPARATOR '#') AS grp_release_name, + GROUP_CONCAT(r.postdate ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_postdate, + GROUP_CONCAT(r.adddate ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_adddate, + GROUP_CONCAT(r.size ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_size, + GROUP_CONCAT(r.totalpart ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_totalparts, + GROUP_CONCAT(r.comments ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_comments, + GROUP_CONCAT(r.grabs ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grabs, + GROUP_CONCAT(df.failed ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_failed, + GROUP_CONCAT(cp.title, ' > ', c.title ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_catname, + GROUP_CONCAT(r.fromname ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_fromname, con.*, r.consoleinfo_id, g.name AS group_name, @@ -189,15 +205,17 @@ class ConsoleService LEFT OUTER JOIN usenet_groups g ON g.id = r.groups_id LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id + LEFT OUTER JOIN categories c ON c.id = r.categories_id + LEFT OUTER JOIN root_categories cp ON cp.id = c.root_categories_id INNER JOIN consoleinfo con ON con.id = r.consoleinfo_id INNER JOIN genres ON con.genres_id = genres.id WHERE con.id IN (%s) AND r.id IN (%s) %s GROUP BY con.id - ORDER BY %s %s', - (\is_array($consoleIDs) ? implode(',', $consoleIDs) : -1), - (\is_array($releaseIDs) ? implode(',', $releaseIDs) : -1), + ORDER BY %s %s", + (! empty($consoleIDs) ? implode(',', $consoleIDs) : -1), + (! empty($releaseIDs) ? implode(',', $releaseIDs) : -1), $catsrch, $order[0], $order[1] diff --git a/app/Services/GamesService.php b/app/Services/GamesService.php index 444c6207c..668f50ced 100644 --- a/app/Services/GamesService.php +++ b/app/Services/GamesService.php @@ -216,16 +216,51 @@ class GamesService Cache::put(md5($gamesSql.$page), $games, $expiresAt); } - $gameIDs = $releaseIDs = false; + $gameIDs = $releaseIDs = []; if (is_array($games['result'])) { foreach ($games['result'] as $game => $id) { - $gameIDs = [$id->id]; - $releaseIDs = [$id->grp_release_id]; + $gameIDs[] = $id->id; + $releaseIDs[] = $id->grp_release_id; } } - $returnSql = - 'SELECT r.id, r.rarinnerfilecount, r.grabs, r.comments, r.totalpart, r.size, r.postdate, r.searchname, r.haspreview, r.passwordstatus, r.guid, g.name AS group_name, df.failed AS failed, gi.*, YEAR (gi.releasedate) as year, r.gamesinfo_id, rn.releases_id AS nfoid FROM releases r LEFT OUTER JOIN usenet_groups g ON g.id = r.groups_id LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id INNER JOIN gamesinfo gi ON gi.id = r.gamesinfo_id WHERE gi.id IN ('.(is_array($gameIDs) ? implode(',', $gameIDs) : -1).') AND r.id IN ('.(is_array($releaseIDs) ? implode(',', $releaseIDs) : -1).')'.$catsrch.' GROUP BY gi.id ORDER BY '.($order[0]).' '.($order[1]); + $returnSql = sprintf( + "SELECT + GROUP_CONCAT(r.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_id, + GROUP_CONCAT(r.rarinnerfilecount ORDER BY r.postdate DESC SEPARATOR ',') AS grp_rarinnerfilecount, + GROUP_CONCAT(r.haspreview ORDER BY r.postdate DESC SEPARATOR ',') AS grp_haspreview, + GROUP_CONCAT(r.passwordstatus ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_password, + GROUP_CONCAT(r.guid ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_guid, + GROUP_CONCAT(rn.releases_id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_nfoid, + GROUP_CONCAT(g.name ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grpname, + GROUP_CONCAT(r.searchname ORDER BY r.postdate DESC SEPARATOR '#') AS grp_release_name, + GROUP_CONCAT(r.postdate ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_postdate, + GROUP_CONCAT(r.adddate ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_adddate, + GROUP_CONCAT(r.size ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_size, + GROUP_CONCAT(r.totalpart ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_totalparts, + GROUP_CONCAT(r.comments ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_comments, + GROUP_CONCAT(r.grabs ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grabs, + GROUP_CONCAT(df.failed ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_failed, + GROUP_CONCAT(cp.title, ' > ', c.title ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_catname, + gi.*, YEAR(gi.releasedate) as year, r.gamesinfo_id, rn.releases_id AS nfoid, g.name AS group_name + FROM releases r + LEFT OUTER JOIN usenet_groups g ON g.id = r.groups_id + LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id + LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id + LEFT OUTER JOIN categories c ON c.id = r.categories_id + LEFT OUTER JOIN root_categories cp ON cp.id = c.root_categories_id + INNER JOIN gamesinfo gi ON gi.id = r.gamesinfo_id + WHERE gi.id IN (%s) + AND r.id IN (%s) + %s + GROUP BY gi.id + ORDER BY %s %s", + (! empty($gameIDs) ? implode(',', $gameIDs) : -1), + (! empty($releaseIDs) ? implode(',', $releaseIDs) : -1), + $catsrch, + $order[0], + $order[1] + ); $return = Cache::get(md5($returnSql.$page)); if ($return !== null) { diff --git a/app/Services/MusicService.php b/app/Services/MusicService.php index dcf139c91..95d240556 100644 --- a/app/Services/MusicService.php +++ b/app/Services/MusicService.php @@ -152,21 +152,37 @@ class MusicService } $sql = sprintf( - ' + " SELECT - r.id, r.rarinnerfilecount, r.grabs, r.comments, r.totalpart, r.size, r.postdate, r.searchname, r.haspreview, r.passwordstatus, r.guid, df.failed AS failed, + GROUP_CONCAT(r.id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_id, + GROUP_CONCAT(r.rarinnerfilecount ORDER BY r.postdate DESC SEPARATOR ',') AS grp_rarinnerfilecount, + GROUP_CONCAT(r.haspreview ORDER BY r.postdate DESC SEPARATOR ',') AS grp_haspreview, + GROUP_CONCAT(r.passwordstatus ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_password, + GROUP_CONCAT(r.guid ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_guid, + GROUP_CONCAT(rn.releases_id ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_nfoid, + GROUP_CONCAT(g.name ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grpname, + GROUP_CONCAT(r.searchname ORDER BY r.postdate DESC SEPARATOR '#') AS grp_release_name, + GROUP_CONCAT(r.postdate ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_postdate, + GROUP_CONCAT(r.adddate ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_adddate, + GROUP_CONCAT(r.size ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_size, + GROUP_CONCAT(r.totalpart ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_totalparts, + GROUP_CONCAT(r.comments ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_comments, + GROUP_CONCAT(r.grabs ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_grabs, + GROUP_CONCAT(df.failed ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_failed, + GROUP_CONCAT(cp.title, ' > ', c.title ORDER BY r.postdate DESC SEPARATOR ',') AS grp_release_catname, m.*, - r.musicinfo_id, r.haspreview, g.name AS group_name, rn.releases_id AS nfoid FROM releases r LEFT OUTER JOIN usenet_groups g ON g.id = r.groups_id LEFT OUTER JOIN release_nfos rn ON rn.releases_id = r.id LEFT OUTER JOIN dnzb_failures df ON df.release_id = r.id + LEFT OUTER JOIN categories c ON c.id = r.categories_id + LEFT OUTER JOIN root_categories cp ON cp.id = c.root_categories_id INNER JOIN musicinfo m ON m.id = r.musicinfo_id %s %s %s GROUP BY m.id - ORDER BY %s %s', + ORDER BY %s %s", ! empty($musicIDs) ? 'WHERE m.id IN ('.implode(',', $musicIDs).')' : 'AND 1=1', (! empty($releaseIDs)) ? 'AND r.id in ('.implode(',', $releaseIDs).')' : '', $catsrch, diff --git a/database/migrations/2026_01_15_000000_fix_invitations_foreign_key.php b/database/migrations/2026_01_15_000000_fix_invitations_foreign_key.php new file mode 100644 index 000000000..8acb26d66 --- /dev/null +++ b/database/migrations/2026_01_15_000000_fix_invitations_foreign_key.php @@ -0,0 +1,190 @@ +dropForeignKeySafely('invitations', 'FK_users_inv'); + + Schema::table('invitations', function (Blueprint $table) { + // Rename users_id to invited_by if needed + if (Schema::hasColumn('invitations', 'users_id') && ! Schema::hasColumn('invitations', 'invited_by')) { + $table->renameColumn('users_id', 'invited_by'); + } + }); + + // Now add missing columns in a separate Schema call (after rename) + Schema::table('invitations', function (Blueprint $table) { + // Drop the old guid column if it exists (replaced by token) + if (Schema::hasColumn('invitations', 'guid') && Schema::hasColumn('invitations', 'token')) { + $table->dropColumn('guid'); + } + + // Add token if it doesn't exist + if (! Schema::hasColumn('invitations', 'token')) { + $table->string('token', 64)->unique()->after('id'); + } + + // Add email if it doesn't exist + if (! Schema::hasColumn('invitations', 'email')) { + $table->string('email')->after('token'); + } + + // Add expires_at if it doesn't exist + if (! Schema::hasColumn('invitations', 'expires_at')) { + if (Schema::hasColumn('invitations', 'invited_by')) { + $table->timestamp('expires_at')->after('invited_by'); + } else { + $table->timestamp('expires_at')->nullable(); + } + } + + // Add used_at if it doesn't exist + if (! Schema::hasColumn('invitations', 'used_at')) { + $table->timestamp('used_at')->nullable(); + } + + // Add used_by if it doesn't exist + if (! Schema::hasColumn('invitations', 'used_by')) { + $table->unsignedBigInteger('used_by')->nullable(); + } + + // Add is_active if it doesn't exist + if (! Schema::hasColumn('invitations', 'is_active')) { + $table->boolean('is_active')->default(true); + } + + // Add metadata if it doesn't exist + if (! Schema::hasColumn('invitations', 'metadata')) { + $table->json('metadata')->nullable(); + } + }); + + // Migrate data from guid to token if guid still exists and has data + if (Schema::hasColumn('invitations', 'guid') && Schema::hasColumn('invitations', 'token')) { + DB::table('invitations') + ->whereNull('token') + ->orWhere('token', '') + ->update(['token' => DB::raw('guid')]); + + // Now drop guid + Schema::table('invitations', function (Blueprint $table) { + $table->dropColumn('guid'); + }); + } + + // Add indexes safely + $this->addIndexSafely('invitations', ['invited_by'], 'invitations_invited_by_index'); + $this->addIndexSafely('invitations', ['used_by'], 'invitations_used_by_index'); + $this->addIndexSafely('invitations', ['token', 'is_active'], 'invitations_token_is_active_index'); + $this->addIndexSafely('invitations', ['email', 'is_active'], 'invitations_email_is_active_index'); + $this->addIndexSafely('invitations', ['expires_at'], 'invitations_expires_at_index'); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // Drop indexes first + $this->dropIndexSafely('invitations', 'invitations_invited_by_index'); + $this->dropIndexSafely('invitations', 'invitations_used_by_index'); + $this->dropIndexSafely('invitations', 'invitations_token_is_active_index'); + $this->dropIndexSafely('invitations', 'invitations_email_is_active_index'); + $this->dropIndexSafely('invitations', 'invitations_expires_at_index'); + + Schema::table('invitations', function (Blueprint $table) { + // Rename back to original column name + if (Schema::hasColumn('invitations', 'invited_by') && ! Schema::hasColumn('invitations', 'users_id')) { + $table->renameColumn('invited_by', 'users_id'); + } + }); + + // Re-add the foreign key constraint + Schema::table('invitations', function (Blueprint $table) { + if (Schema::hasColumn('invitations', 'users_id')) { + $table->foreign('users_id', 'FK_users_inv') + ->references('id') + ->on('users') + ->onDelete('cascade') + ->onUpdate('cascade'); + } + }); + } + + /** + * Safely drop a foreign key if it exists + */ + private function dropForeignKeySafely(string $table, string $foreignKeyName): void + { + try { + $foreignKeys = DB::select( + "SELECT CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS + WHERE CONSTRAINT_TYPE = 'FOREIGN KEY' + AND TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = ? + AND CONSTRAINT_NAME = ?", + [$table, $foreignKeyName] + ); + + if (! empty($foreignKeys)) { + Schema::table($table, function (Blueprint $table) use ($foreignKeyName) { + $table->dropForeign($foreignKeyName); + }); + } + } catch (\Exception $e) { + // Foreign key might not exist, continue + report($e); + } + } + + /** + * Safely add an index if it doesn't exist + */ + private function addIndexSafely(string $table, array $columns, string $indexName): void + { + try { + $indexes = DB::select("SHOW INDEX FROM `{$table}` WHERE Key_name = ?", [$indexName]); + if (empty($indexes)) { + Schema::table($table, function (Blueprint $table) use ($columns, $indexName) { + $table->index($columns, $indexName); + }); + } + } catch (\Exception $e) { + // Index creation failed, but continue + report($e); + } + } + + /** + * Safely drop an index if it exists + */ + private function dropIndexSafely(string $table, string $indexName): void + { + try { + $indexes = DB::select("SHOW INDEX FROM `{$table}` WHERE Key_name = ?", [$indexName]); + if (! empty($indexes)) { + Schema::table($table, function (Blueprint $table) use ($indexName) { + $table->dropIndex($indexName); + }); + } + } catch (\Exception $e) { + // Index drop failed, but continue + report($e); + } + } +}; diff --git a/database/migrations/2026_01_15_000001_cleanup_invitations_legacy_columns.php b/database/migrations/2026_01_15_000001_cleanup_invitations_legacy_columns.php new file mode 100644 index 000000000..fa43bbd51 --- /dev/null +++ b/database/migrations/2026_01_15_000001_cleanup_invitations_legacy_columns.php @@ -0,0 +1,40 @@ +dropColumn('users_id'); + } + + // Drop guid column if it still exists (we use token now) + if (Schema::hasColumn('invitations', 'guid')) { + $table->dropColumn('guid'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('invitations', function (Blueprint $table) { + if (! Schema::hasColumn('invitations', 'users_id')) { + $table->unsignedInteger('users_id')->nullable()->after('id'); + } + }); + } +}; diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php index 696a38068..27b4eb47f 100644 --- a/resources/views/books/index.blade.php +++ b/resources/views/books/index.blade.php @@ -74,11 +74,20 @@ @if(count($results) > 0)