forked from w33b/hstream
Compare commits
135 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57d1ec34c3 | |||
| 81639aaabf | |||
| 5dc1bff60c | |||
| 2f3f0edc30 | |||
| a71b2976af | |||
| 2c016274ab | |||
| 5ba0a55316 | |||
| a6fe34a0d1 | |||
| bb53e06c69 | |||
| 5cae5dc658 | |||
| 356d07365f | |||
| 3574d20fae | |||
| 9fc9e8ed10 | |||
| f5c706b587 | |||
| cbea71d9ae | |||
| 64a621173c | |||
| 839779b82e | |||
| 112cf9433e | |||
| 26a6500fca | |||
| d8cf70e747 | |||
| 0d4545c2ab | |||
| 900103e1c2 | |||
| d4c90976f8 | |||
| 72263127df | |||
| 6d3de59929 | |||
| ddb1bc2d14 | |||
| 5f3874a233 | |||
| ba3650899e | |||
| 904604fcfb | |||
| b7b34b503c | |||
| 4928733383 | |||
| 6340302ac6 | |||
| c1829ba7bd | |||
| 0b155bbb80 | |||
| 9f959efa14 | |||
| 38e3346dc3 | |||
| 09c08f3fea | |||
| 75f631c3e6 | |||
| fdf26604f3 | |||
| 59cb39ca77 | |||
| 62647be75c | |||
| de6efb877c | |||
| 2151d69791 | |||
| 05d4ef1bdb | |||
| 8ae9eaaadb | |||
| 361b511c3e | |||
| 1bc505057f | |||
| a78b1c41ac | |||
| 2480c5b309 | |||
| 4fc11d7329 | |||
| f3e5100d5d | |||
| 5b4d3d435e | |||
| 564f816fb9 | |||
| 3709e378c3 | |||
| 4a45dae593 | |||
| 2b0448d517 | |||
| 3bb6af73c3 | |||
| 57cf153560 | |||
| e45fd4b148 | |||
|
d479369770
|
|||
|
af739e3c88
|
|||
| 273ed65a8d | |||
| ccfd5b996b | |||
| e5ef197ed6 | |||
| c0be2e294a | |||
| b8ba17b33f | |||
| 5a8dd12cb8 | |||
| 3a77c4320d | |||
| 823a284fbc | |||
| 67e601d0c4 | |||
| 7e4ebd91ad | |||
| 4dc5dee2b9 | |||
| 5310908b0c | |||
| 4b05b3db6d | |||
| df47a926e4 | |||
| 1e9e95f35f | |||
| 2aa76baafd | |||
| aa50bb1f72 | |||
| dfedf4058e | |||
| 268e3eb4c2 | |||
| ab61574956 | |||
| 81038b6c26 | |||
| e949ba955a | |||
| 819e2fde27 | |||
| 3259e2197b | |||
| b133db0573 | |||
| 41c34e6d89 | |||
| db6da608aa | |||
| 13b70fdf23 | |||
| cfd6af59fb | |||
| 7810cd53fb | |||
| 871028930b | |||
| 6ce0255764 | |||
| e136e8e1b6 | |||
| a3b66b483b | |||
| 4c2a6024d7 | |||
| 5f575024e2 | |||
| 67f5d0db8b | |||
| 571bf4584c | |||
| d7dc96e11c | |||
| 58426b6e4e | |||
| 53b600daea | |||
| 224cdbcdc5 | |||
| 972d3d0aa4 | |||
| 8f7f012c14 | |||
| c0b068de58 | |||
| 51c67bb797 | |||
| 3d78f9e524 | |||
| 2d28a37463 | |||
| ac853920ee | |||
| fb3722036a | |||
| ab4e7c7999 | |||
| 8f99718058 | |||
| 2029af334c | |||
| b1c48830c4 | |||
| e100f3bf23 | |||
| c13d443696 | |||
| 8e7a56f559 | |||
| 30777a6968 | |||
| 256af435ad | |||
| e972f8db41 | |||
| 98d36d6018 | |||
| 7eea8285ca | |||
| 9e8efbbe05 | |||
| 5461606857 | |||
| 9ca2f73714 | |||
| 59d63abd79 | |||
| efb3e4197b | |||
| 735dd693ca | |||
| 36f0126a21 | |||
| 50d8704560 | |||
| 7e382ffe1d | |||
| 6a25fd2700 | |||
| 71bcf277f6 | |||
| 6c44d83e6b |
@@ -57,3 +57,8 @@ VITE_PUSHER_HOST="${PUSHER_HOST}"
|
|||||||
VITE_PUSHER_PORT="${PUSHER_PORT}"
|
VITE_PUSHER_PORT="${PUSHER_PORT}"
|
||||||
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
||||||
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||||
|
|
||||||
|
SCOUT_QUEUE=true
|
||||||
|
SCOUT_DRIVER=meilisearch
|
||||||
|
MEILISEARCH_HOST=http://127.0.0.1:7700
|
||||||
|
MEILISEARCH_KEY=masterKey
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
## hstream Website
|
## hstream Website
|
||||||
|
|
||||||
### Install
|
### Install (Ubuntu)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install PHP
|
# Install PHP
|
||||||
sudo add-apt-repository ppa:ondrej/php
|
sudo add-apt-repository ppa:ondrej/php
|
||||||
apt update && apt upgrade
|
apt update && apt upgrade
|
||||||
apt install php8.3 php8.3-xml php8.3-mysql php8.3-gd php8.3-zip php8.3-curl php8.3-mbstring
|
apt install php8.4 php8.4-xml php8.4-mysql php8.4-gd php8.4-zip php8.4-curl php8.4-mbstring
|
||||||
|
|
||||||
# Install NodeJS
|
# Install NodeJS
|
||||||
curl -sL https://deb.nodesource.com/setup_20.x -o /tmp/nodesource_setup.sh
|
curl -sL https://deb.nodesource.com/setup_20.x -o /tmp/nodesource_setup.sh
|
||||||
@@ -22,12 +22,16 @@ mv composer.phar composer
|
|||||||
|
|
||||||
# Install NGINX (skip for local dev)
|
# Install NGINX (skip for local dev)
|
||||||
apt install nginx
|
apt install nginx
|
||||||
apt install php8.3-fpm
|
apt install php8.4-fpm
|
||||||
|
|
||||||
# Install MariaDB
|
# Install MariaDB
|
||||||
apt install mariadb-server
|
apt install mariadb-server
|
||||||
sudo mysql_secure_installation
|
sudo mysql_secure_installation
|
||||||
|
|
||||||
|
# Install Meilisearch
|
||||||
|
echo "deb [trusted=yes] https://apt.fury.io/meilisearch/ /" | sudo tee /etc/apt/sources.list.d/fury.list
|
||||||
|
sudo apt update && sudo apt install meilisearch
|
||||||
|
|
||||||
# Clone Repo
|
# Clone Repo
|
||||||
cd /var/www
|
cd /var/www
|
||||||
git clone https://gitea.hstream.moe/w33b/hstream.git
|
git clone https://gitea.hstream.moe/w33b/hstream.git
|
||||||
@@ -50,7 +54,7 @@ nano /etc/supervisor/conf.d/laravel-queue.conf :
|
|||||||
|
|
||||||
[program:laravel-queue]
|
[program:laravel-queue]
|
||||||
process_name=%(program_name)s_%(process_num)02d
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
command=php /var/www/hstream/artisan queue:work --queue=default --sleep=3 --tries=3 --max-time=3600
|
command=php84 /var/www/hstream/artisan queue:work --queue=default --sleep=3 --tries=3 --max-time=3600
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
stopasgroup=true
|
stopasgroup=true
|
||||||
@@ -79,9 +83,9 @@ zip -r hstream_2023_11_30.zip hstream/
|
|||||||
|
|
||||||
### Update
|
### Update
|
||||||
```bash
|
```bash
|
||||||
php artisan down
|
php84 artisan down
|
||||||
git pull
|
git pull
|
||||||
npm run build
|
npm run build
|
||||||
php artisan view:clear && php artisan optimize:clear && php artisan cache:clear && service php8.4-fpm restart
|
php84 artisan view:clear && php84 artisan optimize:clear && php84 artisan cache:clear && service php8.4-fpm restart
|
||||||
php artisan up
|
php84 artisan up
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -3,11 +3,10 @@
|
|||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Models\PopularDaily;
|
use App\Models\PopularDaily;
|
||||||
use App\Models\PopularWeekly;
|
|
||||||
use App\Models\PopularMonthly;
|
use App\Models\PopularMonthly;
|
||||||
|
use App\Models\PopularWeekly;
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
class AutoStats extends Command
|
class AutoStats extends Command
|
||||||
{
|
{
|
||||||
@@ -30,9 +29,9 @@ class AutoStats extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
PopularDaily::where('created_at', '<=', Carbon::now()->subMinutes(1440))->forceDelete();
|
PopularDaily::where('created_at', '<=', Carbon::now()->subMinutes(1440))->delete();
|
||||||
PopularWeekly::where('created_at', '<=', Carbon::now()->subMinutes(10080))->forceDelete();
|
PopularWeekly::where('created_at', '<=', Carbon::now()->subMinutes(10080))->delete();
|
||||||
PopularMonthly::where('created_at', '<=', Carbon::now()->subMinutes(43200))->forceDelete();
|
PopularMonthly::where('created_at', '<=', Carbon::now()->subMinutes(43200))->delete();
|
||||||
|
|
||||||
$this->comment('Automated Purge Stats Complete');
|
$this->comment('Automated Purge Stats Complete');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Console\Commands;
|
|||||||
|
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
use App\Models\Hentai;
|
use App\Models\Hentai;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Spatie\Sitemap\Sitemap;
|
use Spatie\Sitemap\Sitemap;
|
||||||
@@ -17,7 +16,7 @@ class GenerateSitemap extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'sitemap:generate';
|
protected $signature = 'app:generate-sitemap';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Models\Downloads;
|
|
||||||
use App\Jobs\GetFileSizeFromCDN;
|
use App\Jobs\GetFileSizeFromCDN;
|
||||||
|
use App\Models\Downloads;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
class GetFileSize extends Command
|
class GetFileSize extends Command
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ namespace App\Console\Commands;
|
|||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\UserDownload;
|
use App\Models\UserDownload;
|
||||||
|
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
class ResetUserDownloads extends Command
|
class ResetUserDownloads extends Command
|
||||||
{
|
{
|
||||||
@@ -35,6 +34,6 @@ class ResetUserDownloads extends Command
|
|||||||
|
|
||||||
// Clear old downloads which have expired
|
// Clear old downloads which have expired
|
||||||
UserDownload::where('created_at', '<=', Carbon::now()->subHour(6))
|
UserDownload::where('created_at', '<=', Carbon::now()->subHour(6))
|
||||||
->forceDelete();
|
->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class SyncSubscriptionKeys extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:sync-subscription-keys';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Sync local users against active subscription keys';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$endpoint = config('services.subscription_service_host');
|
||||||
|
if (!$endpoint) {
|
||||||
|
$this->error('Missing endpoint.');
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = Http::asJson()
|
||||||
|
->acceptJson()
|
||||||
|
->timeout(20)
|
||||||
|
->retry(3, 1000)
|
||||||
|
->post($endpoint.'/api/membership/keys', [
|
||||||
|
'payload' => base64_encode(Crypt::encryptString('get-active-keys')),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $response->successful()) {
|
||||||
|
$this->error('Subscription API request failed: HTTP ' . $response->status());
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$decrypted = Crypt::decryptString(base64_decode($response->json('payload')));
|
||||||
|
$activeKeys = json_decode($decrypted, true, flags: JSON_THROW_ON_ERROR);
|
||||||
|
} catch (DecryptException $e) {
|
||||||
|
$this->error('Could not decrypt API response.');
|
||||||
|
return self::FAILURE;
|
||||||
|
} catch (\JsonException $e) {
|
||||||
|
$this->error('API returned invalid JSON.');
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! is_array($activeKeys)) {
|
||||||
|
$this->error('API response payload was not an array.');
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$activeKeys = collect($activeKeys)
|
||||||
|
->filter(fn ($key) => is_string($key) && $key !== '')
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
$this->markInactiveUsers($activeKeys);
|
||||||
|
$this->markActiveUsers($activeKeys);
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function markInactiveUsers(array $activeKeys)
|
||||||
|
{
|
||||||
|
User::query()
|
||||||
|
->whereNotNull('subscription_key')
|
||||||
|
->whereNotIn('subscription_key', $activeKeys)
|
||||||
|
->chunk(100, function ($users) {
|
||||||
|
foreach($users as $user) {
|
||||||
|
if ($user->hasRole(UserRole::SUPPORTER)) {
|
||||||
|
Log::info("Removed Supporter Role from {$user->name}");
|
||||||
|
$user->removeRole(UserRole::SUPPORTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private function markActiveUsers(array $activeKeys)
|
||||||
|
{
|
||||||
|
User::query()
|
||||||
|
->whereNotNull('subscription_key')
|
||||||
|
->whereIn('subscription_key', $activeKeys)
|
||||||
|
->chunk(100, function ($users) {
|
||||||
|
foreach($users as $user) {
|
||||||
|
if (!$user->hasRole(UserRole::SUPPORTER)) {
|
||||||
|
Log::info("Added Supporter Role for {$user->name}");
|
||||||
|
$user->addRole(UserRole::SUPPORTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum UserRole: string
|
||||||
|
{
|
||||||
|
case ADMINISTRATOR = 'admin';
|
||||||
|
case MODERATOR = 'moderator';
|
||||||
|
case SUPPORTER = 'supporter';
|
||||||
|
case BANNED = 'banned';
|
||||||
|
}
|
||||||
+19
-17
@@ -2,23 +2,23 @@
|
|||||||
|
|
||||||
namespace App\Helpers;
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use App\Models\Comment;
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
use App\Models\Hentai;
|
use App\Models\Hentai;
|
||||||
|
use App\Models\PopularDaily;
|
||||||
use App\Models\PopularMonthly;
|
use App\Models\PopularMonthly;
|
||||||
use App\Models\PopularWeekly;
|
use App\Models\PopularWeekly;
|
||||||
use App\Models\PopularDaily;
|
|
||||||
|
|
||||||
use Conner\Tagging\Model\Tag;
|
use Conner\Tagging\Model\Tag;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class CacheHelper
|
class CacheHelper
|
||||||
{
|
{
|
||||||
public static function getRecentlyReleased(bool $guest)
|
public static function getRecentlyReleased(bool $guest)
|
||||||
{
|
{
|
||||||
$guestString = $guest ? 'guest' : 'authed';
|
$guestString = $guest ? 'guest' : 'authed';
|
||||||
return Cache::remember("recently_released_".$guestString, now()->addMinutes(60), function () use ($guest) {
|
|
||||||
|
return Cache::remember('recently_released_'.$guestString, now()->addMinutes(60), function () use ($guest) {
|
||||||
return Episode::with('gallery')
|
return Episode::with('gallery')
|
||||||
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
|
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
|
||||||
->orderBy('release_date', 'desc')
|
->orderBy('release_date', 'desc')
|
||||||
@@ -30,7 +30,8 @@ class CacheHelper
|
|||||||
public static function getRecentlyUploaded(bool $guest)
|
public static function getRecentlyUploaded(bool $guest)
|
||||||
{
|
{
|
||||||
$guestString = $guest ? 'guest' : 'authed';
|
$guestString = $guest ? 'guest' : 'authed';
|
||||||
return Cache::remember("recently_uploaded".$guestString, now()->addMinutes(5), function () use ($guest) {
|
|
||||||
|
return Cache::remember('recently_uploaded'.$guestString, now()->addMinutes(5), function () use ($guest) {
|
||||||
return Episode::with('gallery')
|
return Episode::with('gallery')
|
||||||
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
|
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
@@ -41,21 +42,21 @@ class CacheHelper
|
|||||||
|
|
||||||
public static function getTotalViewCount()
|
public static function getTotalViewCount()
|
||||||
{
|
{
|
||||||
return Cache::remember("total_view_count", now()->addMinutes(60), function () {
|
return Cache::remember('total_view_count', now()->addMinutes(60), function () {
|
||||||
return Episode::sum('view_count');
|
return Episode::sum('view_count');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getTotalEpisodeCount()
|
public static function getTotalEpisodeCount()
|
||||||
{
|
{
|
||||||
return Cache::remember("total_episode_count", now()->addMinutes(60), function () {
|
return Cache::remember('total_episode_count', now()->addMinutes(60), function () {
|
||||||
return Episode::count();
|
return Episode::count();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getTotalHentaiCount()
|
public static function getTotalHentaiCount()
|
||||||
{
|
{
|
||||||
return Cache::remember("total_hentai_count", now()->addMinutes(60), function () {
|
return Cache::remember('total_hentai_count', now()->addMinutes(60), function () {
|
||||||
return Hentai::count();
|
return Hentai::count();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -63,7 +64,8 @@ class CacheHelper
|
|||||||
public static function getPopularAllTime(bool $guest)
|
public static function getPopularAllTime(bool $guest)
|
||||||
{
|
{
|
||||||
$guestString = $guest ? 'guest' : 'authed';
|
$guestString = $guest ? 'guest' : 'authed';
|
||||||
return Cache::remember("top_hentai_alltime".$guestString, now()->addMinutes(360), function () use ($guest) {
|
|
||||||
|
return Cache::remember('top_hentai_alltime'.$guestString, now()->addMinutes(360), function () use ($guest) {
|
||||||
return Episode::with('gallery')
|
return Episode::with('gallery')
|
||||||
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
|
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
|
||||||
->orderBy('view_count', 'desc')
|
->orderBy('view_count', 'desc')
|
||||||
@@ -74,7 +76,7 @@ class CacheHelper
|
|||||||
|
|
||||||
public static function getPopularMonthly()
|
public static function getPopularMonthly()
|
||||||
{
|
{
|
||||||
return Cache::remember("top_hentai_monthly", now()->addMinutes(360), function () {
|
return Cache::remember('top_hentai_monthly', now()->addMinutes(360), function () {
|
||||||
return PopularMonthly::groupBy('episode_id')
|
return PopularMonthly::groupBy('episode_id')
|
||||||
->select('episode_id', DB::raw('count(*) as total'))
|
->select('episode_id', DB::raw('count(*) as total'))
|
||||||
->with('episode.gallery')
|
->with('episode.gallery')
|
||||||
@@ -86,7 +88,7 @@ class CacheHelper
|
|||||||
|
|
||||||
public static function getPopularWeekly()
|
public static function getPopularWeekly()
|
||||||
{
|
{
|
||||||
return Cache::remember("top_hentai_weekly", now()->addMinutes(360), function () {
|
return Cache::remember('top_hentai_weekly', now()->addMinutes(360), function () {
|
||||||
return PopularWeekly::groupBy('episode_id')
|
return PopularWeekly::groupBy('episode_id')
|
||||||
->select('episode_id', DB::raw('count(*) as total'))
|
->select('episode_id', DB::raw('count(*) as total'))
|
||||||
->with('episode.gallery')
|
->with('episode.gallery')
|
||||||
@@ -99,7 +101,7 @@ class CacheHelper
|
|||||||
|
|
||||||
public static function getPopularDaily()
|
public static function getPopularDaily()
|
||||||
{
|
{
|
||||||
return Cache::remember("top_hentai_daily", now()->addMinutes(30), function () {
|
return Cache::remember('top_hentai_daily', now()->addMinutes(30), function () {
|
||||||
return PopularDaily::groupBy('episode_id')
|
return PopularDaily::groupBy('episode_id')
|
||||||
->select('episode_id', DB::raw('count(*) as total'))
|
->select('episode_id', DB::raw('count(*) as total'))
|
||||||
->with('episode.gallery')
|
->with('episode.gallery')
|
||||||
@@ -111,22 +113,22 @@ class CacheHelper
|
|||||||
|
|
||||||
public static function getMostLikes()
|
public static function getMostLikes()
|
||||||
{
|
{
|
||||||
return Cache::remember("top_likes", now()->addMinutes(30), function () {
|
return Cache::remember('top_likes', now()->addMinutes(30), function () {
|
||||||
return DB::table('markable_likes')->groupBy('markable_id')->select('markable_id', DB::raw('count(*) as total'))->orderBy('total', 'desc')->limit(16)->get();
|
return DB::table('markable_likes')->groupBy('markable_id')->select('markable_id', DB::raw('count(*) as total'))->orderBy('total', 'desc')->limit(16)->get();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAllTags()
|
public static function getAllTags()
|
||||||
{
|
{
|
||||||
return Cache::remember("all_tags", now()->addMinutes(10080), function () {
|
return Cache::remember('all_tags', now()->addMinutes(10080), function () {
|
||||||
return Tag::where('count', '>', 0)->orderBy('slug', 'ASC')->get();
|
return Tag::where('count', '>', 0)->orderBy('slug', 'ASC')->get();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getLatestComments()
|
public static function getLatestComments()
|
||||||
{
|
{
|
||||||
return Cache::remember("latest_comments", now()->addMinutes(60), function () {
|
return Cache::remember('latest_comments', now()->addMinutes(60), function () {
|
||||||
return DB::table('comments')->latest()->take(10)->get();
|
return Comment::with('user')->latest()->take(10)->get();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Conner\Tagging\Model\Tag;
|
||||||
|
|
||||||
|
class FilterCategories
|
||||||
|
{
|
||||||
|
public static function getFilterCategories()
|
||||||
|
{
|
||||||
|
$taglist = Cache::remember(
|
||||||
|
'searchtags',
|
||||||
|
300,
|
||||||
|
fn () => Tag::where('count', '>', 0)
|
||||||
|
->orderBy('slug')
|
||||||
|
->get(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$appearances = [
|
||||||
|
'Loli',
|
||||||
|
'Shota',
|
||||||
|
'Milf',
|
||||||
|
'Futanari',
|
||||||
|
'Big Boobs',
|
||||||
|
'Small Boobs',
|
||||||
|
'Dark Skin',
|
||||||
|
'Cosplay',
|
||||||
|
'Elf',
|
||||||
|
'Maid',
|
||||||
|
'Nekomimi',
|
||||||
|
'Nurse',
|
||||||
|
'School Girl',
|
||||||
|
'Succubus',
|
||||||
|
'Teacher',
|
||||||
|
'Trap',
|
||||||
|
'Pregnant',
|
||||||
|
'Glasses',
|
||||||
|
'Swim Suit',
|
||||||
|
'Ugly Bastard',
|
||||||
|
'Monster',
|
||||||
|
];
|
||||||
|
|
||||||
|
$types = [
|
||||||
|
'3D',
|
||||||
|
'4K',
|
||||||
|
'48Fps',
|
||||||
|
'4K 48Fps',
|
||||||
|
'Censored',
|
||||||
|
'Uncensored',
|
||||||
|
'Comedy',
|
||||||
|
'Fantasy',
|
||||||
|
'Horror',
|
||||||
|
'Vanilla',
|
||||||
|
'Ntr',
|
||||||
|
'Pov',
|
||||||
|
'Filmed',
|
||||||
|
'X-Ray',
|
||||||
|
];
|
||||||
|
|
||||||
|
$actions = [
|
||||||
|
'Anal',
|
||||||
|
'Bdsm',
|
||||||
|
'Facial',
|
||||||
|
'Blow Job',
|
||||||
|
'Boob Job',
|
||||||
|
'Foot Job',
|
||||||
|
'Hand Job',
|
||||||
|
'Rimjob',
|
||||||
|
'Inflation',
|
||||||
|
'Masturbation',
|
||||||
|
'Public Sex',
|
||||||
|
'Rape',
|
||||||
|
'Reverse Rape',
|
||||||
|
'Threesome',
|
||||||
|
'Orgy',
|
||||||
|
'Gangbang',
|
||||||
|
];
|
||||||
|
|
||||||
|
$excluded = [...$appearances, ...$types, ...$actions];
|
||||||
|
|
||||||
|
$categories = [
|
||||||
|
'Genres' => $taglist
|
||||||
|
->reject(fn ($tag) => in_array($tag->name, $excluded))
|
||||||
|
->pluck('name')
|
||||||
|
->toArray(),
|
||||||
|
|
||||||
|
'Actions' => $actions,
|
||||||
|
|
||||||
|
'Appearance' => collect($appearances)
|
||||||
|
->reject(function ($tag) {
|
||||||
|
return Auth::guest() && in_array($tag, ['Loli', 'Shota']);
|
||||||
|
})
|
||||||
|
->toArray(),
|
||||||
|
|
||||||
|
'Types' => $types,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $categories;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ class GitHelper
|
|||||||
{
|
{
|
||||||
public static function shortCommit()
|
public static function shortCommit()
|
||||||
{
|
{
|
||||||
return Cache::remember("git_commit", now()->addMinutes(60), function () {
|
return Cache::remember('git_commit', now()->addMinutes(60), function () {
|
||||||
try {
|
try {
|
||||||
return trim(exec('git rev-parse --short HEAD'));
|
return trim(exec('git rev-parse --short HEAD'));
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ namespace App\Http\Controllers\Admin;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Alert;
|
use App\Models\Alert;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class AlertController extends Controller
|
class AlertController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Display alert index page
|
* Display alert index page
|
||||||
*/
|
*/
|
||||||
public function index(): \Illuminate\View\View
|
public function index(): View
|
||||||
{
|
{
|
||||||
return view('admin.alert.index');
|
return view('admin.alert.index');
|
||||||
}
|
}
|
||||||
@@ -19,7 +21,7 @@ class AlertController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Create Alert.
|
* Create Alert.
|
||||||
*/
|
*/
|
||||||
public function store(Request $request): \Illuminate\Http\RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'message' => 'required|string|max:255',
|
'message' => 'required|string|max:255',
|
||||||
@@ -39,9 +41,9 @@ class AlertController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Delete Alert.
|
* Delete Alert.
|
||||||
*/
|
*/
|
||||||
public function delete(int $alert_id): \Illuminate\Http\RedirectResponse
|
public function delete(int $alert_id): RedirectResponse
|
||||||
{
|
{
|
||||||
Alert::where('id', $alert_id)->forceDelete();
|
Alert::where('id', $alert_id)->delete();
|
||||||
|
|
||||||
cache()->forget('alerts');
|
cache()->forget('alerts');
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class CommentsController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display Comments Page.
|
||||||
|
*/
|
||||||
|
public function index(): View
|
||||||
|
{
|
||||||
|
return view('admin.comments.index');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,25 +4,27 @@ namespace App\Http\Controllers\Admin;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Contact;
|
use App\Models\Contact;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class ContactController extends Controller
|
class ContactController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Display Contact Page.
|
* Display Contact Page.
|
||||||
*/
|
*/
|
||||||
public function index(): \Illuminate\View\View
|
public function index(): View
|
||||||
{
|
{
|
||||||
$contacts = Contact::orderBy('created_at', 'DESC')->get();
|
$contacts = Contact::orderBy('created_at', 'DESC')->get();
|
||||||
|
|
||||||
return view('admin.contact.index', [
|
return view('admin.contact.index', [
|
||||||
'contacts' => $contacts
|
'contacts' => $contacts,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete Contact.
|
* Delete Contact.
|
||||||
*/
|
*/
|
||||||
public function delete(int $contact_id): \Illuminate\Http\RedirectResponse
|
public function delete(int $contact_id): RedirectResponse
|
||||||
{
|
{
|
||||||
Contact::where('id', $contact_id)->delete();
|
Contact::where('id', $contact_id)->delete();
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,22 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Episode;
|
|
||||||
|
|
||||||
use App\Jobs\DiscordReleaseNotification;
|
use App\Jobs\DiscordReleaseNotification;
|
||||||
|
use App\Models\Episode;
|
||||||
use App\Services\DownloadService;
|
use App\Services\DownloadService;
|
||||||
use App\Services\EpisodeService;
|
use App\Services\EpisodeService;
|
||||||
use App\Services\GalleryService;
|
use App\Services\GalleryService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class EpisodeController extends Controller
|
class EpisodeController extends Controller
|
||||||
{
|
{
|
||||||
protected EpisodeService $episodeService;
|
protected EpisodeService $episodeService;
|
||||||
|
|
||||||
protected GalleryService $galleryService;
|
protected GalleryService $galleryService;
|
||||||
|
|
||||||
protected DownloadService $downloadService;
|
protected DownloadService $downloadService;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -30,7 +33,7 @@ class EpisodeController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Add Episode to existing series
|
* Add Episode to existing series
|
||||||
*/
|
*/
|
||||||
public function store(Request $request): \Illuminate\Http\RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$referenceEpisode = Episode::with('hentai')->where('id', $request->input('episode_id'))->firstOrFail();
|
$referenceEpisode = Episode::with('hentai')->where('id', $request->input('episode_id'))->firstOrFail();
|
||||||
$episodeNumber = $referenceEpisode->hentai->episodes()->count() + 1;
|
$episodeNumber = $referenceEpisode->hentai->episodes()->count() + 1;
|
||||||
@@ -43,7 +46,7 @@ class EpisodeController extends Controller
|
|||||||
|
|
||||||
// Discord Alert
|
// Discord Alert
|
||||||
if ($request->has('censored')) {
|
if ($request->has('censored')) {
|
||||||
DiscordReleaseNotification::dispatch($referenceEpisode->title." - ".$episodeNumber, 'release-censored');
|
DiscordReleaseNotification::dispatch($referenceEpisode->title.' - '.$episodeNumber, 'release-censored');
|
||||||
} else {
|
} else {
|
||||||
DiscordReleaseNotification::dispatch($episode->slug, 'release');
|
DiscordReleaseNotification::dispatch($episode->slug, 'release');
|
||||||
}
|
}
|
||||||
@@ -51,16 +54,27 @@ class EpisodeController extends Controller
|
|||||||
cache()->flush();
|
cache()->flush();
|
||||||
|
|
||||||
return to_route('hentai.index', [
|
return to_route('hentai.index', [
|
||||||
'title' => $episode->slug
|
'title' => $episode->slug,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit Episode
|
* Edit Episode
|
||||||
*/
|
*/
|
||||||
public function update(Request $request): \Illuminate\Http\RedirectResponse
|
public function update(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$episode = Episode::with('hentai')->where('id', $request->input('episode_id'))->firstOrFail();
|
$episode = Episode::with('hentai')->where('id', $request->input('episode_id'))->firstOrFail();
|
||||||
|
|
||||||
|
if ($request->user()->hasRole(UserRole::MODERATOR)) {
|
||||||
|
$this->episodeService->updateEpisodeModerator($request, $episode->id);
|
||||||
|
|
||||||
|
cache()->flush();
|
||||||
|
|
||||||
|
return to_route('hentai.index', [
|
||||||
|
'title' => $episode->slug,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$studio = $this->episodeService->getOrCreateStudio(json_decode($request->input('studio'))[0]->value);
|
$studio = $this->episodeService->getOrCreateStudio(json_decode($request->input('studio'))[0]->value);
|
||||||
|
|
||||||
$oldinterpolated = $episode->interpolated;
|
$oldinterpolated = $episode->interpolated;
|
||||||
@@ -87,7 +101,7 @@ class EpisodeController extends Controller
|
|||||||
cache()->flush();
|
cache()->flush();
|
||||||
|
|
||||||
return to_route('hentai.index', [
|
return to_route('hentai.index', [
|
||||||
'title' => $episode->slug
|
'title' => $episode->slug,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,21 @@
|
|||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Hentai;
|
|
||||||
|
|
||||||
use App\Jobs\DiscordReleaseNotification;
|
use App\Jobs\DiscordReleaseNotification;
|
||||||
|
use App\Models\Hentai;
|
||||||
use App\Services\DownloadService;
|
use App\Services\DownloadService;
|
||||||
use App\Services\EpisodeService;
|
use App\Services\EpisodeService;
|
||||||
use App\Services\GalleryService;
|
use App\Services\GalleryService;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class ReleaseController extends Controller
|
class ReleaseController extends Controller
|
||||||
{
|
{
|
||||||
protected EpisodeService $episodeService;
|
protected EpisodeService $episodeService;
|
||||||
|
|
||||||
protected GalleryService $galleryService;
|
protected GalleryService $galleryService;
|
||||||
|
|
||||||
protected DownloadService $downloadService;
|
protected DownloadService $downloadService;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -30,7 +33,7 @@ class ReleaseController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display release page
|
* Display release page
|
||||||
*/
|
*/
|
||||||
public function index(): \Illuminate\View\View
|
public function index(): View
|
||||||
{
|
{
|
||||||
return view('admin.release.create');
|
return view('admin.release.create');
|
||||||
}
|
}
|
||||||
@@ -38,7 +41,7 @@ class ReleaseController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Upload New Hentai with One or Multipe Episodes
|
* Upload New Hentai with One or Multipe Episodes
|
||||||
*/
|
*/
|
||||||
public function store(Request $request): \Illuminate\Http\RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
// Create new Hentai or find existing one
|
// Create new Hentai or find existing one
|
||||||
$slug = $this->episodeService->generateSlug($request->input('title'));
|
$slug = $this->episodeService->generateSlug($request->input('title'));
|
||||||
|
|||||||
@@ -3,22 +3,22 @@
|
|||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
use App\Models\SiteBackground;
|
use App\Models\SiteBackground;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Intervention\Image\Laravel\Facades\Image;
|
use Illuminate\View\View;
|
||||||
use Intervention\Image\Encoders\WebpEncoder;
|
use Intervention\Image\Encoders\WebpEncoder;
|
||||||
|
use Intervention\Image\Laravel\Facades\Image;
|
||||||
|
|
||||||
class SiteBackgroundController extends Controller
|
class SiteBackgroundController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Display admin index page
|
* Display admin index page
|
||||||
*/
|
*/
|
||||||
public function index(): \Illuminate\View\View
|
public function index(): View
|
||||||
{
|
{
|
||||||
return view('admin.background.index', [
|
return view('admin.background.index', [
|
||||||
'images' => SiteBackground::all(),
|
'images' => SiteBackground::all(),
|
||||||
@@ -28,7 +28,7 @@ class SiteBackgroundController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Create new site backgrounds
|
* Create new site backgrounds
|
||||||
*/
|
*/
|
||||||
public function create(Request $request): \Illuminate\Http\RedirectResponse
|
public function create(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'images' => 'required',
|
'images' => 'required',
|
||||||
@@ -44,7 +44,7 @@ class SiteBackgroundController extends Controller
|
|||||||
$bg = SiteBackground::create(array_merge(
|
$bg = SiteBackground::create(array_merge(
|
||||||
$request->only(['date_start', 'date_end']),
|
$request->only(['date_start', 'date_end']),
|
||||||
[
|
[
|
||||||
'default' => (bool) $request->input('default', false)
|
'default' => (bool) $request->input('default', false),
|
||||||
]
|
]
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -55,13 +55,14 @@ class SiteBackgroundController extends Controller
|
|||||||
|
|
||||||
Image::read($file->getRealPath())
|
Image::read($file->getRealPath())
|
||||||
->scaleDown(height: $resolution)
|
->scaleDown(height: $resolution)
|
||||||
->encode(new WebpEncoder())
|
->encode(new WebpEncoder)
|
||||||
->save(public_path($targetPath));
|
->save(public_path($targetPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
Log::error($e->getMessage());
|
Log::error($e->getMessage());
|
||||||
|
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ class SiteBackgroundController extends Controller
|
|||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request): \Illuminate\Http\RedirectResponse
|
public function update(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'id' => 'required|exists:site_backgrounds,id',
|
'id' => 'required|exists:site_backgrounds,id',
|
||||||
@@ -85,7 +86,7 @@ class SiteBackgroundController extends Controller
|
|||||||
SiteBackground::where('id', $request->input('id'))->update(array_merge(
|
SiteBackground::where('id', $request->input('id'))->update(array_merge(
|
||||||
$request->only(['date_start', 'date_end']),
|
$request->only(['date_start', 'date_end']),
|
||||||
[
|
[
|
||||||
'default' => (bool) $request->input('default', false)
|
'default' => (bool) $request->input('default', false),
|
||||||
]
|
]
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ class SiteBackgroundController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Delete backround
|
* Delete backround
|
||||||
*/
|
*/
|
||||||
public function delete(Request $request): \Illuminate\Http\RedirectResponse
|
public function delete(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$id = $request->input('id');
|
$id = $request->input('id');
|
||||||
|
|
||||||
@@ -105,7 +106,7 @@ class SiteBackgroundController extends Controller
|
|||||||
DB::beginTransaction();
|
DB::beginTransaction();
|
||||||
|
|
||||||
$bg = SiteBackground::where('id', $id)->firstOrFail();
|
$bg = SiteBackground::where('id', $id)->firstOrFail();
|
||||||
$bg->forceDelete();
|
$bg->delete();
|
||||||
|
|
||||||
$resolutions = [1440, 1080, 720, 640];
|
$resolutions = [1440, 1080, 720, 640];
|
||||||
try {
|
try {
|
||||||
@@ -116,6 +117,7 @@ class SiteBackgroundController extends Controller
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
DB::rollBack();
|
DB::rollBack();
|
||||||
Log::error($e->getMessage());
|
Log::error($e->getMessage());
|
||||||
|
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
use App\Models\EpisodeSubtitle;
|
use App\Models\EpisodeSubtitle;
|
||||||
use App\Models\Subtitle;
|
use App\Models\Subtitle;
|
||||||
use App\Http\Controllers\Controller;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class SubtitleController extends Controller
|
class SubtitleController extends Controller
|
||||||
@@ -13,7 +14,7 @@ class SubtitleController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Add new Subtitle.
|
* Add new Subtitle.
|
||||||
*/
|
*/
|
||||||
public function store(Request $request): \Illuminate\Http\RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$subtitle = Subtitle::create([
|
$subtitle = Subtitle::create([
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
@@ -32,13 +33,13 @@ class SubtitleController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Update Episode Subtitles.
|
* Update Episode Subtitles.
|
||||||
*/
|
*/
|
||||||
public function update(Request $request): \Illuminate\Http\RedirectResponse
|
public function update(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$episode = Episode::where('id', $request->input('episode_id'))->firstOrFail();
|
$episode = Episode::where('id', $request->input('episode_id'))->firstOrFail();
|
||||||
|
|
||||||
// Clear everything
|
// Clear everything
|
||||||
foreach ($episode->subtitles as $sub) {
|
foreach ($episode->subtitles as $sub) {
|
||||||
$sub->forceDelete();
|
$sub->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $request->input('subtitles')) {
|
if (! $request->input('subtitles')) {
|
||||||
|
|||||||
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Enums\UserRole;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Display Users Page.
|
* Display Users Page.
|
||||||
*/
|
*/
|
||||||
public function index(): \Illuminate\View\View
|
public function index(): View
|
||||||
{
|
{
|
||||||
return view('admin.users.index');
|
return view('admin.users.index');
|
||||||
}
|
}
|
||||||
@@ -26,16 +28,15 @@ class UserController extends Controller
|
|||||||
'action' => 'required',
|
'action' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
$user = User::findOrFail($validated['id']);
|
$user = User::findOrFail($validated['id']);
|
||||||
|
|
||||||
switch ($validated['action']) {
|
switch ($validated['action']) {
|
||||||
case 'ban':
|
case 'ban':
|
||||||
$user->update(['is_banned' => 1]);
|
$user->addRole(UserRole::BANNED);
|
||||||
alert()->success('Banned', 'User has been banned.');
|
alert()->success('Banned', 'User has been banned.');
|
||||||
break;
|
break;
|
||||||
case 'unban':
|
case 'unban':
|
||||||
$user->update(['is_banned' => 0]);
|
$user->removeRole(UserRole::BANNED);
|
||||||
alert()->success('Unbanned', 'User has been unbanned.');
|
alert()->success('Unbanned', 'User has been unbanned.');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Helpers\CacheHelper;
|
use App\Helpers\CacheHelper;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
use App\Models\Studios;
|
use App\Models\Studios;
|
||||||
use App\Models\Subtitle;
|
use App\Models\Subtitle;
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
|
|
||||||
class AdminApiController extends Controller
|
class AdminApiController extends Controller
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Downloads;
|
use App\Models\Downloads;
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
|
use App\Rules\ValidCaptcha;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
|
|
||||||
class DownloadApiController extends Controller
|
class DownloadApiController extends Controller
|
||||||
{
|
{
|
||||||
@@ -16,11 +16,12 @@ class DownloadApiController extends Controller
|
|||||||
public function getDownload(Request $request)
|
public function getDownload(Request $request)
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'episode_id' => 'required',
|
'episode_id' => ['required'],
|
||||||
'captcha' => 'required|captcha'
|
'captcha' => ['required', new ValidCaptcha],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$episode = Episode::where('id', $request->input('episode_id'))->firstOrFail();
|
$episode = Episode::where('id', $request->input('episode_id'))
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
// Increase download count, as we assume the user
|
// Increase download count, as we assume the user
|
||||||
// downloads after submitting the captcha
|
// downloads after submitting the captcha
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Hentai;
|
use App\Models\Hentai;
|
||||||
use App\Models\PopularMonthly;
|
use App\Models\PopularMonthly;
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
|
|
||||||
class HentaiApiController extends Controller
|
class HentaiApiController extends Controller
|
||||||
{
|
{
|
||||||
@@ -46,6 +46,8 @@ class HentaiApiController extends Controller
|
|||||||
// Cache for 60 minutes
|
// Cache for 60 minutes
|
||||||
$data = Cache::remember('api_monthly_views', now()->addMinutes(60), function () {
|
$data = Cache::remember('api_monthly_views', now()->addMinutes(60), function () {
|
||||||
return PopularMonthly::selectRaw('DATE(created_at) as date, COUNT(*) as count')
|
return PopularMonthly::selectRaw('DATE(created_at) as date, COUNT(*) as count')
|
||||||
|
->whereDate('created_at', '<', Carbon::today())
|
||||||
|
->whereDate('created_at', '>=', Carbon::today()->subDays(28))
|
||||||
->groupBy('date')
|
->groupBy('date')
|
||||||
->orderBy('date', 'asc')
|
->orderBy('date', 'asc')
|
||||||
->get();
|
->get();
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class StreamApiController extends Controller
|
class StreamApiController extends Controller
|
||||||
@@ -30,10 +28,10 @@ class StreamApiController extends Controller
|
|||||||
'poster' => $episode->gallery()->first()->image_url,
|
'poster' => $episode->gallery()->first()->image_url,
|
||||||
'interpolated' => $episode->interpolated,
|
'interpolated' => $episode->interpolated,
|
||||||
'interpolated_uhd' => $episode->interpolated_uhd,
|
'interpolated_uhd' => $episode->interpolated_uhd,
|
||||||
'stream_url' => $episode->url,
|
'stream_url' => $episode->dmca_takedown ? 'stuff/dmca' : $episode->url,
|
||||||
'stream_domains' => config('hstream.stream_domain'),
|
'stream_domains' => config('hstream.stream_domain'),
|
||||||
'asia_stream_domains' => config('hstream.asia_stream_domain'),
|
'asia_stream_domains' => config('hstream.asia_stream_domain'),
|
||||||
'extra_subtitles' => $subtitles
|
'extra_subtitles' => $subtitles,
|
||||||
], 200);
|
], 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ namespace App\Http\Controllers\Api;
|
|||||||
|
|
||||||
use App\Helpers\CacheHelper;
|
use App\Helpers\CacheHelper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
use Conner\Tagging\Model\Tag;
|
use Conner\Tagging\Model\Tag;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class UserApiController extends Controller
|
class UserApiController extends Controller
|
||||||
{
|
{
|
||||||
@@ -33,11 +32,10 @@ class UserApiController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'success',
|
'message' => 'success',
|
||||||
'tags' => $tagWhiteList,
|
'tags' => $tagWhiteList,
|
||||||
'usertags' => $tagBlackList
|
'usertags' => $tagBlackList,
|
||||||
], 200);
|
], 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Http\Controllers\Auth;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Auth\LoginRequest;
|
use App\Http\Requests\Auth\LoginRequest;
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
@@ -29,7 +28,7 @@ class AuthenticatedSessionController extends Controller
|
|||||||
|
|
||||||
$request->session()->regenerate();
|
$request->session()->regenerate();
|
||||||
|
|
||||||
return redirect()->intended(RouteServiceProvider::HOME);
|
return redirect()->intended(route('home.index', absolute: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use AltchaOrg\Altcha\Algorithm\Pbkdf2;
|
||||||
|
use AltchaOrg\Altcha\Altcha;
|
||||||
|
use AltchaOrg\Altcha\CreateChallengeOptions;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
|
class CaptchaController extends Controller
|
||||||
|
{
|
||||||
|
public function create(): array
|
||||||
|
{
|
||||||
|
$pbkdf2 = new Pbkdf2;
|
||||||
|
|
||||||
|
$altcha = new Altcha(
|
||||||
|
hmacSignatureSecret: config('captcha.hmac_key'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create challenge
|
||||||
|
$challenge = $altcha->createChallenge(new CreateChallengeOptions(
|
||||||
|
algorithm: $pbkdf2,
|
||||||
|
cost: 5000,
|
||||||
|
counter: random_int(5000, 10000),
|
||||||
|
expiresAt: time() + 600,
|
||||||
|
));
|
||||||
|
|
||||||
|
return get_object_vars($challenge);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
@@ -36,6 +35,6 @@ class ConfirmablePasswordController extends Controller
|
|||||||
|
|
||||||
$request->session()->put('auth.password_confirmed_at', time());
|
$request->session()->put('auth.password_confirmed_at', time());
|
||||||
|
|
||||||
return redirect()->intended(RouteServiceProvider::HOME);
|
return redirect()->intended(route('home.index', absolute: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Laravel\Socialite\Facades\Socialite;
|
||||||
|
|
||||||
|
class DiscordAuthController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Redirect to Discord
|
||||||
|
*/
|
||||||
|
public function redirect(): RedirectResponse
|
||||||
|
{
|
||||||
|
return Socialite::driver('discord')->redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback received from Discord
|
||||||
|
*/
|
||||||
|
public function callback(): RedirectResponse
|
||||||
|
{
|
||||||
|
$discordUser = Socialite::driver('discord')->user();
|
||||||
|
|
||||||
|
$user = User::where('discord_id', $discordUser->id)->first();
|
||||||
|
|
||||||
|
if (! $user) {
|
||||||
|
// link by email if it already exists
|
||||||
|
$user = User::where('email', $discordUser->email)->first();
|
||||||
|
|
||||||
|
if ($user) {
|
||||||
|
$user->update([
|
||||||
|
'discord_id' => $discordUser->id,
|
||||||
|
'discord_avatar' => $discordUser->avatar,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// Create new user
|
||||||
|
$user = User::create([
|
||||||
|
'name' => $discordUser->name,
|
||||||
|
'email' => $discordUser->email,
|
||||||
|
'discord_id' => $discordUser->id,
|
||||||
|
'discord_avatar' => $discordUser->avatar,
|
||||||
|
'password' => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->checkDiscordAvatar($discordUser, $user);
|
||||||
|
$this->checkDiscordRoles($user);
|
||||||
|
|
||||||
|
Auth::login($user, true);
|
||||||
|
|
||||||
|
return redirect()->route('home.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if discord avatar changed
|
||||||
|
*/
|
||||||
|
private function checkDiscordAvatar(\Laravel\Socialite\Contracts\User $socialiteUser, User $user): void
|
||||||
|
{
|
||||||
|
if ($socialiteUser->avatar != $user->discord_avatar) {
|
||||||
|
$user->update(['discord_avatar' => $socialiteUser->avatar]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check Discord Roles if user is Patreon member
|
||||||
|
*/
|
||||||
|
private function checkDiscordRoles(User $user): void
|
||||||
|
{
|
||||||
|
// Should not ever happen
|
||||||
|
if (! $user->discord_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$guildId = config('discord.guild_id');
|
||||||
|
|
||||||
|
$response = Http::withToken(config('discord.discord_bot_token'), 'Bot')
|
||||||
|
->timeout(5)
|
||||||
|
->get("https://discord.com/api/v10/guilds/{$guildId}/members/{$user->discord_id}");
|
||||||
|
|
||||||
|
// User is not in the guild
|
||||||
|
if ($response->status() === 404) {
|
||||||
|
$user->removeRole(UserRole::SUPPORTER);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Something else failed
|
||||||
|
if ($response->failed()) {
|
||||||
|
Log::warning('Discord role check failed', [
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'discord_id' => $user->discord_id,
|
||||||
|
'status' => $response->status(),
|
||||||
|
'body' => $response->body(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$discordRoles = $response->json('roles', []);
|
||||||
|
$patreonRoles = config('discord.patreon_roles', []);
|
||||||
|
|
||||||
|
// If intersect of array is empty, then the user doesn't have the role
|
||||||
|
$hasSupporterRole = ! empty(array_intersect($discordRoles, $patreonRoles));
|
||||||
|
|
||||||
|
if (! $hasSupporterRole) {
|
||||||
|
// Remove role if not found
|
||||||
|
$user->removeRole(UserRole::SUPPORTER);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->addRole(UserRole::SUPPORTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@ class EmailVerificationNotificationController extends Controller
|
|||||||
public function store(Request $request): RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
if ($request->user()->hasVerifiedEmail()) {
|
if ($request->user()->hasVerifiedEmail()) {
|
||||||
return redirect()->intended(RouteServiceProvider::HOME);
|
return redirect()->intended(route('home.index', absolute: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
$request->user()->sendEmailVerificationNotification();
|
$request->user()->sendEmailVerificationNotification();
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
@@ -16,7 +15,7 @@ class EmailVerificationPromptController extends Controller
|
|||||||
public function __invoke(Request $request): RedirectResponse|View
|
public function __invoke(Request $request): RedirectResponse|View
|
||||||
{
|
{
|
||||||
return $request->user()->hasVerifiedEmail()
|
return $request->user()->hasVerifiedEmail()
|
||||||
? redirect()->intended(RouteServiceProvider::HOME)
|
? redirect()->intended(route('home.index', absolute: false))
|
||||||
: view('auth.verify-email');
|
: view('auth.verify-email');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Auth\Events\PasswordReset;
|
use Illuminate\Auth\Events\PasswordReset;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -10,6 +11,7 @@ use Illuminate\Support\Facades\Hash;
|
|||||||
use Illuminate\Support\Facades\Password;
|
use Illuminate\Support\Facades\Password;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Validation\Rules;
|
use Illuminate\Validation\Rules;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class NewPasswordController extends Controller
|
class NewPasswordController extends Controller
|
||||||
@@ -25,7 +27,7 @@ class NewPasswordController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Handle an incoming new password request.
|
* Handle an incoming new password request.
|
||||||
*
|
*
|
||||||
* @throws \Illuminate\Validation\ValidationException
|
* @throws ValidationException
|
||||||
*/
|
*/
|
||||||
public function store(Request $request): RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
@@ -40,7 +42,7 @@ class NewPasswordController extends Controller
|
|||||||
// database. Otherwise we will parse the error and return the response.
|
// database. Otherwise we will parse the error and return the response.
|
||||||
$status = Password::reset(
|
$status = Password::reset(
|
||||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||||
function ($user) use ($request) {
|
function (User $user) use ($request) {
|
||||||
$user->forceFill([
|
$user->forceFill([
|
||||||
'password' => Hash::make($request->password),
|
'password' => Hash::make($request->password),
|
||||||
'remember_token' => Str::random(60),
|
'remember_token' => Str::random(60),
|
||||||
|
|||||||
@@ -15,6 +15,19 @@ class PasswordController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function update(Request $request): RedirectResponse
|
public function update(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
// If user logged in with Discord and has not yet a password, allow to set password
|
||||||
|
if ($request->user()->discord_id && is_null($request->user()->password)) {
|
||||||
|
$validated = $request->validateWithBag('updatePassword', [
|
||||||
|
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$request->user()->update([
|
||||||
|
'password' => Hash::make($validated['password']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return back()->with('status', 'password-updated');
|
||||||
|
}
|
||||||
|
|
||||||
$validated = $request->validateWithBag('updatePassword', [
|
$validated = $request->validateWithBag('updatePassword', [
|
||||||
'current_password' => ['required', 'current_password'],
|
'current_password' => ['required', 'current_password'],
|
||||||
'password' => ['required', Password::defaults(), 'confirmed'],
|
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
|
|||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Password;
|
use Illuminate\Support\Facades\Password;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class PasswordResetLinkController extends Controller
|
class PasswordResetLinkController extends Controller
|
||||||
@@ -21,7 +22,7 @@ class PasswordResetLinkController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Handle an incoming password reset link request.
|
* Handle an incoming password reset link request.
|
||||||
*
|
*
|
||||||
* @throws \Illuminate\Validation\ValidationException
|
* @throws ValidationException
|
||||||
*/
|
*/
|
||||||
public function store(Request $request): RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,36 +4,29 @@ namespace App\Http\Controllers\Auth;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Providers\RouteServiceProvider;
|
use App\Rules\ValidCaptcha;
|
||||||
use Illuminate\Auth\Events\Registered;
|
use Illuminate\Auth\Events\Registered;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Validation\Rules;
|
use Illuminate\Validation\Rules;
|
||||||
use Illuminate\View\View;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
class RegisteredUserController extends Controller
|
class RegisteredUserController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Display the registration view.
|
|
||||||
*/
|
|
||||||
public function create(): View
|
|
||||||
{
|
|
||||||
return view('auth.register');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an incoming registration request.
|
* Handle an incoming registration request.
|
||||||
*
|
*
|
||||||
* @throws \Illuminate\Validation\ValidationException
|
* @throws ValidationException
|
||||||
*/
|
*/
|
||||||
public function store(Request $request): RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:'.User::class],
|
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
||||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||||
|
'altcha' => ['required', new ValidCaptcha],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = User::create([
|
$user = User::create([
|
||||||
@@ -46,6 +39,6 @@ class RegisteredUserController extends Controller
|
|||||||
|
|
||||||
Auth::login($user);
|
Auth::login($user);
|
||||||
|
|
||||||
return redirect(RouteServiceProvider::HOME);
|
return redirect(route('home.index', absolute: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Auth\Events\Verified;
|
use Illuminate\Auth\Events\Verified;
|
||||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
@@ -16,13 +15,13 @@ class VerifyEmailController extends Controller
|
|||||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||||
{
|
{
|
||||||
if ($request->user()->hasVerifiedEmail()) {
|
if ($request->user()->hasVerifiedEmail()) {
|
||||||
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
|
return redirect()->intended(route('home.index', absolute: false).'?verified=1');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->user()->markEmailAsVerified()) {
|
if ($request->user()->markEmailAsVerified()) {
|
||||||
event(new Verified($request->user()));
|
event(new Verified($request->user()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
|
return redirect()->intended(route('home.index', absolute: false).'?verified=1');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,17 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Contact;
|
use App\Models\Contact;
|
||||||
|
use App\Rules\ValidCaptcha;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class ContactController extends Controller
|
class ContactController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Display Contact Page.
|
* Display Contact Page.
|
||||||
*/
|
*/
|
||||||
public function index(): \Illuminate\View\View
|
public function index(): View
|
||||||
{
|
{
|
||||||
return view('contact.form');
|
return view('contact.form');
|
||||||
}
|
}
|
||||||
@@ -18,17 +21,17 @@ class ContactController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Store Contact Submission.
|
* Store Contact Submission.
|
||||||
*/
|
*/
|
||||||
public function store(Request $request): \Illuminate\Http\RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'name' => 'required|max:30',
|
'name' => 'required|max:30',
|
||||||
'email' => 'required|max:50',
|
'email' => 'required|max:50',
|
||||||
'message' => 'required|max:1000',
|
'message' => 'required|max:1000',
|
||||||
'subject' => 'required|max:50',
|
'subject' => 'required|max:50',
|
||||||
'captcha' => 'required|captcha',
|
'altcha' => ['required', new ValidCaptcha],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$contact = new Contact();
|
$contact = new Contact;
|
||||||
$contact->name = $request->input('name');
|
$contact->name = $request->input('name');
|
||||||
$contact->email = $request->input('email');
|
$contact->email = $request->input('email');
|
||||||
$contact->message = $request->input('message');
|
$contact->message = $request->input('message');
|
||||||
@@ -37,9 +40,4 @@ class ContactController extends Controller
|
|||||||
|
|
||||||
return back()->with('status', 'contact-submitted');
|
return back()->with('status', 'contact-submitted');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reloadCaptcha(): \Illuminate\Http\JsonResponse
|
|
||||||
{
|
|
||||||
return response()->json(['captcha'=> captcha_img()]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,32 +2,32 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Episode;
|
|
||||||
use App\Helpers\CacheHelper;
|
use App\Helpers\CacheHelper;
|
||||||
|
use App\Models\Episode;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Cookie;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class HomeController extends Controller
|
class HomeController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Display Home Page.
|
* Display Home Page.
|
||||||
*/
|
*/
|
||||||
public function index(): \Illuminate\View\View
|
public function index(): View
|
||||||
{
|
{
|
||||||
$guest = Auth::guest();
|
$guest = Auth::guest();
|
||||||
|
|
||||||
$guestString = $guest ? 'guest' : 'authed';
|
$guestString = $guest ? 'guest' : 'authed';
|
||||||
|
|
||||||
$mostLikes = \cache()->remember('mostLikes'.$guestString, 300, fn () =>
|
$mostLikes = \cache()->remember('mostLikes'.$guestString, 300, fn () => Episode::with('gallery')
|
||||||
Episode::with('gallery')
|
|
||||||
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
|
->when($guest, fn ($query) => $query->withoutTags(['loli', 'shota']))
|
||||||
->whereIn('id', function ($query) {
|
->whereIn('id', function ($query) {
|
||||||
$mostLikesIds = CacheHelper::getMostLikes()->pluck('markable_id')->toArray();
|
$mostLikesIds = CacheHelper::getMostLikes()->pluck('markable_id')->toArray();
|
||||||
$query->selectRaw('id')
|
$query->selectRaw('id')
|
||||||
->from('episodes')
|
->from('episodes')
|
||||||
->whereIn('id', $mostLikesIds)
|
->whereIn('id', $mostLikesIds)
|
||||||
->orderByRaw("FIELD(id, " . implode(',', $mostLikesIds) . ")");
|
->orderByRaw('FIELD(id, '.implode(',', $mostLikesIds).')');
|
||||||
})
|
})
|
||||||
->get()
|
->get()
|
||||||
);
|
);
|
||||||
@@ -47,7 +47,7 @@ class HomeController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display Banned Page.
|
* Display Banned Page.
|
||||||
*/
|
*/
|
||||||
public function banned(): \Illuminate\View\View
|
public function banned(): View
|
||||||
{
|
{
|
||||||
return view('auth.banned');
|
return view('auth.banned');
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ class HomeController extends Controller
|
|||||||
* Redirects to a random Hentai episode
|
* Redirects to a random Hentai episode
|
||||||
* Done due to performance reasons
|
* Done due to performance reasons
|
||||||
*/
|
*/
|
||||||
public function random(): \Illuminate\Http\RedirectResponse
|
public function random(): RedirectResponse
|
||||||
{
|
{
|
||||||
$random = Episode::inRandomOrder()
|
$random = Episode::inRandomOrder()
|
||||||
->limit(1)
|
->limit(1)
|
||||||
@@ -71,7 +71,7 @@ class HomeController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display Search Page.
|
* Display Search Page.
|
||||||
*/
|
*/
|
||||||
public function search(): \Illuminate\View\View
|
public function search(): View
|
||||||
{
|
{
|
||||||
return view('search.index');
|
return view('search.index');
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ class HomeController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display Download Search Page.
|
* Display Download Search Page.
|
||||||
*/
|
*/
|
||||||
public function downloadSearch(): \Illuminate\View\View
|
public function downloadSearch(): View
|
||||||
{
|
{
|
||||||
return view('search.download');
|
return view('search.download');
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ class HomeController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Redirect POST Data to GET with Query String.
|
* Redirect POST Data to GET with Query String.
|
||||||
*/
|
*/
|
||||||
public function searchRedirect(Request $request): \Illuminate\Http\RedirectResponse
|
public function searchRedirect(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
return redirect()->route('hentai.search', [
|
return redirect()->route('hentai.search', [
|
||||||
'search' => $request->input('live-search'),
|
'search' => $request->input('live-search'),
|
||||||
@@ -97,7 +97,7 @@ class HomeController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display Stats Page.
|
* Display Stats Page.
|
||||||
*/
|
*/
|
||||||
public function stats(): \Illuminate\View\View
|
public function stats(): View
|
||||||
{
|
{
|
||||||
return view('home.stats', [
|
return view('home.stats', [
|
||||||
'viewCount' => CacheHelper::getTotalViewCount(),
|
'viewCount' => CacheHelper::getTotalViewCount(),
|
||||||
@@ -109,13 +109,15 @@ class HomeController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Manually set website language
|
* Manually set website language
|
||||||
*/
|
*/
|
||||||
public function updateLanguage(Request $request): \Illuminate\Http\RedirectResponse
|
public function updateLanguage(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
if(! in_array($request->language, config('lang-detector.languages'))) {
|
abort_unless(in_array($request->language, config('app.supported_locales'), true), 404);
|
||||||
return redirect()->back();
|
|
||||||
}
|
|
||||||
|
|
||||||
Cookie::queue(Cookie::forever('locale', $request->language));
|
session(['locale' => $request->language]);
|
||||||
|
|
||||||
|
if (Auth::check()) {
|
||||||
|
Auth::user()->update(['locale' => $request->language]);
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\MatrixRegisterRequest;
|
||||||
|
use App\Services\MatrixRegistrationService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
|
||||||
|
class MatrixController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display the user page.
|
||||||
|
*/
|
||||||
|
public function index(Request $request): View
|
||||||
|
{
|
||||||
|
$rooms = [
|
||||||
|
['name' => '🏠 General', 'description' => 'Our main chat.', 'alias' => 'https://matrix.to/#/#general:hstream.moe'],
|
||||||
|
['name' => '📡 Releases', 'description' => 'Were we @everyone for new releases.', 'alias' => 'https://matrix.to/#/#releases:hstream.moe'],
|
||||||
|
['name' => '👗 NSFW 2D', 'description' => 'Channel for R18 2D Media.', 'alias' => 'https://matrix.to/#/#nsfw:hstream.moe'],
|
||||||
|
['name' => '👗 NSFW IRL', 'description' => 'Channel for R18 IRL Media.', 'alias' => 'https://matrix.to/#/#nsfw-irl:hstream.moe'],
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('matrix.index', [
|
||||||
|
'user' => $request->user(),
|
||||||
|
'rooms' => $rooms,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create matrix user
|
||||||
|
*/
|
||||||
|
public function store(
|
||||||
|
MatrixRegisterRequest $request,
|
||||||
|
MatrixRegistrationService $matrixService
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
$result = $matrixService->registerUser(
|
||||||
|
$request->username,
|
||||||
|
$request->password
|
||||||
|
);
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
$user->matrix_id = $result['user_id'];
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with('success', 'Matrix user created successfully.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return back()
|
||||||
|
->withErrors([
|
||||||
|
'username' => $e->getMessage(),
|
||||||
|
])
|
||||||
|
->withInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,15 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class NotificationController extends Controller
|
class NotificationController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Display the user's notification page.
|
* Display the user's notification page.
|
||||||
*/
|
*/
|
||||||
public function index(Request $request): \Illuminate\View\View
|
public function index(Request $request): View
|
||||||
{
|
{
|
||||||
return view('profile.notifications', [
|
return view('profile.notifications', [
|
||||||
'user' => $request->user(),
|
'user' => $request->user(),
|
||||||
@@ -21,7 +22,7 @@ class NotificationController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Delete Notifcation
|
* Delete Notifcation
|
||||||
*/
|
*/
|
||||||
public function delete(Request $request): \Illuminate\Http\RedirectResponse
|
public function delete(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'id' => 'required|exists:notifications,id',
|
'id' => 'required|exists:notifications,id',
|
||||||
@@ -32,7 +33,7 @@ class NotificationController extends Controller
|
|||||||
->where('id', $request->input('id'))
|
->where('id', $request->input('id'))
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
$notification->forceDelete();
|
$notification->delete();
|
||||||
|
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ use App\Models\Episode;
|
|||||||
use App\Models\Playlist;
|
use App\Models\Playlist;
|
||||||
use App\Models\PlaylistEpisode;
|
use App\Models\PlaylistEpisode;
|
||||||
use App\Services\PlaylistService;
|
use App\Services\PlaylistService;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use RealRashid\SweetAlert\Facades\Alert;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class PlaylistController extends Controller
|
class PlaylistController extends Controller
|
||||||
{
|
{
|
||||||
@@ -21,7 +23,7 @@ class PlaylistController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display the public playlists page.
|
* Display the public playlists page.
|
||||||
*/
|
*/
|
||||||
public function index(): \Illuminate\View\View
|
public function index(): View
|
||||||
{
|
{
|
||||||
return view('playlist.index');
|
return view('playlist.index');
|
||||||
}
|
}
|
||||||
@@ -29,7 +31,7 @@ class PlaylistController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display public playlist.
|
* Display public playlist.
|
||||||
*/
|
*/
|
||||||
public function show($playlist_id): \Illuminate\View\View
|
public function show($playlist_id): View
|
||||||
{
|
{
|
||||||
if (! is_numeric($playlist_id)) {
|
if (! is_numeric($playlist_id)) {
|
||||||
abort(404);
|
abort(404);
|
||||||
@@ -42,14 +44,13 @@ class PlaylistController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the user's playlists page.
|
* Display the user's playlists page.
|
||||||
*/
|
*/
|
||||||
public function playlists(Request $request): \Illuminate\View\View
|
public function playlists(Request $request): View
|
||||||
{
|
{
|
||||||
$title = 'Delete Playlist!';
|
$title = 'Delete Playlist!';
|
||||||
$text = "Are you sure you want to delete?";
|
$text = 'Are you sure you want to delete?';
|
||||||
confirmDelete($title, $text);
|
confirmDelete($title, $text);
|
||||||
|
|
||||||
return view('profile.playlists', [
|
return view('profile.playlists', [
|
||||||
@@ -61,7 +62,7 @@ class PlaylistController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display user's playlist.
|
* Display user's playlist.
|
||||||
*/
|
*/
|
||||||
public function showPlaylist(Request $request, $playlist_id): \Illuminate\View\View
|
public function showPlaylist(Request $request, $playlist_id): View
|
||||||
{
|
{
|
||||||
if (! is_numeric($playlist_id)) {
|
if (! is_numeric($playlist_id)) {
|
||||||
abort(404);
|
abort(404);
|
||||||
@@ -79,13 +80,13 @@ class PlaylistController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Create user playlist (Form).
|
* Create user playlist (Form).
|
||||||
*/
|
*/
|
||||||
public function createPlaylist(Request $request): \Illuminate\Http\RedirectResponse
|
public function createPlaylist(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'name' => 'required|max:30',
|
'name' => 'required|max:30',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$playlist = new Playlist();
|
$playlist = new Playlist;
|
||||||
$playlist->user_id = $request->user()->id;
|
$playlist->user_id = $request->user()->id;
|
||||||
$playlist->name = $request->input('name');
|
$playlist->name = $request->input('name');
|
||||||
$playlist->is_private = $request->input('visiblity') === 'private';
|
$playlist->is_private = $request->input('visiblity') === 'private';
|
||||||
@@ -97,7 +98,7 @@ class PlaylistController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Delete user playlist.
|
* Delete user playlist.
|
||||||
*/
|
*/
|
||||||
public function deletePlaylist(Request $request, $playlist_id): \Illuminate\Http\RedirectResponse
|
public function deletePlaylist(Request $request, $playlist_id): RedirectResponse
|
||||||
{
|
{
|
||||||
if (! is_numeric($playlist_id)) {
|
if (! is_numeric($playlist_id)) {
|
||||||
abort(404);
|
abort(404);
|
||||||
@@ -105,13 +106,11 @@ class PlaylistController extends Controller
|
|||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
$playlist = Playlist::where('user_id', $user->id)->where('id', $playlist_id)->firstOrFail();
|
$playlist = Playlist::where('user_id', $user->id)
|
||||||
|
->where('id', $playlist_id)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
// Delete Playlist Episodes
|
$playlist->delete();
|
||||||
PlaylistEpisode::where('playlist_id', $playlist->id)->forceDelete();
|
|
||||||
|
|
||||||
// Delete Playlist
|
|
||||||
$playlist->forceDelete();
|
|
||||||
|
|
||||||
return to_route('profile.playlists');
|
return to_route('profile.playlists');
|
||||||
}
|
}
|
||||||
@@ -119,7 +118,7 @@ class PlaylistController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Delete episode from playlist.
|
* Delete episode from playlist.
|
||||||
*/
|
*/
|
||||||
public function deleteEpisodeFromPlaylist(Request $request): \Illuminate\Http\JsonResponse
|
public function deleteEpisodeFromPlaylist(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
if (! is_numeric($request->input('playlist')) || ! is_numeric($request->input('episode'))) {
|
if (! is_numeric($request->input('playlist')) || ! is_numeric($request->input('episode'))) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -128,8 +127,14 @@ class PlaylistController extends Controller
|
|||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$playlist = Playlist::where('user_id', $request->user()->id)->where('id', (int) $request->input('playlist'))->firstOrFail();
|
$playlist = Playlist::where('user_id', $request->user()->id)
|
||||||
PlaylistEpisode::where('playlist_id', $playlist->id)->where('episode_id', (int) $request->input('episode'))->forceDelete();
|
->where('id', (int) $request->input('playlist'))
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
PlaylistEpisode::where('playlist_id', $playlist->id)
|
||||||
|
->where('episode_id', (int) $request->input('episode'))
|
||||||
|
->delete();
|
||||||
|
|
||||||
$this->playlistService->reorderPositions($playlist);
|
$this->playlistService->reorderPositions($playlist);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -141,13 +146,13 @@ class PlaylistController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Add to user playlist (API).
|
* Add to user playlist (API).
|
||||||
*/
|
*/
|
||||||
public function addPlaylistApi(Request $request): \Illuminate\Http\JsonResponse
|
public function addPlaylistApi(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'playlist' => 'required|max:30',
|
'playlist' => 'required|max:30',
|
||||||
'episode_id' => 'required'
|
'episode_id' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$playlist = Playlist::where('user_id', $user->id)->where('id', $request->input('playlist'))->firstOrFail();
|
$playlist = Playlist::where('user_id', $user->id)->where('id', $request->input('playlist'))->firstOrFail();
|
||||||
@@ -157,7 +162,7 @@ class PlaylistController extends Controller
|
|||||||
$exists = PlaylistEpisode::where('playlist_id', $playlist->id)->where('episode_id', $episode->id)->exists();
|
$exists = PlaylistEpisode::where('playlist_id', $playlist->id)->where('episode_id', $episode->id)->exists();
|
||||||
if ($exists) {
|
if ($exists) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'already-added'
|
'message' => 'already-added',
|
||||||
], 200);
|
], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,20 +176,20 @@ class PlaylistController extends Controller
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'success'
|
'message' => 'success',
|
||||||
], 200);
|
], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create user playlist (API).
|
* Create user playlist (API).
|
||||||
*/
|
*/
|
||||||
public function createPlaylistApi(Request $request): \Illuminate\Http\JsonResponse
|
public function createPlaylistApi(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'name' => 'required|max:30',
|
'name' => 'required|max:30',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$playlist = new Playlist();
|
$playlist = new Playlist;
|
||||||
$playlist->user_id = $request->user()->id;
|
$playlist->user_id = $request->user()->id;
|
||||||
$playlist->name = $request->input('name');
|
$playlist->name = $request->input('name');
|
||||||
$playlist->is_private = $request->input('visiblity') === 'private';
|
$playlist->is_private = $request->input('visiblity') === 'private';
|
||||||
@@ -192,7 +197,7 @@ class PlaylistController extends Controller
|
|||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'success',
|
'message' => 'success',
|
||||||
'playlist_id' => $playlist->id
|
'playlist_id' => $playlist->id,
|
||||||
], 200);
|
], 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,27 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Episode;
|
|
||||||
use App\Http\Requests\ProfileUpdateRequest;
|
use App\Http\Requests\ProfileUpdateRequest;
|
||||||
|
use App\Models\Episode;
|
||||||
use Illuminate\Http\Request;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Redirect;
|
|
||||||
|
|
||||||
use Conner\Tagging\Model\Tag;
|
use Conner\Tagging\Model\Tag;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Redirect;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
use Intervention\Image\Laravel\Facades\Image;
|
||||||
|
|
||||||
class ProfileController extends Controller
|
class ProfileController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Display the user page.
|
* Display the user page.
|
||||||
*/
|
*/
|
||||||
public function index(Request $request): \Illuminate\View\View
|
public function index(Request $request): View
|
||||||
{
|
{
|
||||||
return view('profile.index', [
|
return view('profile.index', [
|
||||||
'user' => $request->user(),
|
'user' => $request->user(),
|
||||||
@@ -27,7 +32,7 @@ class ProfileController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display the user's settings form.
|
* Display the user's settings form.
|
||||||
*/
|
*/
|
||||||
public function settings(Request $request): \Illuminate\View\View
|
public function settings(Request $request): View
|
||||||
{
|
{
|
||||||
$example = Episode::where('title', 'Succubus Yondara Gibo ga Kita!?')->first();
|
$example = Episode::where('title', 'Succubus Yondara Gibo ga Kita!?')->first();
|
||||||
|
|
||||||
@@ -37,10 +42,33 @@ class ProfileController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the user's profile information.
|
||||||
|
*/
|
||||||
|
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
// Fill everything except the image
|
||||||
|
$user->fill($request->safe()->except('image'));
|
||||||
|
|
||||||
|
if ($request->user()->isDirty('email')) {
|
||||||
|
$request->user()->email_verified_at = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('image')) {
|
||||||
|
$this->storeAvatar($request->file('image'), $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
return Redirect::route('profile.settings')->with('status', 'profile-updated');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the user's watched page.
|
* Display the user's watched page.
|
||||||
*/
|
*/
|
||||||
public function watched(Request $request): \Illuminate\View\View
|
public function watched(Request $request): View
|
||||||
{
|
{
|
||||||
return view('profile.watched', [
|
return view('profile.watched', [
|
||||||
'user' => $request->user(),
|
'user' => $request->user(),
|
||||||
@@ -50,7 +78,7 @@ class ProfileController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display the user's comments page.
|
* Display the user's comments page.
|
||||||
*/
|
*/
|
||||||
public function comments(Request $request): \Illuminate\View\View
|
public function comments(Request $request): View
|
||||||
{
|
{
|
||||||
return view('profile.comments', [
|
return view('profile.comments', [
|
||||||
'user' => $request->user(),
|
'user' => $request->user(),
|
||||||
@@ -60,17 +88,27 @@ class ProfileController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display the user's likes page.
|
* Display the user's likes page.
|
||||||
*/
|
*/
|
||||||
public function likes(Request $request): \Illuminate\View\View
|
public function likes(Request $request): View
|
||||||
{
|
{
|
||||||
return view('profile.likes', [
|
return view('profile.likes', [
|
||||||
'user' => $request->user(),
|
'user' => $request->user(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the user's subscription page.
|
||||||
|
*/
|
||||||
|
public function subscription(Request $request): View
|
||||||
|
{
|
||||||
|
return view('profile.subscription', [
|
||||||
|
'user' => $request->user(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update user settings.
|
* Update user settings.
|
||||||
*/
|
*/
|
||||||
public function saveSettings(Request $request): \Illuminate\Http\RedirectResponse
|
public function saveSettings(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$user->search_design = $request->input('searchDesign') == 'thumbnail';
|
$user->search_design = $request->input('searchDesign') == 'thumbnail';
|
||||||
@@ -84,7 +122,7 @@ class ProfileController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Update user tag blacklist.
|
* Update user tag blacklist.
|
||||||
*/
|
*/
|
||||||
public function saveBlacklist(Request $request): \Illuminate\Http\RedirectResponse
|
public function saveBlacklist(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$tags = json_decode($request->input('tags'));
|
$tags = json_decode($request->input('tags'));
|
||||||
@@ -92,6 +130,7 @@ class ProfileController extends Controller
|
|||||||
if (! $tags) {
|
if (! $tags) {
|
||||||
$user->tag_blacklist = null;
|
$user->tag_blacklist = null;
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
return Redirect::route('profile.settings')->with('status', 'blacklist-updated');
|
return Redirect::route('profile.settings')->with('status', 'blacklist-updated');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,21 +149,61 @@ class ProfileController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Delete the user's account.
|
* Delete the user's account.
|
||||||
*/
|
*/
|
||||||
public function destroy(Request $request): \Illuminate\Http\RedirectResponse
|
public function destroy(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
// Verify password if user has password
|
||||||
|
if (! is_null($user->password)) {
|
||||||
$request->validateWithBag('userDeletion', [
|
$request->validateWithBag('userDeletion', [
|
||||||
'password' => ['required', 'current_password'],
|
'password' => ['required', 'current_password'],
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$user = $request->user();
|
// Update comments to deleted user
|
||||||
|
DB::table('comments')->where('user_id', '=', $user->id)->update(['user_id' => 1]);
|
||||||
|
|
||||||
|
// Delete Profile Picture
|
||||||
|
if ($user->avatar) {
|
||||||
|
Storage::disk('public')->delete($user->avatar);
|
||||||
|
}
|
||||||
|
|
||||||
Auth::logout();
|
Auth::logout();
|
||||||
|
|
||||||
$user->delete();
|
$user->delete();
|
||||||
|
|
||||||
$request->session()->invalidate();
|
$request->session()->invalidate();
|
||||||
|
|
||||||
$request->session()->regenerateToken();
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
|
cache()->flush();
|
||||||
|
|
||||||
return Redirect::to('/');
|
return Redirect::to('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store custom user avatar.
|
||||||
|
*/
|
||||||
|
protected function storeAvatar(UploadedFile $file, User $user): void
|
||||||
|
{
|
||||||
|
// Create Folder for Image Upload
|
||||||
|
if (! Storage::disk('public')->exists('/images/avatars')) {
|
||||||
|
Storage::disk('public')->makeDirectory('/images/avatars');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete old avatar if it exists
|
||||||
|
if ($user->avatar) {
|
||||||
|
Storage::disk('public')->delete($user->avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = "images/avatars/{$user->id}.webp";
|
||||||
|
|
||||||
|
$image = Image::read($file->getRealPath())
|
||||||
|
->cover(128, 128)
|
||||||
|
->toWebp(quality: 85);
|
||||||
|
|
||||||
|
Storage::disk('public')->put($filename, $image);
|
||||||
|
|
||||||
|
$user->avatar = $filename;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,26 +2,25 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Helpers\CacheHelper;
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
use App\Models\Gallery;
|
use App\Models\Gallery;
|
||||||
use App\Models\Hentai;
|
use App\Models\Hentai;
|
||||||
use App\Models\Playlist;
|
use App\Models\Playlist;
|
||||||
use App\Models\PlaylistEpisode;
|
use App\Models\PlaylistEpisode;
|
||||||
use App\Models\Watched;
|
use App\Models\Watched;
|
||||||
use App\Helpers\CacheHelper;
|
use hisorange\BrowserDetect\Facade as Browser;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\View\View;
|
||||||
use hisorange\BrowserDetect\Facade as Browser;
|
|
||||||
|
|
||||||
class StreamController extends Controller
|
class StreamController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Display Stream Page.
|
* Display Stream Page.
|
||||||
*/
|
*/
|
||||||
public function index(Request $request, string $title): \Illuminate\View\View
|
public function index(Request $request, string $title): View
|
||||||
{
|
{
|
||||||
$titleParts = explode('-', $title);
|
$titleParts = explode('-', $title);
|
||||||
if (! is_numeric($titleParts[array_key_last($titleParts)])) {
|
if (! is_numeric($titleParts[array_key_last($titleParts)])) {
|
||||||
@@ -37,7 +36,6 @@ class StreamController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$episode = Episode::where('slug', $title)->firstOrFail();
|
$episode = Episode::where('slug', $title)->firstOrFail();
|
||||||
$gallery = Gallery::where('episode_id', $episode->id)->get();
|
$gallery = Gallery::where('episode_id', $episode->id)->get();
|
||||||
$moreEpisodes = Episode::with(['gallery', 'studio'])->where('hentai_id', $episode->hentai_id)->whereNot('id', $episode->id)->get();
|
$moreEpisodes = Episode::with(['gallery', 'studio'])->where('hentai_id', $episode->hentai_id)->whereNot('id', $episode->id)->get();
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\Playlist;
|
|
||||||
use App\Models\PlaylistEpisode;
|
|
||||||
use App\Models\Watched;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class UserController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Display User Page.
|
|
||||||
*/
|
|
||||||
public function index(string $username): \Illuminate\View\View
|
|
||||||
{
|
|
||||||
$user = User::where('username', $username)
|
|
||||||
->select('id', 'username', 'global_name', 'avatar', 'created_at', 'is_patreon')
|
|
||||||
->firstOrFail();
|
|
||||||
|
|
||||||
return view('user.index', [
|
|
||||||
'user' => $user,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete User.
|
|
||||||
*/
|
|
||||||
public function delete(Request $request): \Illuminate\Http\RedirectResponse
|
|
||||||
{
|
|
||||||
$user = User::where('id', $request->user()->id)->firstOrFail();
|
|
||||||
|
|
||||||
// Delete Playlist
|
|
||||||
$playlists = Playlist::where('user_id', $user->id)->get();
|
|
||||||
foreach($playlists as $playlist) {
|
|
||||||
PlaylistEpisode::where('playlist_id', $playlist->id)->forceDelete();
|
|
||||||
$playlist->forceDelete();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update comments to deleted user
|
|
||||||
DB::table('comments')->where('commenter_id', '=', $user->id)->update(['commenter_id' => 1]);
|
|
||||||
|
|
||||||
$user->forceDelete();
|
|
||||||
|
|
||||||
Auth::guard('web')->logout();
|
|
||||||
|
|
||||||
$request->session()->invalidate();
|
|
||||||
|
|
||||||
$request->session()->regenerateToken();
|
|
||||||
|
|
||||||
cache()->flush();
|
|
||||||
|
|
||||||
return redirect('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+56
-27
@@ -2,7 +2,34 @@
|
|||||||
|
|
||||||
namespace App\Http;
|
namespace App\Http;
|
||||||
|
|
||||||
|
use App\Http\Middleware\Authenticate;
|
||||||
|
use App\Http\Middleware\EncryptCookies;
|
||||||
|
use App\Http\Middleware\IsAdmin;
|
||||||
|
use App\Http\Middleware\IsBanned;
|
||||||
|
use App\Http\Middleware\IsModerator;
|
||||||
|
use App\Http\Middleware\PreventRequestsDuringMaintenance;
|
||||||
|
use App\Http\Middleware\RedirectIfAuthenticated;
|
||||||
|
use App\Http\Middleware\SetLocale;
|
||||||
|
use App\Http\Middleware\TrimStrings;
|
||||||
|
use App\Http\Middleware\TrustProxies;
|
||||||
|
use App\Http\Middleware\ValidateSignature;
|
||||||
|
use App\Http\Middleware\VerifyCsrfToken;
|
||||||
|
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
|
||||||
|
use Illuminate\Auth\Middleware\Authorize;
|
||||||
|
use Illuminate\Auth\Middleware\EnsureEmailIsVerified;
|
||||||
|
use Illuminate\Auth\Middleware\RequirePassword;
|
||||||
|
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
|
||||||
|
use Illuminate\Http\Middleware\HandleCors;
|
||||||
|
use Illuminate\Http\Middleware\SetCacheHeaders;
|
||||||
|
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||||
|
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||||
|
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||||
|
use Illuminate\Session\Middleware\StartSession;
|
||||||
|
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||||
|
|
||||||
class Kernel extends HttpKernel
|
class Kernel extends HttpKernel
|
||||||
{
|
{
|
||||||
@@ -15,12 +42,12 @@ class Kernel extends HttpKernel
|
|||||||
*/
|
*/
|
||||||
protected $middleware = [
|
protected $middleware = [
|
||||||
// \App\Http\Middleware\TrustHosts::class,
|
// \App\Http\Middleware\TrustHosts::class,
|
||||||
\App\Http\Middleware\TrustProxies::class,
|
TrustProxies::class,
|
||||||
\Illuminate\Http\Middleware\HandleCors::class,
|
HandleCors::class,
|
||||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
PreventRequestsDuringMaintenance::class,
|
||||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
ValidatePostSize::class,
|
||||||
\App\Http\Middleware\TrimStrings::class,
|
TrimStrings::class,
|
||||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
ConvertEmptyStringsToNull::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,19 +57,20 @@ class Kernel extends HttpKernel
|
|||||||
*/
|
*/
|
||||||
protected $middlewareGroups = [
|
protected $middlewareGroups = [
|
||||||
'web' => [
|
'web' => [
|
||||||
\App\Http\Middleware\EncryptCookies::class,
|
EncryptCookies::class,
|
||||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
AddQueuedCookiesToResponse::class,
|
||||||
\Illuminate\Session\Middleware\StartSession::class,
|
StartSession::class,
|
||||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
ShareErrorsFromSession::class,
|
||||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
VerifyCsrfToken::class,
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
SubstituteBindings::class,
|
||||||
\App\Http\Middleware\IsBanned::class,
|
IsBanned::class,
|
||||||
|
SetLocale::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'api' => [
|
'api' => [
|
||||||
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||||
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
|
ThrottleRequests::class.':api',
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
SubstituteBindings::class,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -54,17 +82,18 @@ class Kernel extends HttpKernel
|
|||||||
* @var array<string, class-string|string>
|
* @var array<string, class-string|string>
|
||||||
*/
|
*/
|
||||||
protected $middlewareAliases = [
|
protected $middlewareAliases = [
|
||||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
'auth' => Authenticate::class,
|
||||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
'auth.basic' => AuthenticateWithBasicAuth::class,
|
||||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
'auth.session' => AuthenticateSession::class,
|
||||||
'auth.admin' => \App\Http\Middleware\IsAdmin::class,
|
'auth.admin' => IsAdmin::class,
|
||||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
'auth.moderator' => IsModerator::class,
|
||||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
'cache.headers' => SetCacheHeaders::class,
|
||||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
'can' => Authorize::class,
|
||||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
'guest' => RedirectIfAuthenticated::class,
|
||||||
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
|
'password.confirm' => RequirePassword::class,
|
||||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
'precognitive' => HandlePrecognitiveRequests::class,
|
||||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
'signed' => ValidateSignature::class,
|
||||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
'throttle' => ThrottleRequests::class,
|
||||||
|
'verified' => EnsureEmailIsVerified::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,28 @@
|
|||||||
<?php namespace app\Http\Middleware;
|
<?php
|
||||||
|
|
||||||
|
namespace app\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Contracts\Auth\Guard;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
class IsAdmin {
|
class IsAdmin
|
||||||
|
|
||||||
/**
|
|
||||||
* The Guard implementation.
|
|
||||||
*
|
|
||||||
* @var Guard
|
|
||||||
*/
|
|
||||||
protected $auth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new filter instance.
|
|
||||||
*
|
|
||||||
* @param Guard $auth
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct(Guard $auth)
|
|
||||||
{
|
{
|
||||||
$this->auth = $auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure $next
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function handle($request, Closure $next)
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
if( ! $this->auth->user()->is_admin)
|
if (Auth::check() && Auth::user()->hasRole(UserRole::ADMINISTRATOR)) {
|
||||||
{
|
|
||||||
session()->flash('error_msg','This resource is restricted to Administrators!');
|
|
||||||
return redirect()->route('home.index');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session()->flash('error_msg', 'This resource is restricted to Administrators!');
|
||||||
|
|
||||||
|
return redirect()->route('home.index');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,30 @@
|
|||||||
<?php namespace app\Http\Middleware;
|
<?php
|
||||||
|
|
||||||
|
namespace app\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
use Closure;
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Contracts\Auth\Guard;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
class IsBanned {
|
|
||||||
|
|
||||||
|
class IsBanned
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure $next
|
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function handle($request, Closure $next)
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
|
||||||
if(auth()->check() && auth()->user()->is_banned == 1)
|
|
||||||
{
|
{
|
||||||
|
if (Auth::check() && Auth::user()->hasRole(UserRole::BANNED)) {
|
||||||
Auth::logout();
|
Auth::logout();
|
||||||
$request->session()->invalidate();
|
$request->session()->invalidate();
|
||||||
$request->session()->regenerateToken();
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
return redirect()->route('home.banned');
|
return redirect()->route('home.banned');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class IsModerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param Closure(Request): (Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
if (Auth::check() && (
|
||||||
|
Auth::user()->hasRole(UserRole::MODERATOR) ||
|
||||||
|
Auth::user()->hasRole(UserRole::ADMINISTRATOR))) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
session()->flash('error_msg', 'This resource is restricted to Moderators!');
|
||||||
|
|
||||||
|
return redirect()->route('home.index');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ class RedirectIfAuthenticated
|
|||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
* @param Closure(Request): (Response) $next
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next, string ...$guards): Response
|
public function handle(Request $request, Closure $next, string ...$guards): Response
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class SetLocale
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param Closure(Request): (Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
// 1. Logged-in user preference
|
||||||
|
if (Auth::check() && Auth::user()->locale) {
|
||||||
|
App::setLocale(Auth::user()->locale);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Session (guest or user override)
|
||||||
|
if ($request->session()->has('locale') &&
|
||||||
|
in_array(session('locale'), config('app.supported_locales'), true)) {
|
||||||
|
|
||||||
|
App::setLocale(session('locale'));
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Browser language
|
||||||
|
$locale = $request->getPreferredLanguage(config('app.supported_locales'));
|
||||||
|
|
||||||
|
if ($locale) {
|
||||||
|
App::setLocale($locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests\Auth;
|
namespace App\Http\Requests\Auth;
|
||||||
|
|
||||||
|
use App\Rules\ValidCaptcha;
|
||||||
use Illuminate\Auth\Events\Lockout;
|
use Illuminate\Auth\Events\Lockout;
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\RateLimiter;
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
@@ -22,20 +24,21 @@ class LoginRequest extends FormRequest
|
|||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'email' => ['required', 'string', 'email'],
|
'email' => ['required', 'string', 'email'],
|
||||||
'password' => ['required', 'string'],
|
'password' => ['required', 'string'],
|
||||||
|
'altcha' => ['required', new ValidCaptcha],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to authenticate the request's credentials.
|
* Attempt to authenticate the request's credentials.
|
||||||
*
|
*
|
||||||
* @throws \Illuminate\Validation\ValidationException
|
* @throws ValidationException
|
||||||
*/
|
*/
|
||||||
public function authenticate(): void
|
public function authenticate(): void
|
||||||
{
|
{
|
||||||
@@ -55,7 +58,7 @@ class LoginRequest extends FormRequest
|
|||||||
/**
|
/**
|
||||||
* Ensure the login request is not rate limited.
|
* Ensure the login request is not rate limited.
|
||||||
*
|
*
|
||||||
* @throws \Illuminate\Validation\ValidationException
|
* @throws ValidationException
|
||||||
*/
|
*/
|
||||||
public function ensureIsNotRateLimited(): void
|
public function ensureIsNotRateLimited(): void
|
||||||
{
|
{
|
||||||
@@ -80,6 +83,6 @@ class LoginRequest extends FormRequest
|
|||||||
*/
|
*/
|
||||||
public function throttleKey(): string
|
public function throttleKey(): string
|
||||||
{
|
{
|
||||||
return Str::transliterate(Str::lower($this->input('email')).'|'.$this->ip());
|
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class MatrixRegisterRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
$isOldEnough = $this->user()->created_at->lt(now()->subMonth());
|
||||||
|
$noAccount = ! $this->user()->matrix_id;
|
||||||
|
|
||||||
|
return $isOldEnough && $noAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'username' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'min:3',
|
||||||
|
'max:32',
|
||||||
|
'regex:/^[a-z0-9._=-]+$/', // Valid Matrix localpart
|
||||||
|
],
|
||||||
|
'password' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'min:8',
|
||||||
|
'confirmed',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'username.regex' => 'Username may only contain lowercase letters, numbers and . _ = -',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
@@ -11,13 +12,26 @@ class ProfileUpdateRequest extends FormRequest
|
|||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to the request.
|
* Get the validation rules that apply to the request.
|
||||||
*
|
*
|
||||||
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
|
* @return array<string, ValidationRule|array<mixed>|string>
|
||||||
*/
|
*/
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => ['string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'email' => ['email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
|
'image' => [
|
||||||
|
'nullable',
|
||||||
|
'image',
|
||||||
|
'mimes:jpg,png,jpeg,webp,gif',
|
||||||
|
'max:8192',
|
||||||
|
],
|
||||||
|
'email' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'lowercase',
|
||||||
|
'email',
|
||||||
|
'max:255',
|
||||||
|
Rule::unique(User::class)->ignore($this->user()->id),
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
use Spatie\DiscordAlerts\Facades\DiscordAlert;
|
use Spatie\DiscordAlerts\Facades\DiscordAlert;
|
||||||
|
|
||||||
class DiscordReleaseNotification implements ShouldQueue
|
class DiscordReleaseNotification implements ShouldQueue
|
||||||
@@ -32,26 +31,25 @@ class DiscordReleaseNotification implements ShouldQueue
|
|||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
switch($this->messageType)
|
switch ($this->messageType) {
|
||||||
{
|
|
||||||
case 'release':
|
case 'release':
|
||||||
DiscordAlert::message("<@&868457842250764289> (´• ω •`)ノ New **4k** Release! Check it out here: https://hstream.moe/hentai/".$this->slug);
|
DiscordAlert::message('<@&868457842250764289> (´• ω •`)ノ New **4k** Release! Check it out here: https://hstream.moe/hentai/'.$this->slug);
|
||||||
break;
|
break;
|
||||||
case 'release-censored':
|
case 'release-censored':
|
||||||
# Because Discord TOS
|
// Because Discord TOS
|
||||||
DiscordAlert::message("<@&868457842250764289> (´• ω •`)ノ New **4k** Release: ".$this->slug." - *No link here because of* :pLoli:");
|
DiscordAlert::message('<@&868457842250764289> (´• ω •`)ノ New **4k** Release: '.$this->slug.' - *No link here because of* :pLoli:');
|
||||||
break;
|
break;
|
||||||
case 'update':
|
case 'update':
|
||||||
# 1080p 48fps added
|
// 1080p 48fps added
|
||||||
DiscordAlert::to('update')->message("<@&1283518462584426598> (´• ω •`)ノ Added **48fps** to Release! Check it out here: https://hstream.moe/hentai/".$this->slug);
|
DiscordAlert::to('update')->message('<@&1283518462584426598> (´• ω •`)ノ Added **48fps** to Release! Check it out here: https://hstream.moe/hentai/'.$this->slug);
|
||||||
break;
|
break;
|
||||||
case 'updateUHD':
|
case 'updateUHD':
|
||||||
# 4k 48fps added
|
// 4k 48fps added
|
||||||
DiscordAlert::to('update')->message("<@&1326860920902778963> (´• ω •`)ノ Added **48fps 4k** to Release! Check it out here: https://hstream.moe/hentai/".$this->slug);
|
DiscordAlert::to('update')->message('<@&1326860920902778963> (´• ω •`)ノ Added **48fps 4k** to Release! Check it out here: https://hstream.moe/hentai/'.$this->slug);
|
||||||
break;
|
break;
|
||||||
case 'v2':
|
case 'v2':
|
||||||
# v2 re-release
|
// v2 re-release
|
||||||
DiscordAlert::to('rerelease')->message("<@&1425505303075754035> (´• ω •`)ノ **v2 Re-**Release! Check it out here: https://hstream.moe/hentai/".$this->slug);
|
DiscordAlert::to('rerelease')->message('<@&1425505303075754035> (´• ω •`)ノ **v2 Re-**Release! Check it out here: https://hstream.moe/hentai/'.$this->slug);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ use Illuminate\Bus\Queueable;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Http\Client\RequestException;
|
use Illuminate\Http\Client\RequestException;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Crypt;
|
use Illuminate\Support\Facades\Crypt;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class GetFileSizeFromCDN implements ShouldQueue
|
class GetFileSizeFromCDN implements ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -37,6 +37,7 @@ class GetFileSizeFromCDN implements ShouldQueue
|
|||||||
$download = Downloads::find($this->downloadId);
|
$download = Downloads::find($this->downloadId);
|
||||||
if (! $download) {
|
if (! $download) {
|
||||||
Log::error("Download not found for ID: {$this->downloadId}");
|
Log::error("Download not found for ID: {$this->downloadId}");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Comment;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
class AdminCommentSearch extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
|
||||||
|
public $search = '';
|
||||||
|
|
||||||
|
public $userSearch = '';
|
||||||
|
|
||||||
|
public function updatingSearch(): void
|
||||||
|
{
|
||||||
|
$this->resetPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatingUserSearch(): void
|
||||||
|
{
|
||||||
|
$this->resetPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteComment($commentId)
|
||||||
|
{
|
||||||
|
$comment = Comment::where('id', (int) $commentId)->firstOrFail();
|
||||||
|
$comment->delete();
|
||||||
|
|
||||||
|
cache()->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$comments = Comment::when($this->search !== '', fn ($query) => $query->where('body', 'LIKE', "%$this->search%"))
|
||||||
|
->when($this->userSearch !== '', fn ($query) => $query->whereHas('user', fn ($query) => $query->where('name', 'LIKE', "%{$this->userSearch}%")))
|
||||||
|
->orderBy('created_at', 'DESC')
|
||||||
|
->paginate(12);
|
||||||
|
|
||||||
|
return view('livewire.admin-comment-search', [
|
||||||
|
'comments' => $comments,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\Comment;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Livewire\Attributes\Url;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithPagination;
|
use Livewire\WithPagination;
|
||||||
use Livewire\Attributes\Url;
|
|
||||||
|
|
||||||
class AdminUserSearch extends Component
|
class AdminUserSearch extends Component
|
||||||
{
|
{
|
||||||
@@ -16,7 +17,7 @@ class AdminUserSearch extends Component
|
|||||||
public $search = '';
|
public $search = '';
|
||||||
|
|
||||||
#[Url(history: true)]
|
#[Url(history: true)]
|
||||||
public $filtered = ['true'];
|
public $discordId = '';
|
||||||
|
|
||||||
#[Url(history: true)]
|
#[Url(history: true)]
|
||||||
public $patreon = [];
|
public $patreon = [];
|
||||||
@@ -24,19 +25,27 @@ class AdminUserSearch extends Component
|
|||||||
#[Url(history: true)]
|
#[Url(history: true)]
|
||||||
public $banned = [];
|
public $banned = [];
|
||||||
|
|
||||||
|
public function deleteUserComments(int $userID)
|
||||||
|
{
|
||||||
|
$user = User::where('id', $userID)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
Comment::where('user_id', $user->id)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
cache()->flush();
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
$users = User::when($this->filtered !== [], fn ($query) => $query->where('id', '>=', 10000))
|
$users = User::when($this->patreon !== [], fn ($query) => $query->whereJsonContains('roles', UserRole::SUPPORTER->value))
|
||||||
->when($this->patreon !== [], fn ($query) => $query->where('is_patreon', 1))
|
->when($this->banned !== [], fn ($query) => $query->whereJsonContains('roles', UserRole::BANNED->value))
|
||||||
->when($this->banned !== [], fn ($query) => $query->where('is_banned', 1))
|
->when($this->search !== '', fn ($query) => $query->where('name', 'like', '%'.$this->search.'%'))
|
||||||
->when($this->search !== '', fn ($query) => $query->where(function($query) {
|
->when($this->discordId !== '', fn ($query) => $query->where('discord_id', '=', $this->discordId))
|
||||||
$query->where('username', 'like', '%'.$this->search.'%')
|
|
||||||
->orWhere('global_name', 'like', '%'.$this->search.'%');
|
|
||||||
}))
|
|
||||||
->paginate(20);
|
->paginate(20);
|
||||||
|
|
||||||
return view('livewire.admin-user-search', [
|
return view('livewire.admin-user-search', [
|
||||||
'users' => $users
|
'users' => $users,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use Livewire\Component;
|
|
||||||
use Livewire\WithPagination;
|
|
||||||
use Livewire\Attributes\Url;
|
|
||||||
|
|
||||||
use App\Models\SiteBackground;
|
use App\Models\SiteBackground;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Livewire\Attributes\Url;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
class BackgroundImages extends Component
|
class BackgroundImages extends Component
|
||||||
{
|
{
|
||||||
@@ -20,16 +19,14 @@ class BackgroundImages extends Component
|
|||||||
{
|
{
|
||||||
$now = Carbon::now();
|
$now = Carbon::now();
|
||||||
|
|
||||||
$images = SiteBackground::when($this->filter === 'active', fn ($query) =>
|
$images = SiteBackground::when($this->filter === 'active', fn ($query) => $query->whereDate('date_start', '<=', $now)->whereDate('date_end', '>=', $now)
|
||||||
$query->whereDate('date_start', '<=', $now)->whereDate('date_end', '>=', $now)
|
|
||||||
)
|
)
|
||||||
->when($this->filter === 'inactive', fn ($query) =>
|
->when($this->filter === 'inactive', fn ($query) => $query->whereDate('date_start', '>', $now)->orWhereDate('date_end', '<', $now)
|
||||||
$query->whereDate('date_start', '>', $now)->orWhereDate('date_end', '<', $now)
|
|
||||||
)
|
)
|
||||||
->paginate(10);
|
->paginate(10);
|
||||||
|
|
||||||
return view('livewire.background-images', [
|
return view('livewire.background-images', [
|
||||||
'images' => $images
|
'images' => $images,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\Episode;
|
||||||
|
use App\Models\ModLog;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Notifications\CommentNotification;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Maize\Markable\Models\Like;
|
||||||
|
|
||||||
|
class Comment extends Component
|
||||||
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
|
||||||
|
public $comment;
|
||||||
|
|
||||||
|
public $isReplying = false;
|
||||||
|
|
||||||
|
public $likeCount = 0;
|
||||||
|
|
||||||
|
public $liked = false;
|
||||||
|
|
||||||
|
public $replyState = [
|
||||||
|
'body' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
public $isEditing = false;
|
||||||
|
|
||||||
|
public $editState = [
|
||||||
|
'body' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $listeners = [
|
||||||
|
'refresh' => '$refresh',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'replyState.body' => 'reply',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function updatedIsEditing(bool $isEditing)
|
||||||
|
{
|
||||||
|
if (! $isEditing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->editState = [
|
||||||
|
'body' => $this->comment->body,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function editComment()
|
||||||
|
{
|
||||||
|
$this->authorize('update', $this->comment);
|
||||||
|
|
||||||
|
$this->comment->update($this->editState);
|
||||||
|
|
||||||
|
$this->isEditing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteComment()
|
||||||
|
{
|
||||||
|
$this->authorize('destroy', $this->comment);
|
||||||
|
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
if ($user->hasRole(UserRole::ADMINISTRATOR) || $user->hasRole(UserRole::MODERATOR)) {
|
||||||
|
// Log to ModLog
|
||||||
|
ModLog::create([
|
||||||
|
'moderator' => $user->name,
|
||||||
|
'data' => "Deleted comment {$this->comment->id} written by {$this->comment->user->id} with contents: {$this->comment->body}",
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->comment->deleted_by_moderator_id = $user->id;
|
||||||
|
$this->comment->save();
|
||||||
|
$this->dispatch('refresh');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->comment->delete();
|
||||||
|
|
||||||
|
$this->dispatch('refresh');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restoreComment()
|
||||||
|
{
|
||||||
|
$this->authorize('restore', $this->comment);
|
||||||
|
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
if ($user->hasRole(UserRole::ADMINISTRATOR) || $user->hasRole(UserRole::MODERATOR)) {
|
||||||
|
// Log to ModLog
|
||||||
|
ModLog::create([
|
||||||
|
'moderator' => $user->name,
|
||||||
|
'data' => "Restored comment {$this->comment->id} written by {$this->comment->user->id} with contents: {$this->comment->body}",
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->comment->deleted_by_moderator_id = null;
|
||||||
|
$this->comment->save();
|
||||||
|
$this->dispatch('refresh');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postReply()
|
||||||
|
{
|
||||||
|
if (! ($this->comment->depth() < 2)) {
|
||||||
|
$this->addError('replyState.body', 'Too many sub comments.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = auth()->user();
|
||||||
|
$rateLimitKey = "send-comment:{$user->id}";
|
||||||
|
$rateLimitMinutes = 60 * 5; // 5 minutes
|
||||||
|
|
||||||
|
if (RateLimiter::tooManyAttempts($rateLimitKey, 1)) {
|
||||||
|
$seconds = RateLimiter::availableIn($rateLimitKey);
|
||||||
|
|
||||||
|
$this->addError('replyState.body', "Too many comments. Try again in {$seconds} seconds.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RateLimiter::hit($rateLimitKey, $rateLimitMinutes);
|
||||||
|
|
||||||
|
$this->validate([
|
||||||
|
'replyState.body' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$reply = $this->comment->children()->make($this->replyState);
|
||||||
|
$reply->user()->associate($user);
|
||||||
|
$reply->commentable()->associate($this->comment->commentable);
|
||||||
|
|
||||||
|
$reply->save();
|
||||||
|
|
||||||
|
// Notify if Episode and if not the same user
|
||||||
|
if ($reply->commentable_type == Episode::class && $user->id !== $reply->parent->user->id) {
|
||||||
|
$episode = Episode::where('id', $reply->commentable_id)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
$url = route('hentai.index', ['title' => $episode->slug]);
|
||||||
|
|
||||||
|
$reply->parent->user->notify(
|
||||||
|
new CommentNotification(
|
||||||
|
"{$user->name} replied to your comment.",
|
||||||
|
Str::limit($reply->body, 50),
|
||||||
|
"{$url}#comment-{$reply->id}"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->replyState = [
|
||||||
|
'body' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->isReplying = false;
|
||||||
|
|
||||||
|
$this->dispatch('refresh')->self();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function like()
|
||||||
|
{
|
||||||
|
if (! Auth::check()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Like::toggle($this->comment, User::where('id', Auth::user()->id)->firstOrFail());
|
||||||
|
|
||||||
|
Cache::forget('commentLikes'.$this->comment->id);
|
||||||
|
|
||||||
|
if ($this->liked) {
|
||||||
|
$this->liked = false;
|
||||||
|
$this->likeCount--;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->liked = true;
|
||||||
|
$this->likeCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
if (Auth::check()) {
|
||||||
|
$this->likeCount = $this->comment->likeCount();
|
||||||
|
$this->liked = Like::has($this->comment, User::where('id', Auth::user()->id)->firstOrFail());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.comment');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
class Comments extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
|
||||||
|
public $model;
|
||||||
|
|
||||||
|
public $newCommentState = [
|
||||||
|
'body' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'newCommentState.body' => 'comment',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $listeners = [
|
||||||
|
'refresh' => '$refresh',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function postComment()
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'newCommentState.body' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = auth()->user();
|
||||||
|
$rateLimitKey = "send-comment:{$user->id}";
|
||||||
|
$rateLimitMinutes = 60 * 5; // 5 minutes
|
||||||
|
|
||||||
|
if (RateLimiter::tooManyAttempts($rateLimitKey, 1)) {
|
||||||
|
$seconds = RateLimiter::availableIn($rateLimitKey);
|
||||||
|
|
||||||
|
$this->addError('newCommentState.body', "Too many comments. Try again in {$seconds} seconds.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RateLimiter::hit($rateLimitKey, $rateLimitMinutes);
|
||||||
|
|
||||||
|
$comment = $this->model->comments()->make($this->newCommentState);
|
||||||
|
$comment->user()->associate($user);
|
||||||
|
$comment->save();
|
||||||
|
|
||||||
|
$this->newCommentState = [
|
||||||
|
'body' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->resetPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$comments = $this->model
|
||||||
|
->comments()
|
||||||
|
->with('user', 'children.user', 'children.children')
|
||||||
|
->parent()
|
||||||
|
->latest()
|
||||||
|
->paginate(50);
|
||||||
|
|
||||||
|
return view('livewire.comments', [
|
||||||
|
'comments' => $comments,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,25 @@ class DownloadButton extends Component
|
|||||||
|
|
||||||
public $background = 'bg-rose-600';
|
public $background = 'bg-rose-600';
|
||||||
|
|
||||||
|
public $fileExtension = 'HEVC';
|
||||||
|
|
||||||
|
public $version = '';
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
if (str_contains($this->downloadUrl, 'AV1')) {
|
||||||
|
$this->fileExtension = 'AV1';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($this->downloadUrl, 'v2')) {
|
||||||
|
$this->version = 'v2';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_contains($this->downloadUrl, 'v3')) {
|
||||||
|
$this->version = 'v3';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function clicked($downloadId)
|
public function clicked($downloadId)
|
||||||
{
|
{
|
||||||
$download = Downloads::find($downloadId);
|
$download = Downloads::find($downloadId);
|
||||||
|
|||||||
@@ -5,12 +5,9 @@ namespace App\Livewire;
|
|||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\UserDownload;
|
use App\Models\UserDownload;
|
||||||
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Livewire\Component;
|
||||||
|
|
||||||
class DownloadsFree extends Component
|
class DownloadsFree extends Component
|
||||||
{
|
{
|
||||||
@@ -51,7 +48,8 @@ class DownloadsFree extends Component
|
|||||||
// Check timestamp
|
// Check timestamp
|
||||||
if (Carbon::parse($alreadyDownloaded->created_at)->addHours(6) <= Carbon::now()) {
|
if (Carbon::parse($alreadyDownloaded->created_at)->addHours(6) <= Carbon::now()) {
|
||||||
// Already expired
|
// Already expired
|
||||||
$alreadyDownloaded->forceDelete();
|
$alreadyDownloaded->delete();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +64,7 @@ class DownloadsFree extends Component
|
|||||||
if ($user->downloads_left <= 0) {
|
if ($user->downloads_left <= 0) {
|
||||||
// Daily limit reached
|
// Daily limit reached
|
||||||
$this->granted = 3;
|
$this->granted = 3;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
use App\Models\Downloads;
|
use App\Models\Downloads;
|
||||||
|
use Livewire\Attributes\Url;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithPagination;
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
@@ -10,10 +12,36 @@ class DownloadsSearch extends Component
|
|||||||
{
|
{
|
||||||
use WithPagination;
|
use WithPagination;
|
||||||
|
|
||||||
|
#[Url(history: true)]
|
||||||
public $fileSearch;
|
public $fileSearch;
|
||||||
|
|
||||||
public $order = 'created_at_desc';
|
public $order = 'created_at_desc';
|
||||||
|
|
||||||
|
public $options = [
|
||||||
|
'FHD' => true,
|
||||||
|
'FHD 48fps' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
public $isOpen = false;
|
||||||
|
|
||||||
|
#[Url(history: true)]
|
||||||
|
public $studios = [];
|
||||||
|
|
||||||
|
public $studiosCopy = [];
|
||||||
|
|
||||||
|
// To toggle individual option selection
|
||||||
|
public function toggleOption($option)
|
||||||
|
{
|
||||||
|
$this->options[$option] = ! $this->options[$option];
|
||||||
|
$this->resetPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// To toggle dropdown visibility
|
||||||
|
public function toggleDropdown()
|
||||||
|
{
|
||||||
|
$this->isOpen = ! $this->isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
protected $queryString = [
|
protected $queryString = [
|
||||||
'fileSearch' => ['except' => '', 'as' => 'fS'],
|
'fileSearch' => ['except' => '', 'as' => 'fS'],
|
||||||
'order' => ['except' => '', 'as' => 'order'],
|
'order' => ['except' => '', 'as' => 'order'],
|
||||||
@@ -24,6 +52,40 @@ class DownloadsSearch extends Component
|
|||||||
$this->resetPage();
|
$this->resetPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function applyFilters(): void
|
||||||
|
{
|
||||||
|
$this->studiosCopy = $this->studios;
|
||||||
|
$this->resetPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function revertFilters(): void
|
||||||
|
{
|
||||||
|
$this->studios = $this->studiosCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map the selected options to database types
|
||||||
|
private function getSelectedTypes()
|
||||||
|
{
|
||||||
|
$types = [];
|
||||||
|
|
||||||
|
// Map the options to their corresponding database values
|
||||||
|
foreach ($this->options as $label => $selected) {
|
||||||
|
if ($selected) {
|
||||||
|
if ($label === 'FHD') {
|
||||||
|
$types[] = 'FHD';
|
||||||
|
} elseif ($label === 'FHD 48fps') {
|
||||||
|
$types[] = 'FHDi';
|
||||||
|
} elseif ($label === 'UHD' && auth()->user()->hasRole(UserRole::SUPPORTER)) {
|
||||||
|
$types[] = 'UHD';
|
||||||
|
} elseif ($label === 'UHD 48fps' && auth()->user()->hasRole(UserRole::SUPPORTER)) {
|
||||||
|
$types[] = 'UHDi';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $types;
|
||||||
|
}
|
||||||
|
|
||||||
public function clicked($downloadId)
|
public function clicked($downloadId)
|
||||||
{
|
{
|
||||||
$download = Downloads::find($downloadId);
|
$download = Downloads::find($downloadId);
|
||||||
@@ -36,6 +98,17 @@ class DownloadsSearch extends Component
|
|||||||
cache()->forget("episode_{$download->episode->id}_download_{$download->type}");
|
cache()->forget("episode_{$download->episode->id}_download_{$download->type}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
if (! auth()->user()->hasRole(UserRole::SUPPORTER)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add patreon options
|
||||||
|
$this->options['UHD'] = true;
|
||||||
|
$this->options['UHD 48fps'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
$orderby = 'created_at';
|
$orderby = 'created_at';
|
||||||
@@ -72,7 +145,10 @@ class DownloadsSearch extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
$downloads = Downloads::when($this->fileSearch != '', fn ($query) => $query->where('url', 'like', '%'.$this->fileSearch.'%'))
|
$downloads = Downloads::when($this->fileSearch != '', fn ($query) => $query->where('url', 'like', '%'.$this->fileSearch.'%'))
|
||||||
->when(!auth()->user()->is_patreon, fn ($query) => $query->whereIn('type', ['FHD', 'FHDi']))
|
->whereIn('type', $this->getSelectedTypes())
|
||||||
|
->when($this->studios !== [], fn ($q) => $q->whereHas('episode', fn ($query) => $query->whereHas('studio', function ($query) {
|
||||||
|
$query->whereIn('slug', $this->studios);
|
||||||
|
})))
|
||||||
->whereNotNull('size')
|
->whereNotNull('size')
|
||||||
->orderBy($orderby, $orderdirection)
|
->orderBy($orderby, $orderdirection)
|
||||||
->paginate(20);
|
->paginate(20);
|
||||||
@@ -80,6 +156,7 @@ class DownloadsSearch extends Component
|
|||||||
return view('livewire.downloads-search', [
|
return view('livewire.downloads-search', [
|
||||||
'downloads' => $downloads,
|
'downloads' => $downloads,
|
||||||
'query' => $this->fileSearch,
|
'query' => $this->fileSearch,
|
||||||
|
'studiocount' => is_array($this->studios) ? count($this->studios) : 0,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ namespace App\Livewire;
|
|||||||
|
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Livewire\Component;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Livewire\Component;
|
||||||
use Maize\Markable\Models\Like;
|
use Maize\Markable\Models\Like;
|
||||||
|
|
||||||
class LikeButton extends Component
|
class LikeButton extends Component
|
||||||
@@ -52,6 +51,7 @@ class LikeButton extends Component
|
|||||||
if ($this->liked) {
|
if ($this->liked) {
|
||||||
$this->liked = false;
|
$this->liked = false;
|
||||||
$this->likeCount--;
|
$this->likeCount--;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Livewire\Attributes\Url;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithPagination;
|
use Livewire\WithPagination;
|
||||||
use Livewire\Attributes\Url;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
|
|
||||||
class LiveSearch extends Component
|
class LiveSearch extends Component
|
||||||
{
|
{
|
||||||
@@ -20,14 +20,17 @@ class LiveSearch extends Component
|
|||||||
|
|
||||||
#[Url(history: true)]
|
#[Url(history: true)]
|
||||||
public $tags = [];
|
public $tags = [];
|
||||||
|
|
||||||
public $tagsCopy = [];
|
public $tagsCopy = [];
|
||||||
|
|
||||||
#[Url(history: true)]
|
#[Url(history: true)]
|
||||||
public $studios = [];
|
public $studios = [];
|
||||||
|
|
||||||
public $studiosCopy = [];
|
public $studiosCopy = [];
|
||||||
|
|
||||||
#[Url(history: true)]
|
#[Url(history: true)]
|
||||||
public $blacklist = [];
|
public $blacklist = [];
|
||||||
|
|
||||||
public $blacklistCopy = [];
|
public $blacklistCopy = [];
|
||||||
|
|
||||||
#[Url(history: true)]
|
#[Url(history: true)]
|
||||||
@@ -118,10 +121,14 @@ class LiveSearch extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
$user_id = Auth::check() ? auth()->user()->id : 0;
|
$user_id = Auth::check() ? auth()->user()->id : 0;
|
||||||
$episodes = Episode::with('gallery')->when($this->search != '', fn ($query) => $query->where(function($query) { $query->where('title', 'like', '%'.$this->search.'%')->orWhere('title_search', 'like', '%'.$this->search.'%')->orWhere('title_jpn', 'like', '%'.$this->search.'%'); }))
|
$episodes = Episode::with('gallery')->when($this->search != '', fn ($query) => $query->where(function ($query) {
|
||||||
|
$query->where('title', 'like', '%'.$this->search.'%')->orWhere('title_search', 'like', '%'.$this->search.'%')->orWhere('title_jpn', 'like', '%'.$this->search.'%');
|
||||||
|
}))
|
||||||
->when($this->tags !== [], fn ($query) => $query->withAllTags($this->tags))
|
->when($this->tags !== [], fn ($query) => $query->withAllTags($this->tags))
|
||||||
->when($this->blacklist !== [], fn ($query) => $query->withoutTags($this->blacklist))
|
->when($this->blacklist !== [], fn ($query) => $query->withoutTags($this->blacklist))
|
||||||
->when($this->studios !== [], fn ($query) => $query->whereHas('studio', function ($query) { $query->whereIn('slug', $this->studios); }))
|
->when($this->studios !== [], fn ($query) => $query->whereHas('studio', function ($query) {
|
||||||
|
$query->whereIn('slug', $this->studios);
|
||||||
|
}))
|
||||||
->when($this->hideWatched !== [] && Auth::check(), fn ($query) => $query->whereDoesntHave('watched', function ($query) use ($user_id) {
|
->when($this->hideWatched !== [] && Auth::check(), fn ($query) => $query->whereDoesntHave('watched', function ($query) use ($user_id) {
|
||||||
$query->where('user_id', $user_id);
|
$query->where('user_id', $user_id);
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -3,9 +3,8 @@
|
|||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
use App\Models\Gallery;
|
|
||||||
use Livewire\Component;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
class NavLiveSearch extends Component
|
class NavLiveSearch extends Component
|
||||||
{
|
{
|
||||||
@@ -18,22 +17,15 @@ class NavLiveSearch extends Component
|
|||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
$episodes = [];
|
$episodes = [];
|
||||||
$randomimage = null;
|
|
||||||
if ($this->navSearch != '') {
|
if ($this->navSearch != '') {
|
||||||
$episodes = Episode::with('gallery')->where('title', 'like', '%'.$this->navSearch.'%')
|
$episodes = Episode::search($this->navSearch)
|
||||||
->orWhere('title_jpn', 'like', '%'.$this->navSearch.'%')
|
->when(Auth::guest(), fn ($query) => $query->whereNotIn('tags', ['Loli', 'Shota']))
|
||||||
->when(Auth::guest(), fn ($query) => $query->withoutTags(['loli', 'shota']))
|
->take(7)
|
||||||
->take(10)
|
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
$randomimage = Gallery::all()
|
|
||||||
->random(1)
|
|
||||||
->first();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('livewire.nav-live-search', [
|
return view('livewire.nav-live-search', [
|
||||||
'episodes' => $episodes,
|
'episodes' => $episodes,
|
||||||
'randomimage' => $randomimage,
|
|
||||||
'query' => $this->navSearch,
|
'query' => $this->navSearch,
|
||||||
'hide' => empty($this->navSearch),
|
'hide' => empty($this->navSearch),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -5,13 +5,11 @@ namespace App\Livewire;
|
|||||||
use App\Models\Playlist;
|
use App\Models\Playlist;
|
||||||
use App\Models\PlaylistEpisode;
|
use App\Models\PlaylistEpisode;
|
||||||
use App\Services\PlaylistService;
|
use App\Services\PlaylistService;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Livewire\Attributes\Url;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithPagination;
|
use Livewire\WithPagination;
|
||||||
use Livewire\Attributes\Url;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
|
||||||
|
|
||||||
class PlaylistOverview extends Component
|
class PlaylistOverview extends Component
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,10 +3,9 @@
|
|||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use App\Models\Playlist;
|
use App\Models\Playlist;
|
||||||
|
use Livewire\Attributes\Url;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithPagination;
|
use Livewire\WithPagination;
|
||||||
use Livewire\Attributes\Url;
|
|
||||||
|
|
||||||
class Playlists extends Component
|
class Playlists extends Component
|
||||||
{
|
{
|
||||||
@@ -59,7 +58,7 @@ class Playlists extends Component
|
|||||||
->paginate($this->pagination);
|
->paginate($this->pagination);
|
||||||
|
|
||||||
return view('livewire.playlists', [
|
return view('livewire.playlists', [
|
||||||
'playlists' => $playlists
|
'playlists' => $playlists,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Comment;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
class UserComments extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
|
||||||
|
public $model;
|
||||||
|
|
||||||
|
public $commentSearch;
|
||||||
|
|
||||||
|
public $order = 'created_at_desc';
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$orderby = 'created_at';
|
||||||
|
$orderdirection = 'desc';
|
||||||
|
|
||||||
|
switch ($this->order) {
|
||||||
|
case 'created_at_desc':
|
||||||
|
$orderby = 'created_at';
|
||||||
|
$orderdirection = 'desc';
|
||||||
|
break;
|
||||||
|
case 'created_at_asc':
|
||||||
|
$orderby = 'created_at';
|
||||||
|
$orderdirection = 'asc';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$orderby = 'created_at';
|
||||||
|
$orderdirection = 'desc';
|
||||||
|
}
|
||||||
|
|
||||||
|
$comments = Comment::where('user_id', $this->model->id)
|
||||||
|
->when($this->commentSearch != '', fn ($query) => $query->where('body', 'like', '%'.$this->commentSearch.'%'))
|
||||||
|
->orderBy($orderby, $orderdirection)
|
||||||
|
->paginate(6);
|
||||||
|
|
||||||
|
return view('livewire.user-comments', [
|
||||||
|
'comments' => $comments,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Livewire\Attributes\Url;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithPagination;
|
use Livewire\WithPagination;
|
||||||
use Livewire\Attributes\Url;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
|
|
||||||
class UserLikes extends Component
|
class UserLikes extends Component
|
||||||
{
|
{
|
||||||
@@ -20,14 +20,17 @@ class UserLikes extends Component
|
|||||||
|
|
||||||
#[Url(history: true)]
|
#[Url(history: true)]
|
||||||
public $tags = [];
|
public $tags = [];
|
||||||
|
|
||||||
public $tagsCopy = [];
|
public $tagsCopy = [];
|
||||||
|
|
||||||
#[Url(history: true)]
|
#[Url(history: true)]
|
||||||
public $studios = [];
|
public $studios = [];
|
||||||
|
|
||||||
public $studiosCopy = [];
|
public $studiosCopy = [];
|
||||||
|
|
||||||
#[Url(history: true)]
|
#[Url(history: true)]
|
||||||
public $blacklist = [];
|
public $blacklist = [];
|
||||||
|
|
||||||
public $blacklistCopy = [];
|
public $blacklistCopy = [];
|
||||||
|
|
||||||
#[Url(history: true)]
|
#[Url(history: true)]
|
||||||
@@ -119,10 +122,14 @@ class UserLikes extends Component
|
|||||||
|
|
||||||
$user_id = Auth::check() ? auth()->user()->id : 0;
|
$user_id = Auth::check() ? auth()->user()->id : 0;
|
||||||
$episodes = Episode::whereHasLike(auth()->user())
|
$episodes = Episode::whereHasLike(auth()->user())
|
||||||
->when($this->search !== '', fn ($query) => $query->where(function($query) { $query->where('title', 'like', '%'.$this->search.'%')->orWhere('title_search', 'like', '%'.$this->search.'%')->orWhere('title_jpn', 'like', '%'.$this->search.'%'); }))
|
->when($this->search !== '', fn ($query) => $query->where(function ($query) {
|
||||||
|
$query->where('title', 'like', '%'.$this->search.'%')->orWhere('title_search', 'like', '%'.$this->search.'%')->orWhere('title_jpn', 'like', '%'.$this->search.'%');
|
||||||
|
}))
|
||||||
->when($this->tags !== [], fn ($query) => $query->withAllTags($this->tags))
|
->when($this->tags !== [], fn ($query) => $query->withAllTags($this->tags))
|
||||||
->when($this->blacklist !== [], fn ($query) => $query->withoutTags($this->blacklist))
|
->when($this->blacklist !== [], fn ($query) => $query->withoutTags($this->blacklist))
|
||||||
->when($this->studios !== [], fn ($query) => $query->whereHas('studio', function ($query) { $query->whereIn('slug', $this->studios); }))
|
->when($this->studios !== [], fn ($query) => $query->whereHas('studio', function ($query) {
|
||||||
|
$query->whereIn('slug', $this->studios);
|
||||||
|
}))
|
||||||
->when($this->hideWatched !== [] && Auth::check(), fn ($query) => $query->whereDoesntHave('watched', function ($query) use ($user_id) {
|
->when($this->hideWatched !== [] && Auth::check(), fn ($query) => $query->whereDoesntHave('watched', function ($query) use ($user_id) {
|
||||||
$query->where('user_id', $user_id);
|
$query->where('user_id', $user_id);
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\SubscriptionService;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\Attributes\Computed;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
|
||||||
|
class UserSubscription extends Component
|
||||||
|
{
|
||||||
|
public $userId = 0;
|
||||||
|
|
||||||
|
public $subscriptionKey = '';
|
||||||
|
|
||||||
|
public $isActive = false;
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'subscriptionKey' => 'required|string|size:48',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount(User $user)
|
||||||
|
{
|
||||||
|
$this->userId = $user ? $user->id : auth()->user()->id;
|
||||||
|
$this->subscriptionKey = $user->subscription_key ?? '';
|
||||||
|
$this->isActive = $user->hasRole(UserRole::SUPPORTER) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyKey(SubscriptionService $subscriptionService)
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
$rateLimitKey = "apply-subscription:{$this->userId}";
|
||||||
|
$rateLimitMinutes = 60 * 5; // 5 minutes
|
||||||
|
|
||||||
|
// Rate Limit to prevent users trying random keys
|
||||||
|
if (RateLimiter::tooManyAttempts($rateLimitKey, 1)) {
|
||||||
|
$seconds = RateLimiter::availableIn($rateLimitKey);
|
||||||
|
$this->addError('subscriptionKey', "Too many attempts. Try again in {$seconds} seconds.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RateLimiter::hit($rateLimitKey, $rateLimitMinutes);
|
||||||
|
|
||||||
|
// Check if token is already being used
|
||||||
|
$alreadyUsed = User::where('subscription_key', $this->subscriptionKey)
|
||||||
|
->whereNot('id', $this->userId)
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($alreadyUsed) {
|
||||||
|
$this->addError('subscriptionKey', 'Key already used!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = User::where('id', $this->userId)->firstOrFail();
|
||||||
|
|
||||||
|
// Verify token
|
||||||
|
$success = $subscriptionService->checkSubscriptionStatus($user, $this->subscriptionKey);
|
||||||
|
if (!$success) {
|
||||||
|
$this->addError('subscriptionKey', 'Invalid Key! If you believe this is a bug, please report this to the admin!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->subscription_key = $this->subscriptionKey;
|
||||||
|
$user->save();
|
||||||
|
$this->isActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.user-subscription');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire;
|
|
||||||
|
|
||||||
use App\Models\Episode;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class ViewCount extends Component
|
|
||||||
{
|
|
||||||
public $episodeId = 0;
|
|
||||||
|
|
||||||
public $viewCount = 0;
|
|
||||||
|
|
||||||
public function mount(Episode $episode)
|
|
||||||
{
|
|
||||||
$this->episodeId = $episode->id;
|
|
||||||
$this->viewCount = $episode->view_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update()
|
|
||||||
{
|
|
||||||
$this->viewCount = Episode::where('id', $this->episodeId)->firstOrFail()->view_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return view('livewire.view-count');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use App\Models\Watched as UserWatched;
|
use App\Models\Watched as UserWatched;
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithPagination;
|
use Livewire\WithPagination;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\Presenters\CommentPresenter;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Maize\Markable\Markable;
|
||||||
|
use Maize\Markable\Models\Like;
|
||||||
|
|
||||||
|
class Comment extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, Markable, SoftDeletes;
|
||||||
|
|
||||||
|
protected static $marks = [
|
||||||
|
Like::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'body',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function presenter()
|
||||||
|
{
|
||||||
|
return new CommentPresenter($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeParent(Builder $builder)
|
||||||
|
{
|
||||||
|
$builder->whereNull('parent_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function children()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Comment::class, 'parent_id')->oldest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function commentable()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parent()
|
||||||
|
{
|
||||||
|
return $this->hasOne(Comment::class, 'id', 'parent_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursevly calculates how deep the nesting is
|
||||||
|
public function depth(): int
|
||||||
|
{
|
||||||
|
return $this->parent
|
||||||
|
? $this->parent->depth() + 1
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cached like count
|
||||||
|
*/
|
||||||
|
public function likeCount(): int
|
||||||
|
{
|
||||||
|
return cache()->remember('commentLikes'.$this->id, 300, fn () => $this->likes->count());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns wether or not comment has been removed by moderation
|
||||||
|
*/
|
||||||
|
public function isDeletedByModerator(): bool
|
||||||
|
{
|
||||||
|
return $this->deleted_by_moderator_id !== null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
-20
@@ -2,35 +2,57 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Models\Downloads;
|
|
||||||
use App\Models\PopularMonthly;
|
|
||||||
use App\Models\PopularWeekly;
|
|
||||||
use App\Models\PopularDaily;
|
|
||||||
|
|
||||||
use Conner\Tagging\Taggable;
|
use Conner\Tagging\Taggable;
|
||||||
use Laravelista\Comments\Commentable;
|
|
||||||
use Maize\Markable\Markable;
|
|
||||||
use Maize\Markable\Models\Like;
|
|
||||||
|
|
||||||
use Spatie\Sitemap\Contracts\Sitemapable;
|
|
||||||
use Spatie\Sitemap\Tags\Url;
|
|
||||||
|
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Laravel\Scout\Searchable;
|
||||||
|
use Maize\Markable\Markable;
|
||||||
|
use Maize\Markable\Models\Like;
|
||||||
|
use Spatie\Sitemap\Contracts\Sitemapable;
|
||||||
|
use Spatie\Sitemap\Tags\Url;
|
||||||
|
|
||||||
class Episode extends Model implements Sitemapable
|
class Episode extends Model implements Sitemapable
|
||||||
{
|
{
|
||||||
use Commentable, Markable, Taggable;
|
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
use Markable, Taggable;
|
||||||
|
use Searchable;
|
||||||
|
|
||||||
protected static $marks = [
|
protected static $marks = [
|
||||||
Like::class
|
Like::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the index associated with the model.
|
||||||
|
*/
|
||||||
|
public function searchableAs(): string
|
||||||
|
{
|
||||||
|
return 'episodes_index';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the indexable data array for the model.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toSearchableArray()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'title' => $this->title,
|
||||||
|
'title_search' => $this->title_search,
|
||||||
|
'title_jpn' => $this->title_jpn,
|
||||||
|
'slug' => $this->slug,
|
||||||
|
'description' => $this->description,
|
||||||
|
'tags' => $this->tagNames(),
|
||||||
|
'release_date' => $this->release_date,
|
||||||
|
'created_at' => $this->created_at,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the studio for the Hentai.
|
* Get the studio for the Hentai.
|
||||||
*/
|
*/
|
||||||
@@ -74,10 +96,11 @@ class Episode extends Model implements Sitemapable
|
|||||||
/**
|
/**
|
||||||
* Increment View Count.
|
* Increment View Count.
|
||||||
*/
|
*/
|
||||||
public function incrementViewCount(): bool
|
public function incrementViewCount(): void
|
||||||
{
|
{
|
||||||
$this->view_count++;
|
DB::table('episodes')
|
||||||
return $this->save();
|
->where('id', $this->id)
|
||||||
|
->update(['view_count' => $this->view_count + 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,6 +153,11 @@ class Episode extends Model implements Sitemapable
|
|||||||
return cache()->remember('episodeComments'.$this->id, 300, fn () => $this->comments->count());
|
return cache()->remember('episodeComments'.$this->id, 300, fn () => $this->comments->count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function comments()
|
||||||
|
{
|
||||||
|
return $this->morphMany(Comment::class, 'commentable');
|
||||||
|
}
|
||||||
|
|
||||||
public function getProblematicTags(): string
|
public function getProblematicTags(): string
|
||||||
{
|
{
|
||||||
$problematicTags = ['Gore', 'Scat', 'Horror'];
|
$problematicTags = ['Gore', 'Scat', 'Horror'];
|
||||||
@@ -145,6 +173,7 @@ class Episode extends Model implements Sitemapable
|
|||||||
|
|
||||||
$problematicResults .= $pTag;
|
$problematicResults .= $pTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $problematicResults;
|
return $problematicResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,9 +228,10 @@ class Episode extends Model implements Sitemapable
|
|||||||
return $this->hasMany(Watched::class);
|
return $this->hasMany(Watched::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDownloadByType(string $type): Downloads | null
|
public function getDownloadByType(string $type): ?Downloads
|
||||||
{
|
{
|
||||||
$cacheKey = "episode_{$this->id}_download_{$type}";
|
$cacheKey = "episode_{$this->id}_download_{$type}";
|
||||||
|
|
||||||
return Cache::remember($cacheKey, now()->addMinutes(10), function () use ($type) {
|
return Cache::remember($cacheKey, now()->addMinutes(10), function () use ($type) {
|
||||||
return $this->downloads()->where('type', $type)->first();
|
return $this->downloads()->where('type', $type)->first();
|
||||||
});
|
});
|
||||||
|
|||||||
+12
-9
@@ -2,20 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Conner\Tagging\Taggable;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Spatie\Sitemap\Contracts\Sitemapable;
|
use Spatie\Sitemap\Contracts\Sitemapable;
|
||||||
use Spatie\Sitemap\Tags\Url;
|
use Spatie\Sitemap\Tags\Url;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Conner\Tagging\Taggable;
|
|
||||||
use Laravelista\Comments\Commentable;
|
|
||||||
|
|
||||||
class Hentai extends Model implements Sitemapable
|
class Hentai extends Model implements Sitemapable
|
||||||
{
|
{
|
||||||
use Commentable, Taggable;
|
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
use Taggable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
@@ -32,11 +30,16 @@ class Hentai extends Model implements Sitemapable
|
|||||||
return $this->hasMany(Episode::class, 'hentai_id');
|
return $this->hasMany(Episode::class, 'hentai_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function title(): String
|
public function title(): string
|
||||||
{
|
{
|
||||||
return $this->episodes->first()->title;
|
return $this->episodes->first()->title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function comments()
|
||||||
|
{
|
||||||
|
return $this->morphMany(Comment::class, 'commentable');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Has a Gallery.
|
* Has a Gallery.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ModLog extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'moderator',
|
||||||
|
'data',
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
class Playlist extends Model
|
class Playlist extends Model
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
class PlaylistEpisode extends Model
|
class PlaylistEpisode extends Model
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Presenters;
|
||||||
|
|
||||||
|
use App\Models\Comment;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class CommentPresenter
|
||||||
|
{
|
||||||
|
public $comment;
|
||||||
|
|
||||||
|
public function __construct(Comment $comment)
|
||||||
|
{
|
||||||
|
$this->comment = $comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markdownBody()
|
||||||
|
{
|
||||||
|
return Str::of($this->comment->body)->markdown([
|
||||||
|
'html_input' => 'strip',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relativeCreatedAt()
|
||||||
|
{
|
||||||
|
return $this->comment->created_at->diffForHumans();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,13 +16,13 @@ class SiteBackground extends Model
|
|||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'date_start',
|
'date_start',
|
||||||
'date_end',
|
'date_end',
|
||||||
'default'
|
'default',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current IDs of active wallpaper
|
* Returns the current IDs of active wallpaper
|
||||||
*/
|
*/
|
||||||
public function getImages(): ? \Illuminate\Support\Collection
|
public function getImages(): ?Collection
|
||||||
{
|
{
|
||||||
$now = Carbon::now();
|
$now = Carbon::now();
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
class Studios extends Model
|
class Studios extends Model
|
||||||
{
|
{
|
||||||
|
|||||||
+95
-36
@@ -2,20 +2,21 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|
||||||
use Jakyeru\Larascord\Traits\InteractsWithDiscord;
|
|
||||||
use Laravelista\Comments\Commenter;
|
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Spatie\LaravelPasskeys\Models\Concerns\HasPasskeys;
|
||||||
|
use Spatie\LaravelPasskeys\Models\Concerns\InteractsWithPasskeys;
|
||||||
|
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable implements HasPasskeys
|
||||||
{
|
{
|
||||||
use HasApiTokens, HasFactory, Notifiable, InteractsWithDiscord, Commenter;
|
use HasFactory, InteractsWithPasskeys, Notifiable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
@@ -23,22 +24,14 @@ class User extends Authenticatable
|
|||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'id',
|
'name',
|
||||||
'username',
|
|
||||||
'global_name',
|
|
||||||
'discriminator',
|
|
||||||
'email',
|
'email',
|
||||||
'avatar',
|
'password',
|
||||||
'verified',
|
|
||||||
'banner',
|
|
||||||
'banner_color',
|
|
||||||
'accent_color',
|
|
||||||
'locale',
|
'locale',
|
||||||
'mfa_enabled',
|
// Discord
|
||||||
'premium_type',
|
'discord_id',
|
||||||
'public_flags',
|
'discord_avatar',
|
||||||
'roles',
|
'subscription_key',
|
||||||
'is_banned',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,7 +40,9 @@ class User extends Authenticatable
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
|
'password',
|
||||||
'remember_token',
|
'remember_token',
|
||||||
|
'subscription_key',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,22 +51,18 @@ class User extends Authenticatable
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'id' => 'integer',
|
// Laravel defaults
|
||||||
'username' => 'string',
|
'email_verified_at' => 'datetime',
|
||||||
'global_name' => 'string',
|
'password' => 'hashed',
|
||||||
'discriminator' => 'string',
|
// Other
|
||||||
|
'name' => 'string',
|
||||||
'email' => 'string',
|
'email' => 'string',
|
||||||
'avatar' => 'string',
|
|
||||||
'verified' => 'boolean',
|
|
||||||
'banner' => 'string',
|
|
||||||
'banner_color' => 'string',
|
|
||||||
'accent_color' => 'string',
|
|
||||||
'locale' => 'string',
|
'locale' => 'string',
|
||||||
'mfa_enabled' => 'boolean',
|
'roles' => 'array',
|
||||||
'premium_type' => 'integer',
|
|
||||||
'public_flags' => 'integer',
|
|
||||||
'roles' => 'json',
|
|
||||||
'tag_blacklist' => 'array',
|
'tag_blacklist' => 'array',
|
||||||
|
// Discord
|
||||||
|
'discord_id' => 'integer',
|
||||||
|
'discord_avatar' => 'string',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,8 +92,76 @@ class User extends Authenticatable
|
|||||||
/**
|
/**
|
||||||
* Has Many Comments.
|
* Has Many Comments.
|
||||||
*/
|
*/
|
||||||
|
public function comments()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Comment::class, 'user_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Comment Count.
|
||||||
|
*/
|
||||||
public function commentCount(): int
|
public function commentCount(): int
|
||||||
{
|
{
|
||||||
return DB::table('comments')->where('commenter_id', $this->id)->count();
|
return cache()->remember('userComments'.$this->id, 300, fn () => $this->comments->count());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user avatar image url.
|
||||||
|
*/
|
||||||
|
public function getAvatar(): string
|
||||||
|
{
|
||||||
|
if ($this->discord_id && $this->discord_avatar && ! $this->avatar) {
|
||||||
|
return "https://external-content.duckduckgo.com/iu/?u={$this->discord_avatar}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->avatar) {
|
||||||
|
return Storage::url($this->avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
return asset('images/default-avatar.webp');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user has a specific role
|
||||||
|
*/
|
||||||
|
public function hasRole(UserRole $role): bool
|
||||||
|
{
|
||||||
|
return in_array($role->value, $this->roles ?? [], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Role to User
|
||||||
|
*/
|
||||||
|
public function addRole(UserRole $role): void
|
||||||
|
{
|
||||||
|
if ($this->hasRole($role)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all current roles
|
||||||
|
$roles = $this->roles ?? [];
|
||||||
|
|
||||||
|
// Add new role
|
||||||
|
$roles[] = $role->value;
|
||||||
|
|
||||||
|
$this->roles = $roles;
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove Role from User
|
||||||
|
*/
|
||||||
|
public function removeRole(UserRole $role): void
|
||||||
|
{
|
||||||
|
if (! $this->hasRole($role)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->roles = collect($this->roles)
|
||||||
|
->reject(fn ($value) => $value === $role->value)
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
$this->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
class UserDownload extends Model
|
class UserDownload extends Model
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
class Watched extends Model
|
class Watched extends Model
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
namespace App\Notifications;
|
namespace App\Notifications;
|
||||||
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
|
|
||||||
class CommentNotification extends Notification
|
class CommentNotification extends Notification
|
||||||
@@ -12,7 +10,9 @@ class CommentNotification extends Notification
|
|||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
protected $type;
|
protected $type;
|
||||||
|
|
||||||
protected $message;
|
protected $message;
|
||||||
|
|
||||||
protected $url;
|
protected $url;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Laravelista\Comments;
|
|
||||||
|
|
||||||
use Laravelista\Comments\Comment;
|
|
||||||
|
|
||||||
class CommentPolicy
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Can user create the comment
|
|
||||||
*
|
|
||||||
* @param $user
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function create($user) : bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can user delete the comment
|
|
||||||
*
|
|
||||||
* @param $user
|
|
||||||
* @param Comment $comment
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function delete($user, Comment $comment) : bool
|
|
||||||
{
|
|
||||||
return ($user->getKey() == $comment->commenter_id) || $user->is_admin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can user update the comment
|
|
||||||
*
|
|
||||||
* @param $user
|
|
||||||
* @param Comment $comment
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function update($user, Comment $comment) : bool
|
|
||||||
{
|
|
||||||
return $user->getKey() == $comment->commenter_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can user reply to the comment
|
|
||||||
*
|
|
||||||
* @param $user
|
|
||||||
* @param Comment $comment
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function reply($user, Comment $comment) : bool
|
|
||||||
{
|
|
||||||
return $user->getKey() != $comment->commenter_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Laravelista\Comments;
|
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Illuminate\Support\Facades\Config;
|
|
||||||
use Illuminate\Support\Facades\Gate;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
|
|
||||||
use App\Notifications\CommentNotification;
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\Episode;
|
|
||||||
|
|
||||||
class CommentService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Handles creating a new comment for given model.
|
|
||||||
* @return mixed the configured comment-model
|
|
||||||
*/
|
|
||||||
public function store(Request $request)
|
|
||||||
{
|
|
||||||
// If guest commenting is turned off, authorize this action.
|
|
||||||
if (Config::get('comments.guest_commenting') == false) {
|
|
||||||
Gate::authorize('create-comment', Comment::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define guest rules if user is not logged in.
|
|
||||||
if (!Auth::check()) {
|
|
||||||
$guest_rules = [
|
|
||||||
'guest_name' => 'required|string|max:255',
|
|
||||||
'guest_email' => 'required|string|email|max:255',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge guest rules, if any, with normal validation rules.
|
|
||||||
Validator::make($request->all(), array_merge($guest_rules ?? [], [
|
|
||||||
'commentable_type' => 'required|string',
|
|
||||||
'commentable_id' => 'required|string|min:1',
|
|
||||||
'message' => 'required|string'
|
|
||||||
]))->validate();
|
|
||||||
|
|
||||||
$model = $request->commentable_type::findOrFail($request->commentable_id);
|
|
||||||
|
|
||||||
$commentClass = Config::get('comments.model');
|
|
||||||
$comment = new $commentClass;
|
|
||||||
|
|
||||||
if (!Auth::check()) {
|
|
||||||
$comment->guest_name = $request->guest_name;
|
|
||||||
$comment->guest_email = $request->guest_email;
|
|
||||||
} else {
|
|
||||||
$comment->commenter()->associate(Auth::user());
|
|
||||||
}
|
|
||||||
|
|
||||||
$comment->commentable()->associate($model);
|
|
||||||
$comment->comment = $request->message;
|
|
||||||
$comment->approved = !Config::get('comments.approval_required');
|
|
||||||
$comment->save();
|
|
||||||
|
|
||||||
return $comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles updating the message of the comment.
|
|
||||||
* @return mixed the configured comment-model
|
|
||||||
*/
|
|
||||||
public function update(Request $request, Comment $comment)
|
|
||||||
{
|
|
||||||
Gate::authorize('edit-comment', $comment);
|
|
||||||
|
|
||||||
Validator::make($request->all(), [
|
|
||||||
'message' => 'required|string'
|
|
||||||
])->validate();
|
|
||||||
|
|
||||||
$comment->update([
|
|
||||||
'comment' => $request->message
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles deleting a comment.
|
|
||||||
* @return mixed the configured comment-model
|
|
||||||
*/
|
|
||||||
public function destroy(Comment $comment): void
|
|
||||||
{
|
|
||||||
Gate::authorize('delete-comment', $comment);
|
|
||||||
|
|
||||||
if (Config::get('comments.soft_deletes') == true) {
|
|
||||||
$comment->delete();
|
|
||||||
} else {
|
|
||||||
$comment->forceDelete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles creating a reply "comment" to a comment.
|
|
||||||
* @return mixed the configured comment-model
|
|
||||||
*/
|
|
||||||
public function reply(Request $request, Comment $comment)
|
|
||||||
{
|
|
||||||
Gate::authorize('reply-to-comment', $comment);
|
|
||||||
|
|
||||||
Validator::make($request->all(), [
|
|
||||||
'message' => 'required|string'
|
|
||||||
])->validate();
|
|
||||||
|
|
||||||
$commentClass = Config::get('comments.model');
|
|
||||||
$reply = new $commentClass;
|
|
||||||
$reply->commenter()->associate(Auth::user());
|
|
||||||
$reply->commentable()->associate($comment->commentable);
|
|
||||||
$reply->parent()->associate($comment);
|
|
||||||
$reply->comment = $request->message;
|
|
||||||
$reply->approved = !Config::get('comments.approval_required');
|
|
||||||
$reply->save();
|
|
||||||
|
|
||||||
// Notify
|
|
||||||
if ($comment->commentable_type == 'App\Models\Episode') {
|
|
||||||
$episode = Episode::where('id', $comment->commentable_id)->firstOrFail();
|
|
||||||
$url = '/hentai/' . $episode->slug . '#comment-' . $reply->id;
|
|
||||||
|
|
||||||
$user = Auth::user();
|
|
||||||
$username = $user->global_name ?? $user->username;
|
|
||||||
|
|
||||||
$parentCommentUser = User::where('id', $comment->commenter_id)->firstOrFail();
|
|
||||||
$parentCommentUser->notify(
|
|
||||||
new CommentNotification(
|
|
||||||
"{$username} replied to your comment.",
|
|
||||||
Str::limit($reply->comment, 50),
|
|
||||||
$url
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $reply;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Jakyeru\Larascord\Http\Controllers;
|
|
||||||
|
|
||||||
use Illuminate\Http\JsonResponse;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use App\Providers\RouteServiceProvider;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
use Jakyeru\Larascord\Http\Requests\StoreUserRequest;
|
|
||||||
use Jakyeru\Larascord\Services\DiscordService;
|
|
||||||
|
|
||||||
use RealRashid\SweetAlert\Facades\Alert;
|
|
||||||
|
|
||||||
class DiscordController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Handles the Discord OAuth2 login.
|
|
||||||
*/
|
|
||||||
public function handle(StoreUserRequest $request): RedirectResponse | JsonResponse
|
|
||||||
{
|
|
||||||
// Making sure the "guilds" scope was added to .env if there are any guilds specified in "larascord.guilds".
|
|
||||||
if (count(config('larascord.guilds'))) {
|
|
||||||
if (!in_array('guilds', explode('&', config('larascord.scopes')))) {
|
|
||||||
return $this->throwError('missing_guilds_scope');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getting the accessToken from the Discord API.
|
|
||||||
try {
|
|
||||||
$accessToken = (new DiscordService())->getAccessTokenFromCode($request->get('code'));
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->throwError('invalid_code', $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the user from the Discord API.
|
|
||||||
try {
|
|
||||||
$user = (new DiscordService())->getCurrentUser($accessToken);
|
|
||||||
$user->setAccessToken($accessToken);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->throwError('authorization_failed', $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Making sure the user has an email if the email scope is set.
|
|
||||||
if (in_array('email', explode('&', config('larascord.scopes')))) {
|
|
||||||
if (empty($user->email)) {
|
|
||||||
return $this->throwError('missing_email');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth()->check()) {
|
|
||||||
// Making sure the current logged-in user's ID is matching the ID retrieved from the Discord API.
|
|
||||||
if (auth()->id() !== (int)$user->id) {
|
|
||||||
auth()->logout();
|
|
||||||
return $this->throwError('invalid_user');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirming the session in case the user was redirected from the password.confirm middleware.
|
|
||||||
$request->session()->put('auth.password_confirmed_at', time());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trying to create or update the user in the database.
|
|
||||||
// Initiating a database transaction in case something goes wrong.
|
|
||||||
DB::beginTransaction();
|
|
||||||
try {
|
|
||||||
$user = (new DiscordService())->createOrUpdateUser($user);
|
|
||||||
$user->accessToken()->updateOrCreate([], $accessToken->toArray());
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
DB::rollBack();
|
|
||||||
return $this->throwError('database_error', $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verifying if the user is soft-deleted.
|
|
||||||
if (Schema::hasColumn('users', 'deleted_at')) {
|
|
||||||
if ($user->trashed()) {
|
|
||||||
DB::rollBack();
|
|
||||||
return $this->throwError('user_deleted');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Patreon check
|
|
||||||
try {
|
|
||||||
if (!$accessToken->hasScopes(['guilds', 'guilds.members.read'])) {
|
|
||||||
DB::rollBack();
|
|
||||||
return $this->throwError('missing_guilds_members_read_scope');
|
|
||||||
}
|
|
||||||
$guildMember = (new DiscordService())->getGuildMember($accessToken, config('discord.guild_id'));
|
|
||||||
$patreonroles = config('discord.patreon_roles');
|
|
||||||
$user->is_patreon = false;
|
|
||||||
if ((new DiscordService())->hasRoleInGuild($guildMember, $patreonroles)) {
|
|
||||||
$user->is_patreon = true;
|
|
||||||
}
|
|
||||||
$user->save();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
// Clearly not a patreon
|
|
||||||
$user->is_patreon = false;
|
|
||||||
$user->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Committing the database transaction.
|
|
||||||
DB::commit();
|
|
||||||
|
|
||||||
// Authenticating the user if the user is not logged in.
|
|
||||||
if (!auth()->check()) {
|
|
||||||
auth()->login($user, config('larascord.remember_me', false));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirecting the user to the intended page or to the home page.
|
|
||||||
return redirect()->intended(RouteServiceProvider::HOME);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the throwing of an error.
|
|
||||||
*/
|
|
||||||
private function throwError(string $message, \Exception $exception = NULL): RedirectResponse | JsonResponse
|
|
||||||
{
|
|
||||||
if (app()->hasDebugModeEnabled()) {
|
|
||||||
return response()->json([
|
|
||||||
'larascord_message' => config('larascord.error_messages.' . $message),
|
|
||||||
'message' => $exception?->getMessage(),
|
|
||||||
'code' => $exception?->getCode()
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
if (config('larascord.error_messages.' . $message . '.redirect')) {
|
|
||||||
Alert::error('Error', config('larascord.error_messages.' . $message . '.message', 'An error occurred while trying to log you in.'));
|
|
||||||
return redirect(config('larascord.error_messages.' . $message . '.redirect'))->with('error', config('larascord.error_messages.' . $message . '.message', 'An error occurred while trying to log you in.'));
|
|
||||||
} else {
|
|
||||||
return redirect('/')->with('error', config('larascord.error_messages.' . $message, 'An error occurred while trying to log you in.'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the deletion of the user.
|
|
||||||
*/
|
|
||||||
public function destroy(): RedirectResponse | JsonResponse
|
|
||||||
{
|
|
||||||
// Revoking the OAuth2 access token.
|
|
||||||
try {
|
|
||||||
(new DiscordService())->revokeAccessToken(auth()->user()->accessToken()->first()->refresh_token);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->throwError('revoke_token_failed', $e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deleting the user from the database.
|
|
||||||
auth()->user()->delete();
|
|
||||||
|
|
||||||
// Showing the success message.
|
|
||||||
if (config('larascord.success_messages.user_deleted.redirect')) {
|
|
||||||
return redirect(config('larascord.success_messages.user_deleted.redirect'))->with('success', config('larascord.success_messages.user_deleted.message', 'Your account has been deleted.'));
|
|
||||||
} else {
|
|
||||||
return redirect('/')->with('success', config('larascord.success_messages.user_deleted', 'Your account has been deleted.'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Jakyeru\Larascord\Services;
|
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\OldUser;
|
|
||||||
use App\Models\Playlist;
|
|
||||||
use App\Models\PlaylistEpisode;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Illuminate\Http\Client\RequestException;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Jakyeru\Larascord\Types\AccessToken;
|
|
||||||
use Jakyeru\Larascord\Types\GuildMember;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class DiscordService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The Discord OAuth2 token URL.
|
|
||||||
*/
|
|
||||||
protected string $tokenURL = "https://discord.com/api/oauth2/token";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Discord API base URL.
|
|
||||||
*/
|
|
||||||
protected string $baseApi = "https://discord.com/api";
|
|
||||||
/**
|
|
||||||
* The required data for the token request.
|
|
||||||
*/
|
|
||||||
protected array $tokenData = [
|
|
||||||
"client_id" => NULL,
|
|
||||||
"client_secret" => NULL,
|
|
||||||
"grant_type" => "authorization_code",
|
|
||||||
"code" => NULL,
|
|
||||||
"redirect_uri" => NULL,
|
|
||||||
"scope" => null
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UserService constructor.
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->tokenData['client_id'] = config('larascord.client_id');
|
|
||||||
$this->tokenData['client_secret'] = config('larascord.client_secret');
|
|
||||||
$this->tokenData['grant_type'] = config('larascord.grant_type');
|
|
||||||
$this->tokenData['redirect_uri'] = config('larascord.redirect_uri');
|
|
||||||
$this->tokenData['scope'] = config('larascord.scopes');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the Discord OAuth2 callback and returns the access token.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
*/
|
|
||||||
public function getAccessTokenFromCode(string $code): AccessToken
|
|
||||||
{
|
|
||||||
$this->tokenData['code'] = $code;
|
|
||||||
|
|
||||||
$response = Http::asForm()->post($this->tokenURL, $this->tokenData);
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return new AccessToken(json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get access token from refresh token.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
*/
|
|
||||||
public function refreshAccessToken(string $refreshToken): AccessToken
|
|
||||||
{
|
|
||||||
$response = Http::asForm()->post($this->tokenURL, [
|
|
||||||
'client_id' => config('larascord.client_id'),
|
|
||||||
'client_secret' => config('larascord.client_secret'),
|
|
||||||
'grant_type' => 'refresh_token',
|
|
||||||
'refresh_token' => $refreshToken,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return new AccessToken(json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authenticates the user with the access token and returns the user data.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
*/
|
|
||||||
public function getCurrentUser(AccessToken $accessToken): \Jakyeru\Larascord\Types\User
|
|
||||||
{
|
|
||||||
$response = Http::withToken($accessToken->access_token)->get($this->baseApi . '/users/@me');
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return new \Jakyeru\Larascord\Types\User(json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the user's guilds.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function getCurrentUserGuilds(AccessToken $accessToken, bool $withCounts = false): array
|
|
||||||
{
|
|
||||||
if (!$accessToken->hasScope('guilds')) throw new Exception(config('larascord.error_messages.missing_guilds_scope.message'));
|
|
||||||
|
|
||||||
$endpoint = '/users/@me/guilds';
|
|
||||||
|
|
||||||
if ($withCounts) {
|
|
||||||
$endpoint .= '?with_counts=true';
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = Http::withToken($accessToken->access_token, $accessToken->token_type)->get($this->baseApi . $endpoint);
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return array_map(function ($guild) {
|
|
||||||
return new \Jakyeru\Larascord\Types\Guild($guild);
|
|
||||||
}, json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the Guild Member object for a user.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function getGuildMember(AccessToken $accessToken, string $guildId): GuildMember
|
|
||||||
{
|
|
||||||
if (!$accessToken->hasScopes(['guilds', 'guilds.members.read'])) throw new Exception(config('larascord.error_messages.missing_guilds_members_read_scope.message'));
|
|
||||||
|
|
||||||
$response = Http::withToken($accessToken->access_token, $accessToken->token_type)->get($this->baseApi . '/users/@me/guilds/' . $guildId . '/member');
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return new GuildMember(json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the User's connections.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function getCurrentUserConnections(AccessToken $accessToken): array
|
|
||||||
{
|
|
||||||
if (!$accessToken->hasScope('connections')) throw new Exception('The "connections" scope is required.');
|
|
||||||
|
|
||||||
$response = Http::withToken($accessToken->access_token, $accessToken->token_type)->get($this->baseApi . '/users/@me/connections');
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return array_map(function ($connection) {
|
|
||||||
return new \Jakyeru\Larascord\Types\Connection($connection);
|
|
||||||
}, json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Join a guild.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function joinGuild(AccessToken $accessToken, User $user, string $guildId, array $options = []): GuildMember
|
|
||||||
{
|
|
||||||
if (!config('larascord.access_token')) throw new Exception(config('larascord.error_messages.missing_access_token.message'));
|
|
||||||
if (!$accessToken->hasScope('guilds.join')) throw new Exception('The "guilds" and "guilds.join" scopes are required.');
|
|
||||||
|
|
||||||
$response = Http::withToken(config('larascord.access_token'), 'Bot')->put($this->baseApi . '/guilds/' . $guildId . '/members/' . $user->id, array_merge([
|
|
||||||
'access_token' => $accessToken->access_token,
|
|
||||||
], $options));
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
if ($response->status() === 204) return throw new Exception('User is already in the guild.');
|
|
||||||
|
|
||||||
return new GuildMember(json_decode($response->body()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create or update a user in the database.
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function createOrUpdateUser(\Jakyeru\Larascord\Types\User $user): User
|
|
||||||
{
|
|
||||||
if (!$user->getAccessToken()) {
|
|
||||||
throw new Exception('User access token is missing.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$forgottenUser = User::where('email', '=', $user->email)->where('id', '!=', $user->id)->first();
|
|
||||||
if ($forgottenUser) {
|
|
||||||
// This case should never happen (TM) - The discord id changed
|
|
||||||
// The user probably re-created their discord account with the same email
|
|
||||||
|
|
||||||
// Delete Playlist
|
|
||||||
$playlists = Playlist::where('user_id', $forgottenUser->id)->get();
|
|
||||||
foreach($playlists as $playlist) {
|
|
||||||
PlaylistEpisode::where('playlist_id', $playlist->id)->forceDelete();
|
|
||||||
$playlist->forceDelete();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update comments to deleted user
|
|
||||||
DB::table('comments')->where('commenter_id', '=', $forgottenUser->id)->update(['commenter_id' => 1]);
|
|
||||||
|
|
||||||
$forgottenUser->forceDelete();
|
|
||||||
}
|
|
||||||
|
|
||||||
return User::updateOrCreate(
|
|
||||||
[
|
|
||||||
'id' => $user->id,
|
|
||||||
],
|
|
||||||
$user->toArray(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify if the user is in the specified guild(s).
|
|
||||||
*/
|
|
||||||
public function isUserInGuilds(array $guilds): bool
|
|
||||||
{
|
|
||||||
// Verify if the user is in all the specified guilds if strict mode is enabled.
|
|
||||||
if (config('larascord.guilds_strict')) {
|
|
||||||
return empty(array_diff(config('larascord.guilds'), array_column($guilds, 'id')));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify if the user is in any of the specified guilds if strict mode is disabled.
|
|
||||||
return !empty(array_intersect(config('larascord.guilds'), array_column($guilds, 'id')));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify if the user has the specified role(s) in the specified guild.
|
|
||||||
*/
|
|
||||||
public function hasRoleInGuild(GuildMember $guildMember, array $roles): bool
|
|
||||||
{
|
|
||||||
// Verify if the user has any of the specified roles.
|
|
||||||
return !empty(array_intersect($roles, $guildMember->roles));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the user's roles in the database.
|
|
||||||
*/
|
|
||||||
public function updateUserRoles(User $user, GuildMember $guildMember, int $guildId): void
|
|
||||||
{
|
|
||||||
// Updating the user's roles in the database.
|
|
||||||
$updatedRoles = $user->roles;
|
|
||||||
$updatedRoles[$guildId] = $guildMember->roles;
|
|
||||||
$user->roles = $updatedRoles;
|
|
||||||
$user->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Revoke the user's access token.
|
|
||||||
*
|
|
||||||
* @throws RequestException
|
|
||||||
*/
|
|
||||||
public function revokeAccessToken(string $accessToken): object
|
|
||||||
{
|
|
||||||
$response = Http::asForm()->post($this->tokenURL . '/revoke', [
|
|
||||||
'token' => $accessToken,
|
|
||||||
'client_id' => config('larascord.client_id'),
|
|
||||||
'client_secret' => config('larascord.client_secret'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response->throw();
|
|
||||||
|
|
||||||
return json_decode($response->body());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\Comment;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||||
|
|
||||||
|
class CommentPolicy
|
||||||
|
{
|
||||||
|
use HandlesAuthorization;
|
||||||
|
|
||||||
|
public function update(User $user, Comment $comment): bool
|
||||||
|
{
|
||||||
|
return $user->id === $comment->user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(User $user, Comment $comment): bool
|
||||||
|
{
|
||||||
|
if ($user->hasRole(UserRole::ADMINISTRATOR) ||
|
||||||
|
$user->hasRole(UserRole::MODERATOR)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user->id === $comment->user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restore(User $user, Comment $comment): bool
|
||||||
|
{
|
||||||
|
// Comment not deleted
|
||||||
|
if ($comment->deleted_by_moderator_id === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->hasRole(UserRole::ADMINISTRATOR) ||
|
||||||
|
$user->hasRole(UserRole::MODERATOR)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use SocialiteProviders\Discord\Provider;
|
||||||
|
use SocialiteProviders\Manager\SocialiteWasCalled;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -19,6 +22,8 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
Event::listen(function (SocialiteWasCalled $event) {
|
||||||
|
$event->extendSocialite('discord', Provider::class);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Rules;
|
||||||
|
|
||||||
|
use AltchaOrg\Altcha\Algorithm\Pbkdf2;
|
||||||
|
use AltchaOrg\Altcha\Altcha;
|
||||||
|
use AltchaOrg\Altcha\Challenge;
|
||||||
|
use AltchaOrg\Altcha\ChallengeParameters;
|
||||||
|
use AltchaOrg\Altcha\Payload;
|
||||||
|
use AltchaOrg\Altcha\Solution;
|
||||||
|
use AltchaOrg\Altcha\VerifySolutionOptions;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\Validation\ValidationRule;
|
||||||
|
use Illuminate\Translation\PotentiallyTranslatedString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation rule to verify captcha solution.
|
||||||
|
*/
|
||||||
|
class ValidCaptcha implements ValidationRule
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Altcha instance.
|
||||||
|
*/
|
||||||
|
protected Altcha $altcha;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pbkdf2 algorithm instance.
|
||||||
|
*/
|
||||||
|
protected Pbkdf2 $pbkdf2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pbkdf2 = new Pbkdf2;
|
||||||
|
$this->altcha = new Altcha(
|
||||||
|
hmacSignatureSecret: config('captcha.hmac_key'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse payload and return the decoded data as an array.
|
||||||
|
*/
|
||||||
|
private function parsePayload(string $value): ?array
|
||||||
|
{
|
||||||
|
$decoded = base64_decode($value, true);
|
||||||
|
if ($decoded === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = json_decode($decoded, true);
|
||||||
|
if (! is_array($payload)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify if payload has required fields.
|
||||||
|
*/
|
||||||
|
private function verifyFields(array $payload): bool
|
||||||
|
{
|
||||||
|
if (! isset($payload['challenge'], $payload['solution'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! is_array($payload['challenge']) || ! is_array($payload['solution'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Challenge object from challenge data.
|
||||||
|
*/
|
||||||
|
private function createChallenge(array $challengeData): Challenge
|
||||||
|
{
|
||||||
|
return new Challenge(
|
||||||
|
ChallengeParameters::fromArray($challengeData['parameters'] ?? []),
|
||||||
|
$challengeData['signature'] ?? null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Solution object from solution data.
|
||||||
|
*/
|
||||||
|
private function createSolution(array $solutionData): Solution
|
||||||
|
{
|
||||||
|
return new Solution(
|
||||||
|
counter: (int) ($solutionData['counter'] ?? 0),
|
||||||
|
derivedKey: (string) ($solutionData['derivedKey'] ?? ''),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the validation rule.
|
||||||
|
*
|
||||||
|
* @param Closure(string, ?string=): PotentiallyTranslatedString $fail
|
||||||
|
*/
|
||||||
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
|
{
|
||||||
|
$payload = $this->parsePayload($value);
|
||||||
|
if (! $payload) {
|
||||||
|
$fail('Invalid captcha.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->verifyFields($payload)) {
|
||||||
|
$fail('Invalid captcha.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$challenge = $this->createChallenge($payload['challenge']);
|
||||||
|
$solution = $this->createSolution($payload['solution']);
|
||||||
|
|
||||||
|
$result = $this->altcha->verifySolution(new VerifySolutionOptions(
|
||||||
|
algorithm: $this->pbkdf2,
|
||||||
|
payload: new Payload($challenge, $solution),
|
||||||
|
));
|
||||||
|
|
||||||
|
if (! $result->verified) {
|
||||||
|
$fail('Invalid captcha.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Jobs\GetFileSizeFromCDN;
|
||||||
use App\Models\Downloads;
|
use App\Models\Downloads;
|
||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
|
|
||||||
use App\Jobs\GetFileSizeFromCDN;
|
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class DownloadService
|
class DownloadService
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ namespace App\Services;
|
|||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
use App\Models\Hentai;
|
use App\Models\Hentai;
|
||||||
use App\Models\Studios;
|
use App\Models\Studios;
|
||||||
|
use App\Models\ModLog;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Intervention\Image\Laravel\Facades\Image;
|
|
||||||
use Intervention\Image\Encoders\WebpEncoder;
|
use Intervention\Image\Encoders\WebpEncoder;
|
||||||
|
use Intervention\Image\Laravel\Facades\Image;
|
||||||
|
|
||||||
class EpisodeService
|
class EpisodeService
|
||||||
{
|
{
|
||||||
@@ -24,6 +23,7 @@ class EpisodeService
|
|||||||
|
|
||||||
if (is_numeric($lastPart) && $lastPart < 1000) {
|
if (is_numeric($lastPart) && $lastPart < 1000) {
|
||||||
$slugParts[array_key_last($slugParts)] = 's'.$lastPart;
|
$slugParts[array_key_last($slugParts)] = 's'.$lastPart;
|
||||||
|
|
||||||
return implode('-', $slugParts);
|
return implode('-', $slugParts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,13 +34,12 @@ class EpisodeService
|
|||||||
Request $request,
|
Request $request,
|
||||||
Hentai $hentai,
|
Hentai $hentai,
|
||||||
int $episodeNumber,
|
int $episodeNumber,
|
||||||
Studios $studio = null,
|
?Studios $studio = null,
|
||||||
Episode $referenceEpisode = null
|
?Episode $referenceEpisode = null
|
||||||
): Episode
|
): Episode {
|
||||||
{
|
$episode = new Episode;
|
||||||
$episode = new Episode();
|
|
||||||
$episode->title = $referenceEpisode->title ?? $request->input('title');
|
$episode->title = $referenceEpisode->title ?? $request->input('title');
|
||||||
$episode->title_search = preg_replace("/[^A-Za-z0-9 ]/", '', $episode->title);
|
$episode->title_search = preg_replace('/[^A-Za-z0-9 ]/', '', $episode->title);
|
||||||
$episode->title_jpn = $referenceEpisode->title_jpn ?? $request->input('title_jpn');
|
$episode->title_jpn = $referenceEpisode->title_jpn ?? $request->input('title_jpn');
|
||||||
$episode->slug = "{$hentai->slug}-{$episodeNumber}";
|
$episode->slug = "{$hentai->slug}-{$episodeNumber}";
|
||||||
$episode->hentai_id = $hentai->id;
|
$episode->hentai_id = $hentai->id;
|
||||||
@@ -64,6 +63,68 @@ class EpisodeService
|
|||||||
return $episode;
|
return $episode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function applyTags(Request $request, Episode $episode): void
|
||||||
|
{
|
||||||
|
$tags = json_decode($request->input('tags'));
|
||||||
|
$newtags = [];
|
||||||
|
foreach ($tags as $t) {
|
||||||
|
$newtags[] = $t->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newTagsTemp = $newtags;
|
||||||
|
$oldTagsTemp = $episode->tagNames();
|
||||||
|
|
||||||
|
sort($newTagsTemp);
|
||||||
|
sort($oldTagsTemp);
|
||||||
|
|
||||||
|
if ($newTagsTemp !== $oldTagsTemp) {
|
||||||
|
ModLog::create([
|
||||||
|
'moderator' => $request->user()->name,
|
||||||
|
'data' => sprintf(
|
||||||
|
'Updated Episode tags from %s to %s',
|
||||||
|
implode(', ', $oldTagsTemp),
|
||||||
|
implode(', ', $newTagsTemp),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$episode->retag($newtags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateTitle(Request $request, Episode $episode): void
|
||||||
|
{
|
||||||
|
$updates = [];
|
||||||
|
|
||||||
|
if ($episode->title !== $request->input('title')) {
|
||||||
|
$updates['title'] = $request->input('title');
|
||||||
|
$updates['title_search'] = preg_replace(
|
||||||
|
'/[^A-Za-z0-9 ]/',
|
||||||
|
'',
|
||||||
|
$request->input('title')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Log to ModLog
|
||||||
|
ModLog::create([
|
||||||
|
'moderator' => $request->user()->name,
|
||||||
|
'data' => "Updating Hentai Title from {$episode->title} to {$request->input('title')}",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($episode->title_jpn !== $request->input('title_jpn')) {
|
||||||
|
$updates['title_jpn'] = $request->input('title_jpn');
|
||||||
|
|
||||||
|
// Log to ModLog
|
||||||
|
ModLog::create([
|
||||||
|
'moderator' => $request->user()->name,
|
||||||
|
'data' => "Updating Hentai Title from {$episode->title_jpn} to {$request->input('title_jpn')}",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($updates)) {
|
||||||
|
$episode->hentai->episodes()->update($updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function updateEpisode(Request $request, Studios $studio, int $episodeId): Episode
|
public function updateEpisode(Request $request, Studios $studio, int $episodeId): Episode
|
||||||
{
|
{
|
||||||
$episode = Episode::where('id', $episodeId)->firstOrFail();
|
$episode = Episode::where('id', $episodeId)->firstOrFail();
|
||||||
@@ -74,19 +135,34 @@ class EpisodeService
|
|||||||
$episode->interpolated = $request->input('interpolated') == 'yes';
|
$episode->interpolated = $request->input('interpolated') == 'yes';
|
||||||
$episode->interpolated_uhd = $request->input('downloadUHDi1') ? true : false;
|
$episode->interpolated_uhd = $request->input('downloadUHDi1') ? true : false;
|
||||||
$episode->is_dvd_aspect = $request->input('dvd') == 'yes';
|
$episode->is_dvd_aspect = $request->input('dvd') == 'yes';
|
||||||
|
$episode->dmca_takedown = $request->input('dmca_takedown') == 'true';
|
||||||
$episode->save();
|
$episode->save();
|
||||||
|
|
||||||
// Tagging
|
$this->applyTags($request, $episode);
|
||||||
$tags = json_decode($request->input('tags'));
|
$this->updateTitle($request, $episode);
|
||||||
$newtags = [];
|
|
||||||
foreach ($tags as $t) {
|
|
||||||
$newtags[] = $t->value;
|
|
||||||
}
|
|
||||||
$episode->retag($newtags);
|
|
||||||
|
|
||||||
return $episode;
|
return $episode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updateEpisodeModerator(Request $request, int $episodeId): void
|
||||||
|
{
|
||||||
|
$episode = Episode::where('id', $episodeId)->firstOrFail();
|
||||||
|
$oldDescription = $episode->description;
|
||||||
|
$episode->description = $request->input('description');
|
||||||
|
$episode->save();
|
||||||
|
|
||||||
|
if ($episode->description !== $oldDescription) {
|
||||||
|
// Log to ModLog
|
||||||
|
ModLog::create([
|
||||||
|
'moderator' => $request->user()->name,
|
||||||
|
'data' => "Updated Episode description from {$oldDescription} to {$episode->description}",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->applyTags($request, $episode);
|
||||||
|
$this->updateTitle($request, $episode);
|
||||||
|
}
|
||||||
|
|
||||||
public function getOrCreateStudio(string $studioName): Studios
|
public function getOrCreateStudio(string $studioName): Studios
|
||||||
{
|
{
|
||||||
return Studios::firstOrCreate(
|
return Studios::firstOrCreate(
|
||||||
@@ -95,7 +171,6 @@ class EpisodeService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function createOrUpdateCover(Request $request, Episode $episode, string $slug, int $episodeNumber): void
|
public function createOrUpdateCover(Request $request, Episode $episode, string $slug, int $episodeNumber): void
|
||||||
{
|
{
|
||||||
if (! $request->hasFile("episodecover{$episodeNumber}")) {
|
if (! $request->hasFile("episodecover{$episodeNumber}")) {
|
||||||
@@ -110,7 +185,7 @@ class EpisodeService
|
|||||||
// Encode and save cover image
|
// Encode and save cover image
|
||||||
Image::read($request->file("episodecover{$episodeNumber}")->getRealPath())
|
Image::read($request->file("episodecover{$episodeNumber}")->getRealPath())
|
||||||
->cover(268, 394)
|
->cover(268, 394)
|
||||||
->encode(new WebpEncoder())
|
->encode(new WebpEncoder)
|
||||||
->save(Storage::disk('public')->path($episode->cover_url));
|
->save(Storage::disk('public')->path($episode->cover_url));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,13 @@ namespace App\Services;
|
|||||||
use App\Models\Episode;
|
use App\Models\Episode;
|
||||||
use App\Models\Gallery;
|
use App\Models\Gallery;
|
||||||
use App\Models\Hentai;
|
use App\Models\Hentai;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\File;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
use Intervention\Image\Laravel\Facades\Image;
|
|
||||||
use Intervention\Image\Encoders\WebpEncoder;
|
use Intervention\Image\Encoders\WebpEncoder;
|
||||||
|
use Intervention\Image\Laravel\Facades\Image;
|
||||||
|
|
||||||
class GalleryService
|
class GalleryService
|
||||||
{
|
{
|
||||||
|
|
||||||
public function createOrUpdateGallery(Request $request, Hentai $hentai, Episode $episode, int $episodeNumber, bool $override = false): void
|
public function createOrUpdateGallery(Request $request, Hentai $hentai, Episode $episode, int $episodeNumber, bool $override = false): void
|
||||||
{
|
{
|
||||||
$galleryInputNumber = $override ? 1 : $episodeNumber;
|
$galleryInputNumber = $override ? 1 : $episodeNumber;
|
||||||
@@ -45,7 +41,7 @@ class GalleryService
|
|||||||
|
|
||||||
private function createGallery(Hentai $hentai, Episode $episode, int $episodeNumber, int $counter): Gallery
|
private function createGallery(Hentai $hentai, Episode $episode, int $episodeNumber, int $counter): Gallery
|
||||||
{
|
{
|
||||||
$gallery = new Gallery();
|
$gallery = new Gallery;
|
||||||
$gallery->hentai_id = $hentai->id;
|
$gallery->hentai_id = $hentai->id;
|
||||||
$gallery->episode_id = $episode->id;
|
$gallery->episode_id = $episode->id;
|
||||||
$gallery->image_url = "/images/hentai/{$hentai->slug}/gallery-ep-{$episodeNumber}-{$counter}.webp";
|
$gallery->image_url = "/images/hentai/{$hentai->slug}/gallery-ep-{$episodeNumber}-{$counter}.webp";
|
||||||
@@ -59,12 +55,12 @@ class GalleryService
|
|||||||
{
|
{
|
||||||
Image::read($sourceImage->getRealPath())
|
Image::read($sourceImage->getRealPath())
|
||||||
->cover(1920, 1080)
|
->cover(1920, 1080)
|
||||||
->encode(new WebpEncoder())
|
->encode(new WebpEncoder)
|
||||||
->save(Storage::disk('public')->path($gallery->image_url));
|
->save(Storage::disk('public')->path($gallery->image_url));
|
||||||
|
|
||||||
Image::read($sourceImage->getRealPath())
|
Image::read($sourceImage->getRealPath())
|
||||||
->cover(960, 540)
|
->cover(960, 540)
|
||||||
->encode(new WebpEncoder())
|
->encode(new WebpEncoder)
|
||||||
->save(Storage::disk('public')->path($gallery->thumbnail_url));
|
->save(Storage::disk('public')->path($gallery->thumbnail_url));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +70,7 @@ class GalleryService
|
|||||||
foreach ($oldGallery as $oldImage) {
|
foreach ($oldGallery as $oldImage) {
|
||||||
Storage::disk('public')->delete($oldImage->image_url);
|
Storage::disk('public')->delete($oldImage->image_url);
|
||||||
Storage::disk('public')->delete($oldImage->thumbnail_url);
|
Storage::disk('public')->delete($oldImage->thumbnail_url);
|
||||||
$oldImage->forceDelete();
|
$oldImage->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
class MatrixRegistrationService
|
||||||
|
{
|
||||||
|
public function registerUser(string $username, string $password)
|
||||||
|
{
|
||||||
|
$server = config('services.matrix.server');
|
||||||
|
$secret = config('services.matrix.shared_secret');
|
||||||
|
|
||||||
|
// Get nonce from Synapse
|
||||||
|
$nonceResponse = Http::get("$server/_synapse/admin/v1/register");
|
||||||
|
|
||||||
|
if (! $nonceResponse->ok()) {
|
||||||
|
throw new \Exception('Could not fetch nonce from Matrix.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$nonce = $nonceResponse->json()['nonce'];
|
||||||
|
|
||||||
|
// Generate MAC
|
||||||
|
$mac = hash_hmac(
|
||||||
|
'sha1',
|
||||||
|
$nonce."\0".
|
||||||
|
$username."\0".
|
||||||
|
$password."\0".
|
||||||
|
'notadmin',
|
||||||
|
$secret
|
||||||
|
);
|
||||||
|
|
||||||
|
// Send registration request
|
||||||
|
$response = Http::post("$server/_synapse/admin/v1/register", [
|
||||||
|
'nonce' => $nonce,
|
||||||
|
'username' => $username,
|
||||||
|
'password' => $password,
|
||||||
|
'admin' => false,
|
||||||
|
'mac' => $mac,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($response->failed()) {
|
||||||
|
$error = $response->json()['error'] ?? $response->body();
|
||||||
|
throw new \Exception($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response->json();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\User;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class SubscriptionService
|
||||||
|
{
|
||||||
|
private function generateEncryptedPayload(string $subscriptionKey): string
|
||||||
|
{
|
||||||
|
return base64_encode(Crypt::encryptString(json_encode([
|
||||||
|
'subscription_access_key' => $subscriptionKey,
|
||||||
|
'timestamp' => now()->timestamp,
|
||||||
|
'nonce' => Str::uuid()->toString(),
|
||||||
|
], JSON_THROW_ON_ERROR)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the subscription status from the subscription service.
|
||||||
|
*/
|
||||||
|
private function getSubscriptionStatus(string $subscriptionKey): array | null
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$payload = $this->generateEncryptedPayload($subscriptionKey);
|
||||||
|
|
||||||
|
$response = Http::post(config('services.subscription_service_host').'/api/membership/verify', [
|
||||||
|
'payload' => $payload,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $response->successful()) {
|
||||||
|
logger()->error('Subscription Service API error', [
|
||||||
|
'status' => $response->status(),
|
||||||
|
'body' => $response->body(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$encryptedResponse = $response->json('payload');
|
||||||
|
|
||||||
|
$json = json_decode(
|
||||||
|
Crypt::decryptString($encryptedResponse),
|
||||||
|
true,
|
||||||
|
flags: JSON_THROW_ON_ERROR
|
||||||
|
);
|
||||||
|
|
||||||
|
return $json;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
logger()->error('getSubscriptionStatus Exception', [
|
||||||
|
'details' => $e,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkSubscriptionStatus(User $user, string $subscriptionKey): bool
|
||||||
|
{
|
||||||
|
$subscriptionStatus = $this->getSubscriptionStatus($subscriptionKey);
|
||||||
|
if (!$subscriptionStatus) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($subscriptionStatus['valid'] === true &&
|
||||||
|
$subscriptionStatus['active'] === true) {
|
||||||
|
$user->addRole(UserRole::SUPPORTER);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->removeRole(UserRole::SUPPORTER);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-4
@@ -1,5 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Exceptions\Handler;
|
||||||
|
use App\Http\Kernel;
|
||||||
|
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||||
|
use Illuminate\Foundation\Application;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Create The Application
|
| Create The Application
|
||||||
@@ -11,7 +16,7 @@
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$app = new Illuminate\Foundation\Application(
|
$app = new Application(
|
||||||
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
|
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -28,7 +33,7 @@ $app = new Illuminate\Foundation\Application(
|
|||||||
|
|
||||||
$app->singleton(
|
$app->singleton(
|
||||||
Illuminate\Contracts\Http\Kernel::class,
|
Illuminate\Contracts\Http\Kernel::class,
|
||||||
App\Http\Kernel::class
|
Kernel::class
|
||||||
);
|
);
|
||||||
|
|
||||||
$app->singleton(
|
$app->singleton(
|
||||||
@@ -37,8 +42,8 @@ $app->singleton(
|
|||||||
);
|
);
|
||||||
|
|
||||||
$app->singleton(
|
$app->singleton(
|
||||||
Illuminate\Contracts\Debug\ExceptionHandler::class,
|
ExceptionHandler::class,
|
||||||
App\Exceptions\Handler::class
|
Handler::class
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
+22
-34
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "laravel/laravel",
|
"name": "w33b/hstream",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"description": "The skeleton application for the Laravel framework.",
|
"description": "The website of hstream.moe",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"laravel",
|
"laravel",
|
||||||
"framework"
|
"framework"
|
||||||
@@ -9,59 +9,47 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
|
"altcha-org/altcha": "^2.0",
|
||||||
"guzzlehttp/guzzle": "^7.8.1",
|
"guzzlehttp/guzzle": "^7.8.1",
|
||||||
"hisorange/browser-detect": "^5.0",
|
"hisorange/browser-detect": "^5.0",
|
||||||
"intervention/image": "^3.9",
|
"http-interop/http-factory-guzzle": "^1.2",
|
||||||
"intervention/image-laravel": "^1.3",
|
"intervention/image": "^3.11",
|
||||||
"jakyeru/larascord": "^6.0",
|
"intervention/image-laravel": "^1.5",
|
||||||
"laravel/framework": "^11.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.2",
|
||||||
|
"laravel/scout": "^10.20",
|
||||||
|
"laravel/socialite": "^5.24",
|
||||||
"laravel/tinker": "^2.10",
|
"laravel/tinker": "^2.10",
|
||||||
"laravelista/comments": "dev-l11-compatibility",
|
"livewire/livewire": "^3.7.0",
|
||||||
"livewire/livewire": "^3.6.4",
|
|
||||||
"maize-tech/laravel-markable": "^2.3.0",
|
"maize-tech/laravel-markable": "^2.3.0",
|
||||||
"mews/captcha": "3.4.4",
|
"meilisearch/meilisearch-php": "^1.16",
|
||||||
"predis/predis": "^2.2",
|
"predis/predis": "^2.2",
|
||||||
"realrashid/sweet-alert": "^7.2",
|
"realrashid/sweet-alert": "^7.2",
|
||||||
"rtconner/laravel-tagging": "^4.1",
|
"rtconner/laravel-tagging": "^5.0",
|
||||||
"spatie/laravel-discord-alerts": "^1.5",
|
"socialiteproviders/discord": "^4.2",
|
||||||
"spatie/laravel-sitemap": "^7.3",
|
"spatie/laravel-discord-alerts": "^1.8",
|
||||||
"vluzrmos/language-detector": "^2.3"
|
"spatie/laravel-passkeys": "^1.7",
|
||||||
|
"spatie/laravel-sitemap": "^7.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"barryvdh/laravel-debugbar": "^3.14.7",
|
"barryvdh/laravel-debugbar": "^3.16",
|
||||||
"fakerphp/faker": "^1.24.0",
|
"fakerphp/faker": "^1.24.0",
|
||||||
|
"laravel/breeze": "^2.3",
|
||||||
"laravel/pint": "^1.18",
|
"laravel/pint": "^1.18",
|
||||||
"laravel/sail": "^1.38",
|
|
||||||
"mockery/mockery": "^1.4.4",
|
"mockery/mockery": "^1.4.4",
|
||||||
"nunomaduro/collision": "^8.1",
|
"nunomaduro/collision": "^8.1",
|
||||||
"phpunit/phpunit": "^11.4",
|
"phpunit/phpunit": "^11.4",
|
||||||
"spatie/laravel-ignition": "^2.0"
|
"spatie/laravel-ignition": "^2.0"
|
||||||
},
|
},
|
||||||
"repositories": [
|
"repositories": [],
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "https://github.com/renatokira/comments.git"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"exclude-from-classmap": [
|
"exclude-from-classmap": [],
|
||||||
"vendor/jakyeru/larascord/src/Http/Services/DiscordService.php",
|
|
||||||
"vendor/jakyeru/larascord/src/Http/Controllers/DiscordController.php",
|
|
||||||
"vendor/laravelista/comments/src/CommentPolicy.php",
|
|
||||||
"vendor/laravelista/comments/src/CommentService.php"
|
|
||||||
],
|
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"App\\": "app/",
|
"App\\": "app/",
|
||||||
"Database\\Factories\\": "database/factories/",
|
"Database\\Factories\\": "database/factories/",
|
||||||
"Database\\Seeders\\": "database/seeders/"
|
"Database\\Seeders\\": "database/seeders/"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": []
|
||||||
"app/Override/Discord/Services/DiscordService.php",
|
|
||||||
"app/Override/Discord/DiscordController.php",
|
|
||||||
"app/Override/Comments/CommentPolicy.php",
|
|
||||||
"app/Override/Comments/CommentService.php"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
Generated
+3203
-1585
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user