Compare commits
17 Commits
823a284fbc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b4d3d435e | |||
| 564f816fb9 | |||
| 3709e378c3 | |||
| 4a45dae593 | |||
| 2b0448d517 | |||
| 3bb6af73c3 | |||
| 57cf153560 | |||
| e45fd4b148 | |||
|
d479369770
|
|||
|
af739e3c88
|
|||
| 273ed65a8d | |||
| ccfd5b996b | |||
| e5ef197ed6 | |||
| c0be2e294a | |||
| b8ba17b33f | |||
| 5a8dd12cb8 | |||
| 3a77c4320d |
14
README.md
14
README.md
@@ -2,13 +2,13 @@
|
||||
|
||||
## hstream Website
|
||||
|
||||
### Install
|
||||
### Install (Ubuntu)
|
||||
|
||||
```bash
|
||||
# Install PHP
|
||||
sudo add-apt-repository ppa:ondrej/php
|
||||
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
|
||||
curl -sL https://deb.nodesource.com/setup_20.x -o /tmp/nodesource_setup.sh
|
||||
@@ -22,7 +22,7 @@ mv composer.phar composer
|
||||
|
||||
# Install NGINX (skip for local dev)
|
||||
apt install nginx
|
||||
apt install php8.3-fpm
|
||||
apt install php8.4-fpm
|
||||
|
||||
# Install MariaDB
|
||||
apt install mariadb-server
|
||||
@@ -54,7 +54,7 @@ nano /etc/supervisor/conf.d/laravel-queue.conf :
|
||||
|
||||
[program:laravel-queue]
|
||||
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
|
||||
autorestart=true
|
||||
stopasgroup=true
|
||||
@@ -83,9 +83,9 @@ zip -r hstream_2023_11_30.zip hstream/
|
||||
|
||||
### Update
|
||||
```bash
|
||||
php artisan down
|
||||
php84 artisan down
|
||||
git pull
|
||||
npm run build
|
||||
php artisan view:clear && php artisan optimize:clear && php artisan cache:clear && service php8.4-fpm restart
|
||||
php artisan up
|
||||
php84 artisan view:clear && php84 artisan optimize:clear && php84 artisan cache:clear && service php8.4-fpm restart
|
||||
php84 artisan up
|
||||
```
|
||||
|
||||
11
app/Enums/UserRole.php
Normal file
11
app/Enums/UserRole.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum UserRole: string
|
||||
{
|
||||
case ADMINISTRATOR = 'admin';
|
||||
case MODERATOR = 'moderator';
|
||||
case SUPPORTER = 'supporter';
|
||||
case BANNED = 'banned';
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Enums\UserRole;
|
||||
use App\Models\User;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -31,11 +32,11 @@ class UserController extends Controller
|
||||
|
||||
switch ($validated['action']) {
|
||||
case 'ban':
|
||||
$user->update(['is_banned' => 1]);
|
||||
$user->addRole(UserRole::BANNED);
|
||||
alert()->success('Banned', 'User has been banned.');
|
||||
break;
|
||||
case 'unban':
|
||||
$user->update(['is_banned' => 0]);
|
||||
$user->removeRole(UserRole::BANNED);
|
||||
alert()->success('Unbanned', 'User has been unbanned.');
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -8,6 +8,8 @@ use App\Models\Episode;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use GrantHolle\Altcha\Rules\ValidAltcha;
|
||||
|
||||
class DownloadApiController extends Controller
|
||||
{
|
||||
/**
|
||||
@@ -16,11 +18,12 @@ class DownloadApiController extends Controller
|
||||
public function getDownload(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'episode_id' => 'required',
|
||||
'captcha' => 'required|captcha'
|
||||
'episode_id' => ['required'],
|
||||
'captcha' => ['required', new ValidAltcha],
|
||||
]);
|
||||
|
||||
$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
|
||||
// downloads after submitting the captcha
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Enums\UserRole;
|
||||
use App\Models\User;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
@@ -88,10 +89,7 @@ class DiscordAuthController extends Controller
|
||||
|
||||
// User is not in the guild
|
||||
if ($response->status() === 404) {
|
||||
$user->update([
|
||||
'is_patreon' => false,
|
||||
]);
|
||||
|
||||
$user->removeRole(UserRole::SUPPORTER);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -110,21 +108,15 @@ class DiscordAuthController extends Controller
|
||||
$discordRoles = $response->json('roles', []);
|
||||
$patreonRoles = config('discord.patreon_roles', []);
|
||||
|
||||
$isPatreon = false;
|
||||
foreach($patreonRoles as $patreonRole)
|
||||
{
|
||||
if (in_array($patreonRole, $discordRoles, true)) {
|
||||
$isPatreon = true;
|
||||
break;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Only update if something actually changed
|
||||
if ($user->is_patreon !== $isPatreon) {
|
||||
$user->update([
|
||||
'is_patreon' => $isPatreon,
|
||||
]);
|
||||
}
|
||||
$user->addRole(UserRole::SUPPORTER);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules;
|
||||
|
||||
use GrantHolle\Altcha\Rules\ValidAltcha;
|
||||
|
||||
class RegisteredUserController extends Controller
|
||||
{
|
||||
/**
|
||||
@@ -24,6 +26,7 @@ class RegisteredUserController extends Controller
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
'altcha' => ['required', new ValidAltcha],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Http\Controllers;
|
||||
use App\Models\Contact;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use GrantHolle\Altcha\Rules\ValidAltcha;
|
||||
|
||||
class ContactController extends Controller
|
||||
{
|
||||
/**
|
||||
@@ -25,7 +27,7 @@ class ContactController extends Controller
|
||||
'email' => 'required|max:50',
|
||||
'message' => 'required|max:1000',
|
||||
'subject' => 'required|max:50',
|
||||
'captcha' => 'required|captcha',
|
||||
'altcha' => ['required', new ValidAltcha],
|
||||
]);
|
||||
|
||||
$contact = new Contact();
|
||||
@@ -37,9 +39,4 @@ class ContactController extends Controller
|
||||
|
||||
return back()->with('status', 'contact-submitted');
|
||||
}
|
||||
|
||||
public function reloadCaptcha(): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
return response()->json(['captcha'=> captcha_img()]);
|
||||
}
|
||||
}
|
||||
|
||||
57
app/Http/Controllers/MatrixController.php
Normal file
57
app/Http/Controllers/MatrixController.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\MatrixRegisterRequest;
|
||||
use App\Services\MatrixRegistrationService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MatrixController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the user page.
|
||||
*/
|
||||
public function index(Request $request): \Illuminate\View\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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ class Kernel extends HttpKernel
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
'auth.admin' => \App\Http\Middleware\IsAdmin::class,
|
||||
'auth.moderator' => \App\Http\Middleware\IsModerator::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
<?php namespace app\Http\Middleware;
|
||||
|
||||
use App\Enums\UserRole;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
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.
|
||||
*
|
||||
@@ -30,15 +16,14 @@ class IsAdmin {
|
||||
* @param \Closure $next
|
||||
* @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,8 +1,11 @@
|
||||
<?php namespace app\Http\Middleware;
|
||||
|
||||
use App\Enums\UserRole;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class IsBanned {
|
||||
|
||||
@@ -13,9 +16,9 @@ class IsBanned {
|
||||
* @param \Closure $next
|
||||
* @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();
|
||||
$request->session()->invalidate();
|
||||
|
||||
29
app/Http/Middleware/IsModerator.php
Normal file
29
app/Http/Middleware/IsModerator.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?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(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (Auth::check() && Auth::user()->hasRole(UserRole::MODERATOR))
|
||||
{
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
session()->flash('error_msg','This resource is restricted to Administrators!');
|
||||
return redirect()->route('home.index');
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
use GrantHolle\Altcha\Rules\ValidAltcha;
|
||||
|
||||
class LoginRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
@@ -29,6 +31,7 @@ class LoginRequest extends FormRequest
|
||||
return [
|
||||
'email' => ['required', 'string', 'email'],
|
||||
'password' => ['required', 'string'],
|
||||
'altcha' => ['required', new ValidAltcha],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
49
app/Http/Requests/MatrixRegisterRequest.php
Normal file
49
app/Http/Requests/MatrixRegisterRequest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
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, \Illuminate\Contracts\Validation\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 . _ = -',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Enums\UserRole;
|
||||
use App\Models\Comment;
|
||||
use App\Models\User;
|
||||
|
||||
@@ -17,7 +18,7 @@ class AdminUserSearch extends Component
|
||||
public $search = '';
|
||||
|
||||
#[Url(history: true)]
|
||||
public $filtered = ['true'];
|
||||
public $discordId = '';
|
||||
|
||||
#[Url(history: true)]
|
||||
public $patreon = [];
|
||||
@@ -38,10 +39,10 @@ class AdminUserSearch extends Component
|
||||
|
||||
public function render()
|
||||
{
|
||||
$users = User::when($this->filtered !== [], fn ($query) => $query->where('id', '>=', 10000))
|
||||
->when($this->patreon !== [], fn ($query) => $query->where('is_patreon', 1))
|
||||
->when($this->banned !== [], fn ($query) => $query->where('is_banned', 1))
|
||||
$users = User::when($this->patreon !== [], fn ($query) => $query->whereJsonContains('roles', UserRole::SUPPORTER->value))
|
||||
->when($this->banned !== [], fn ($query) => $query->whereJsonContains('roles', UserRole::BANNED->value))
|
||||
->when($this->search !== '', fn ($query) => $query->where('name', 'like', '%'.$this->search.'%'))
|
||||
->when($this->discordId !== '', fn ($query) => $query->where('discord_id', '=', $this->discordId))
|
||||
->paginate(20);
|
||||
|
||||
return view('livewire.admin-user-search', [
|
||||
|
||||
@@ -21,6 +21,15 @@ class DownloadButton extends Component
|
||||
|
||||
public $background = 'bg-rose-600';
|
||||
|
||||
public $fileExtension = 'HEVC';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (str_contains($this->downloadUrl, 'AV1')) {
|
||||
$this->fileExtension = 'AV1';
|
||||
}
|
||||
}
|
||||
|
||||
public function clicked($downloadId)
|
||||
{
|
||||
$download = Downloads::find($downloadId);
|
||||
|
||||
@@ -73,9 +73,9 @@ class DownloadsSearch extends Component
|
||||
$types[] = 'FHD';
|
||||
} elseif ($label === 'FHD 48fps') {
|
||||
$types[] = 'FHDi';
|
||||
} elseif ($label === 'UHD' && auth()->user()->is_patreon) {
|
||||
} elseif ($label === 'UHD' && auth()->user()->hasRole(\App\Enums\UserRole::SUPPORTER)) {
|
||||
$types[] = 'UHD';
|
||||
} elseif ($label === 'UHD 48fps' && auth()->user()->is_patreon) {
|
||||
} elseif ($label === 'UHD 48fps' && auth()->user()->hasRole(\App\Enums\UserRole::SUPPORTER)) {
|
||||
$types[] = 'UHDi';
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ class DownloadsSearch extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (!auth()->user()->is_patreon) {
|
||||
if (!auth()->user()->hasRole(\App\Enums\UserRole::SUPPORTER)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
//use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
|
||||
use App\Enums\UserRole;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
@@ -25,7 +27,6 @@ class User extends Authenticatable
|
||||
'email',
|
||||
'password',
|
||||
'locale',
|
||||
'is_banned',
|
||||
// Discord
|
||||
'discord_id',
|
||||
'discord_avatar',
|
||||
@@ -54,7 +55,7 @@ class User extends Authenticatable
|
||||
'name' => 'string',
|
||||
'email' => 'string',
|
||||
'locale' => 'string',
|
||||
'roles' => 'json',
|
||||
'roles' => 'array',
|
||||
'tag_blacklist' => 'array',
|
||||
// Discord
|
||||
'discord_id' => 'integer',
|
||||
@@ -119,4 +120,44 @@ class User extends Authenticatable
|
||||
|
||||
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 = array_diff($this->roles, [$role->value]);
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
49
app/Services/MatrixRegistrationService.php
Normal file
49
app/Services/MatrixRegistrationService.php
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "laravel/laravel",
|
||||
"name": "w33b/hstream",
|
||||
"type": "project",
|
||||
"description": "The skeleton application for the Laravel framework.",
|
||||
"description": "The website of hstream.moe",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"framework"
|
||||
@@ -9,6 +9,7 @@
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"grantholle/laravel-altcha": "^2.1",
|
||||
"guzzlehttp/guzzle": "^7.8.1",
|
||||
"hisorange/browser-detect": "^5.0",
|
||||
"http-interop/http-factory-guzzle": "^1.2",
|
||||
@@ -22,7 +23,6 @@
|
||||
"livewire/livewire": "^3.7.0",
|
||||
"maize-tech/laravel-markable": "^2.3.0",
|
||||
"meilisearch/meilisearch-php": "^1.16",
|
||||
"mews/captcha": "^3.4.4",
|
||||
"predis/predis": "^2.2",
|
||||
"realrashid/sweet-alert": "^7.2",
|
||||
"rtconner/laravel-tagging": "^5.0",
|
||||
@@ -35,18 +35,12 @@
|
||||
"fakerphp/faker": "^1.24.0",
|
||||
"laravel/breeze": "^2.3",
|
||||
"laravel/pint": "^1.18",
|
||||
"laravel/sail": "^1.38",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^8.1",
|
||||
"phpunit/phpunit": "^11.4",
|
||||
"spatie/laravel-ignition": "^2.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/renatokira/comments.git"
|
||||
}
|
||||
],
|
||||
"repositories": [],
|
||||
"autoload": {
|
||||
"exclude-from-classmap": [],
|
||||
"psr-4": {
|
||||
|
||||
349
composer.lock
generated
349
composer.lock
generated
@@ -4,8 +4,55 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9287e7ef1f943600ac3e2b78bc9cd7c8",
|
||||
"content-hash": "3400111a6254560d548639295422875c",
|
||||
"packages": [
|
||||
{
|
||||
"name": "altcha-org/altcha",
|
||||
"version": "v1.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/altcha-org/altcha-lib-php.git",
|
||||
"reference": "9e9e70c864a9db960d071c77c778be0c9ff1a4d0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/altcha-org/altcha-lib-php/zipball/9e9e70c864a9db960d071c77c778be0c9ff1a4d0",
|
||||
"reference": "9e9e70c864a9db960d071c77c778be0c9ff1a4d0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": ">=8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.72",
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpstan/phpstan-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^11.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"AltchaOrg\\Altcha\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniel Regeci",
|
||||
"email": "536331+ovx@users.noreply.github.com"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/altcha-org/altcha-lib-php/issues",
|
||||
"source": "https://github.com/altcha-org/altcha-lib-php/tree/v1.3.1"
|
||||
},
|
||||
"time": "2025-12-13T10:03:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
"version": "0.14.1",
|
||||
@@ -776,6 +823,82 @@
|
||||
],
|
||||
"time": "2025-12-27T19:43:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "grantholle/laravel-altcha",
|
||||
"version": "2.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/grantholle/laravel-altcha.git",
|
||||
"reference": "c0dcc6d0805e8640d46709e5f8d05c7c65b2687c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/grantholle/laravel-altcha/zipball/c0dcc6d0805e8640d46709e5f8d05c7c65b2687c",
|
||||
"reference": "c0dcc6d0805e8640d46709e5f8d05c7c65b2687c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"altcha-org/altcha": "^1.3.1",
|
||||
"illuminate/contracts": "^10.0|^11.0|^12.0",
|
||||
"php": "^8.2",
|
||||
"spatie/laravel-package-tools": "^1.14.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.0",
|
||||
"nunomaduro/collision": "^8.1.1||^7.10.0",
|
||||
"orchestra/testbench": "^10.0.0||^9.0.0||^8.22.0",
|
||||
"pestphp/pest": "^3.0||^2.0",
|
||||
"pestphp/pest-plugin-arch": "^3.0||^2.0",
|
||||
"pestphp/pest-plugin-laravel": "^3.0||^2.0",
|
||||
"spatie/laravel-ray": "^1.26"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"Altcha": "GrantHolle\\Altcha\\Facades\\Altcha"
|
||||
},
|
||||
"providers": [
|
||||
"GrantHolle\\Altcha\\AltchaServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GrantHolle\\Altcha\\": "src/",
|
||||
"GrantHolle\\Altcha\\Database\\Factories\\": "database/factories/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Grant Holle",
|
||||
"email": "hollegrant@gmail.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A Laravel server implementation for Altcha.",
|
||||
"homepage": "https://github.com/grantholle/laravel-altcha",
|
||||
"keywords": [
|
||||
"Grant Holle",
|
||||
"laravel",
|
||||
"laravel-altcha"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/grantholle/laravel-altcha/issues",
|
||||
"source": "https://github.com/grantholle/laravel-altcha/tree/2.1.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/Grant Holle",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-16T03:39:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "7.10.0",
|
||||
@@ -2913,16 +3036,16 @@
|
||||
},
|
||||
{
|
||||
"name": "livewire/livewire",
|
||||
"version": "v3.7.3",
|
||||
"version": "v3.7.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/livewire/livewire.git",
|
||||
"reference": "a5384df9fbd3eaf02e053bc49aabc8ace293fc1c"
|
||||
"reference": "0dc679eb4c8b4470cb12522b5927ef08ca2358bb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/livewire/livewire/zipball/a5384df9fbd3eaf02e053bc49aabc8ace293fc1c",
|
||||
"reference": "a5384df9fbd3eaf02e053bc49aabc8ace293fc1c",
|
||||
"url": "https://api.github.com/repos/livewire/livewire/zipball/0dc679eb4c8b4470cb12522b5927ef08ca2358bb",
|
||||
"reference": "0dc679eb4c8b4470cb12522b5927ef08ca2358bb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2977,7 +3100,7 @@
|
||||
"description": "A front-end framework for Laravel.",
|
||||
"support": {
|
||||
"issues": "https://github.com/livewire/livewire/issues",
|
||||
"source": "https://github.com/livewire/livewire/tree/v3.7.3"
|
||||
"source": "https://github.com/livewire/livewire/tree/v3.7.10"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2985,7 +3108,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-19T02:00:29+00:00"
|
||||
"time": "2026-02-09T22:49:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maize-tech/laravel-markable",
|
||||
@@ -3216,79 +3339,6 @@
|
||||
},
|
||||
"time": "2025-09-18T10:15:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mews/captcha",
|
||||
"version": "3.4.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mewebstudio/captcha.git",
|
||||
"reference": "2622c4f90dd621f19fe57e03e45f6f099509e839"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mewebstudio/captcha/zipball/2622c4f90dd621f19fe57e03e45f6f099509e839",
|
||||
"reference": "2622c4f90dd621f19fe57e03e45f6f099509e839",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-gd": "*",
|
||||
"illuminate/config": "~5|^6|^7|^8|^9|^10|^11|^12",
|
||||
"illuminate/filesystem": "~5|^6|^7|^8|^9|^10|^11|^12",
|
||||
"illuminate/hashing": "~5|^6|^7|^8|^9|^10|^11|^12",
|
||||
"illuminate/session": "~5|^6|^7|^8|^9|^10|^11|^12",
|
||||
"illuminate/support": "~5|^6|^7|^8|^9|^10|^11|^12",
|
||||
"intervention/image": "^3.7",
|
||||
"php": "^7.2|^8.1|^8.2|^8.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.0",
|
||||
"phpunit/phpunit": "^8.5|^9.5.10|^10.5|^11"
|
||||
},
|
||||
"type": "package",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"Captcha": "Mews\\Captcha\\Facades\\Captcha"
|
||||
},
|
||||
"providers": [
|
||||
"Mews\\Captcha\\CaptchaServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Mews\\Captcha\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Muharrem ERİN",
|
||||
"email": "me@mewebstudio.com",
|
||||
"homepage": "https://github.com/mewebstudio",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Laravel 5/6/7/8/9/10/11/12 Captcha Package",
|
||||
"homepage": "https://github.com/mewebstudio/captcha",
|
||||
"keywords": [
|
||||
"captcha",
|
||||
"laravel12 Captcha",
|
||||
"laravel12 Security",
|
||||
"laravel5 Security"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/mewebstudio/captcha/issues",
|
||||
"source": "https://github.com/mewebstudio/captcha/tree/3.4.7"
|
||||
},
|
||||
"time": "2025-10-11T14:42:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mobiledetect/mobiledetectlib",
|
||||
"version": "4.8.10",
|
||||
@@ -9177,69 +9227,6 @@
|
||||
},
|
||||
"time": "2026-01-05T16:49:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sail",
|
||||
"version": "v1.52.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/sail.git",
|
||||
"reference": "64ac7d8abb2dbcf2b76e61289451bae79066b0b3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/sail/zipball/64ac7d8abb2dbcf2b76e61289451bae79066b0b3",
|
||||
"reference": "64ac7d8abb2dbcf2b76e61289451bae79066b0b3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/console": "^9.52.16|^10.0|^11.0|^12.0",
|
||||
"illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0",
|
||||
"illuminate/support": "^9.52.16|^10.0|^11.0|^12.0",
|
||||
"php": "^8.0",
|
||||
"symfony/console": "^6.0|^7.0",
|
||||
"symfony/yaml": "^6.0|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"orchestra/testbench": "^7.0|^8.0|^9.0|^10.0",
|
||||
"phpstan/phpstan": "^2.0"
|
||||
},
|
||||
"bin": [
|
||||
"bin/sail"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Sail\\SailServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Sail\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Docker files for running a basic Laravel application.",
|
||||
"keywords": [
|
||||
"docker",
|
||||
"laravel"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/sail/issues",
|
||||
"source": "https://github.com/laravel/sail"
|
||||
},
|
||||
"time": "2026-01-01T02:46:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mockery/mockery",
|
||||
"version": "1.6.12",
|
||||
@@ -11537,82 +11524,6 @@
|
||||
],
|
||||
"time": "2024-10-20T05:08:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v7.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "24dd4de28d2e3988b311751ac49e684d783e2345"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345",
|
||||
"reference": "24dd4de28d2e3988b311751ac49e684d783e2345",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-ctype": "^1.8"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/console": "<6.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/console": "^6.4|^7.0|^8.0"
|
||||
},
|
||||
"bin": [
|
||||
"Resources/bin/yaml-lint"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Yaml\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Loads and dumps YAML files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/yaml/tree/v7.4.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-04T18:11:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
"version": "1.3.1",
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'characters' => ['2', '3', '4', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'M', 'N', 'P', 'Q', 'R', 'T', 'U', 'X', 'Y', 'Z'],
|
||||
'default' => [
|
||||
'length' => 5,
|
||||
'width' => 120,
|
||||
'height' => 36,
|
||||
'quality' => 90,
|
||||
'math' => false,
|
||||
'expire' => 60,
|
||||
'encrypt' => false,
|
||||
],
|
||||
'math' => [
|
||||
'length' => 9,
|
||||
'width' => 120,
|
||||
'height' => 36,
|
||||
'quality' => 90,
|
||||
'math' => true,
|
||||
],
|
||||
|
||||
'flat' => [
|
||||
'length' => 6,
|
||||
'width' => 160,
|
||||
'height' => 46,
|
||||
'quality' => 90,
|
||||
'lines' => 6,
|
||||
'bgImage' => false,
|
||||
'bgColor' => '#ecf2f4',
|
||||
'fontColors' => ['#2c3e50', '#c0392b', '#16a085', '#c0392b', '#8e44ad', '#303f9f', '#f57c00', '#795548'],
|
||||
'contrast' => -5,
|
||||
],
|
||||
'mini' => [
|
||||
'length' => 3,
|
||||
'width' => 60,
|
||||
'height' => 32,
|
||||
],
|
||||
'inverse' => [
|
||||
'length' => 5,
|
||||
'width' => 120,
|
||||
'height' => 36,
|
||||
'quality' => 90,
|
||||
'sensitive' => true,
|
||||
'angle' => 12,
|
||||
'sharpen' => 10,
|
||||
'blur' => 2,
|
||||
'invert' => true,
|
||||
'contrast' => -5,
|
||||
]
|
||||
];
|
||||
@@ -6,13 +6,13 @@ return [
|
||||
'guild_id' => 802233383710228550,
|
||||
|
||||
'patreon_roles' => [
|
||||
841798154999169054, // ????
|
||||
803329707650187364, // Tier-5
|
||||
803327903659196416, // ????
|
||||
803325441942356059, // Tier-3
|
||||
803322725576736858, // Tier-2
|
||||
802270568912519198, // Tier-1
|
||||
802234830384267315 // admin
|
||||
'841798154999169054', // ????
|
||||
'803329707650187364', // Tier-5
|
||||
'803327903659196416', // ????
|
||||
'803325441942356059', // Tier-3
|
||||
'803322725576736858', // Tier-2
|
||||
'802270568912519198', // Tier-1
|
||||
'802234830384267315' // admin
|
||||
],
|
||||
|
||||
'discord_bot_token' => env('DISCORD_BOT_TOKEN'),
|
||||
|
||||
@@ -47,5 +47,11 @@ return [
|
||||
'avatar_default_extension' => env('DISCORD_EXTENSION_DEFAULT', 'webp'), // only pick from jpg, png, webp
|
||||
],
|
||||
|
||||
|
||||
/**
|
||||
* Matrix Registration
|
||||
*/
|
||||
'matrix' => [
|
||||
'server' => env('MATRIX_SERVER'),
|
||||
'shared_secret' => env('MATRIX_SHARED_SECRET'),
|
||||
],
|
||||
];
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Migrate supporters
|
||||
DB::table('users')->where('is_patreon', 1)->update([
|
||||
'roles' => DB::raw("JSON_ARRAY('supporter')")
|
||||
]);
|
||||
|
||||
// Migrate banned
|
||||
DB::table('users')->where('is_banned', 1)->update([
|
||||
'roles' => DB::raw("JSON_ARRAY('banned')")
|
||||
]);
|
||||
|
||||
// Migrate admins
|
||||
DB::table('users')->where('is_admin', 1)->update([
|
||||
'roles' => DB::raw("JSON_ARRAY('admin')")
|
||||
]);
|
||||
|
||||
// Drop columns
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('is_admin');
|
||||
$table->dropColumn('is_patreon');
|
||||
$table->dropColumn('is_banned');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('users')->update(['roles' => null]);
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->boolean('is_admin')->default(0);
|
||||
$table->boolean('is_patreon')->default(0);
|
||||
$table->boolean('is_banned')->default(0);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('matrix_id')
|
||||
->nullable()
|
||||
->after('discord_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('matrix_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,74 +0,0 @@
|
||||
services:
|
||||
laravel.test:
|
||||
build:
|
||||
context: './vendor/laravel/sail/runtimes/8.3'
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
WWWGROUP: '${WWWGROUP}'
|
||||
MYSQL_CLIENT: mariadb-client
|
||||
image: 'sail-8.3/app'
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
ports:
|
||||
- '${APP_PORT:-80}:80'
|
||||
- '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
|
||||
environment:
|
||||
WWWUSER: '${WWWUSER}'
|
||||
LARAVEL_SAIL: 1
|
||||
XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
|
||||
XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
|
||||
IGNITION_LOCAL_SITES_PATH: '${PWD}'
|
||||
volumes:
|
||||
- '.:/var/www/html'
|
||||
networks:
|
||||
- sail
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
mariadb:
|
||||
image: 'mariadb:11'
|
||||
ports:
|
||||
- '${FORWARD_DB_PORT:-3306}:3306'
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
|
||||
MYSQL_ROOT_HOST: '%'
|
||||
MYSQL_DATABASE: '${DB_DATABASE}'
|
||||
MYSQL_USER: '${DB_USERNAME}'
|
||||
MYSQL_PASSWORD: '${DB_PASSWORD}'
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
|
||||
volumes:
|
||||
- 'sail-mariadb:/var/lib/mysql'
|
||||
- './vendor/laravel/sail/database/mariadb/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh'
|
||||
networks:
|
||||
- sail
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- healthcheck.sh
|
||||
- '--connect'
|
||||
- '--innodb_initialized'
|
||||
retries: 3
|
||||
timeout: 5s
|
||||
redis:
|
||||
image: 'redis:alpine'
|
||||
ports:
|
||||
- '${FORWARD_REDIS_PORT:-6379}:6379'
|
||||
volumes:
|
||||
- 'sail-redis:/data'
|
||||
networks:
|
||||
- sail
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- redis-cli
|
||||
- ping
|
||||
retries: 3
|
||||
timeout: 5s
|
||||
networks:
|
||||
sail:
|
||||
driver: bridge
|
||||
volumes:
|
||||
sail-mariadb:
|
||||
driver: local
|
||||
sail-redis:
|
||||
driver: local
|
||||
61
package-lock.json
generated
61
package-lock.json
generated
@@ -4,10 +4,12 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hstream",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||
"@jellyfin/libass-wasm": "^4.1.1",
|
||||
"@yaireo/tagify": "^4.21.2",
|
||||
"altcha": "^2.3.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"dashjs": "^5.0.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -40,6 +42,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@altcha/crypto": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@altcha/crypto/-/crypto-0.0.1.tgz",
|
||||
"integrity": "sha512-qZMdnoD3lAyvfSUMNtC2adRi666Pxdcw9zqfMU5qBOaJWqpN9K+eqQGWqeiKDMqL0SF+EytNG4kR/Pr/99GJ6g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
||||
@@ -1161,6 +1169,31 @@
|
||||
"@vue/reactivity": "~3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/altcha": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/altcha/-/altcha-2.3.0.tgz",
|
||||
"integrity": "sha512-vl8I0dQvSQB7/Mx09XuWZ1+LdSP7vEda6OLbg9kUQ2ZO2LT7MzgUyLK7Iips+GAV6c0ntVcS1XWOqhEPpwbDhQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@altcha/crypto": "^0.0.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-linux-x64-gnu": "4.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/altcha/node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz",
|
||||
"integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/any-promise": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||
@@ -1231,14 +1264,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -3064,6 +3097,15 @@
|
||||
"postcss": "^8.0.9"
|
||||
}
|
||||
},
|
||||
"node_modules/tw-elements/node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/ua-parser-js": {
|
||||
"version": "1.0.41",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz",
|
||||
@@ -3302,15 +3344,6 @@
|
||||
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||
"@jellyfin/libass-wasm": "^4.1.1",
|
||||
"@yaireo/tagify": "^4.21.2",
|
||||
"altcha": "^2.3.0",
|
||||
"chart.js": "^4.5.0",
|
||||
"dashjs": "^5.0.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
|
||||
@@ -123,3 +123,32 @@ input:checked~.dot {
|
||||
src: url(https://fonts.bunny.net/figtree/files/figtree-latin-ext-600-normal.woff2) format('woff2'), url(https://fonts.bunny.net/figtree/files/figtree-latin-ext-600-normal.woff) format('woff');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
/* Captcha */
|
||||
:root {
|
||||
--altcha-border-width: 1px;
|
||||
--altcha-border-radius: 0.375rem;
|
||||
--altcha-color-base: #333;
|
||||
--altcha-color-border: #a0a0a0;
|
||||
--altcha-color-text: #fff;
|
||||
--altcha-color-border-focus: currentColor;
|
||||
--altcha-color-error-text: #f23939;
|
||||
--altcha-color-footer-bg: #141414;
|
||||
--altcha-max-width: 260px;
|
||||
}
|
||||
|
||||
.altcha-footer {
|
||||
border-bottom-left-radius: 0.375rem;
|
||||
border-bottom-right-radius: 0.375rem;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
background-color: #ffffff;
|
||||
border-color: #a0a0a0;
|
||||
color: rgb(225,29,72);
|
||||
}
|
||||
|
||||
input[type="checkbox"]:checked {
|
||||
background-color: rgb(225,29,72);
|
||||
box-shadow: 0 0 0 0px #fff, 0 0 0 calc(2px + 0px) rgba(246, 59, 118, 0.5), 0 0 #0000;
|
||||
}
|
||||
@@ -12,6 +12,9 @@ import {
|
||||
initTE,
|
||||
} from "tw-elements";
|
||||
|
||||
// Captcha
|
||||
import 'altcha';
|
||||
|
||||
// import Alpine from 'alpinejs';
|
||||
|
||||
// window.Alpine = Alpine;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="mb-4 rounded-lg bg-success-400 px-6 py-5 text-base text-success-800 mt-5" role="alert">
|
||||
{{ $alert->text }}
|
||||
@auth
|
||||
@if(Auth::user()->is_admin)
|
||||
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<form method="POST" action="{{ route('admin.alert.delete', $alert->id) }}" class="float-right hover:text-success-900">
|
||||
@csrf
|
||||
@method('delete')
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="mb-4 rounded-lg bg-danger-400 px-6 py-5 text-base text-danger-800 mt-5" role="alert">
|
||||
{{ $alert->text }}
|
||||
@auth
|
||||
@if(Auth::user()->is_admin)
|
||||
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<form method="POST" action="{{ route('admin.alert.delete', $alert->id) }}" class="float-right hover:text-danger-900">
|
||||
@csrf
|
||||
@method('delete')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@auth
|
||||
@if(Auth::user()->is_admin)
|
||||
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<div class="relative p-5 bg-white dark:bg-neutral-700/40 rounded-lg overflow-hidden z-10">
|
||||
<div class="float-left">
|
||||
<a data-te-toggle="modal" data-te-target="#modalUploadEpisode" class="text-xl text-gray-800 dark:text-gray-200 leading-tight cursor-pointer whitespace-nowrap">
|
||||
|
||||
@@ -69,6 +69,11 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<altcha-widget id="captcha" floating challengeurl="/altcha-challenge"></altcha-widget>
|
||||
<x-input-error :messages="$errors->get('altcha')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
@if (Route::has('password.request'))
|
||||
<a class="underline text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-neutral-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500 dark:focus:ring-offset-neutral-800" href="{{ route('password.request') }}">
|
||||
@@ -127,6 +132,11 @@
|
||||
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<altcha-widget id="captcha" floating challengeurl="/altcha-challenge"></altcha-widget>
|
||||
<x-input-error :messages="$errors->get('altcha')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-primary-button class="ms-4">
|
||||
{{ __('Register') }}
|
||||
|
||||
@@ -33,19 +33,7 @@
|
||||
<x-input-error class="mt-2" :messages="$errors->get('message')" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-input-label for="message" :value="__('Captcha')" />
|
||||
<div class="flex pt-2">
|
||||
<div id="captchaImg">
|
||||
{!! captcha_img() !!}
|
||||
</div>
|
||||
<button type="button" class="inline-flex items-center ml-2 px-2 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-500 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-25 transition ease-in-out duration-150" id="reloadcaptcha">
|
||||
<i class="fa-solid fa-rotate-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
<br>
|
||||
<x-text-input id="captcha" class="block " type="text" name="captcha" required />
|
||||
</div>
|
||||
<altcha-widget id="captcha" floating challengeurl="/altcha-challenge"></altcha-widget>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<x-primary-button>{{ __('Submit') }}</x-primary-button>
|
||||
@@ -65,18 +53,4 @@
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function reloadCaptcha() {
|
||||
window.axios.get('/reload-captcha').then(function(response) {
|
||||
if (response.status == 200) {
|
||||
document.querySelector("#captchaImg").innerHTML = response.data.captcha;
|
||||
}
|
||||
}).catch(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelector("#reloadcaptcha").addEventListener("click", reloadCaptcha);
|
||||
</script>
|
||||
</section>
|
||||
@@ -49,6 +49,22 @@
|
||||
<i class="fa-brands fa-discord"></i> {{ __('nav.our-discord-server') }}
|
||||
</x-dropdown-link>
|
||||
|
||||
<x-dropdown-link :href="route('join.matrix')">
|
||||
<div class="flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="icon icon-tabler icons-tabler-outline icon-tabler-brand-matrix pr-1">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 3h-1v18h1" />
|
||||
<path d="M20 21h1v-18h-1" />
|
||||
<path d="M7 9v6" />
|
||||
<path d="M12 15v-3.5a2.5 2.5 0 1 0 -5 0v.5" />
|
||||
<path d="M17 15v-3.5a2.5 2.5 0 1 0 -5 0v.5" />
|
||||
</svg>
|
||||
Join our Matrix
|
||||
</div>
|
||||
</x-dropdown-link>
|
||||
|
||||
<x-dropdown-link>
|
||||
<div class="grid grid-cols-2">
|
||||
<p class="cursor-default">{{ __('nav.theme') }}</p>
|
||||
@@ -163,7 +179,7 @@
|
||||
<i class="fa-solid fa-gear"></i> {{ __('nav.settings') }}
|
||||
</x-dropdown-link>
|
||||
|
||||
@if (Auth::user()->is_admin)
|
||||
@if (Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<x-dropdown-link href="{{ route('admin.upload.index') }}">
|
||||
<i class="fa-solid fa-user-tie"></i> Admin
|
||||
</x-dropdown-link>
|
||||
|
||||
@@ -6,12 +6,16 @@
|
||||
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-pink-700 dark:text-neutral-200 ">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3">
|
||||
Discord-ID
|
||||
ID
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">
|
||||
Discord ID
|
||||
<input
|
||||
class="w-4 h-4 ml-2 text-rose-600 bg-gray-100 border-gray-300 rounded focus:ring-rose-500 dark:focus:ring-rose-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
||||
type="checkbox"
|
||||
wire:model.live="filtered"
|
||||
value="true"
|
||||
wire:model.live.debounce.600ms="discordId"
|
||||
type="search"
|
||||
id="discord-search"
|
||||
class="ml-2 w-32 h-7 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-rose-800 focus:border-rose-900 dark:bg-neutral-900 dark:border-neutral-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-rose-800 dark:focus:border-rose-900"
|
||||
placeholder="Search..."
|
||||
>
|
||||
</th>
|
||||
<th scope="col" class="px-6 py-3">
|
||||
@@ -59,14 +63,17 @@
|
||||
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{{ $user->id }}
|
||||
</th>
|
||||
<td class="px-6 py-4">
|
||||
{{ $user->discord_id ?? 'n/a' }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
{{ $user->name }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
{{ $user->is_patreon ? 'Yes' : 'No' }}
|
||||
{{ $user->hasRole(\App\Enums\UserRole::SUPPORTER) ? 'Yes' : 'No' }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
{{ $user->is_banned ? 'Yes' : 'No' }}
|
||||
{{ $user->hasRole(\App\Enums\UserRole::BANNED) ? 'Yes' : 'No' }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
{{ $user->created_at->format('Y-m-d') }}
|
||||
@@ -78,9 +85,9 @@
|
||||
<form method="POST" action="{{ route('admin.user.update') }}">
|
||||
@csrf
|
||||
<input type="hidden" value="{{ $user->id }}" name="id">
|
||||
<input type="hidden" value="{{ $user->is_banned ? 'unban' : 'ban' }}" name="action">
|
||||
<input type="hidden" value="{{ $user->hasRole(\App\Enums\UserRole::BANNED) ? 'unban' : 'ban' }}" name="action">
|
||||
<button type="submit" class="inline-block w-full rounded bg-rose-600 pl-[4px] pr-[4px] p-[1px] text-xs font-medium uppercase leading-normal text-white transition duration-150 ease-in-out hover:bg-rose-700 focus:bg-rose-600">
|
||||
{{ $user->is_banned ? 'Unban' : 'Ban' }}
|
||||
{{ $user->hasRole(\App\Enums\UserRole::BANNED) ? 'Unban' : 'Ban' }}
|
||||
</button>
|
||||
</form>
|
||||
<button wire:click="deleteUserComments('{{ $user->id }}')" class="inline-block w-full rounded bg-red-600 pl-[4px] pr-[4px] p-[1px] text-xs font-medium uppercase leading-normal text-white transition duration-150 ease-in-out hover:bg-rose-700 focus:bg-rose-600">
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<div class="flex-grow">
|
||||
<div class="flex gap-2">
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">{{ $comment->user->name }}</p>
|
||||
@if($comment->user->is_admin)
|
||||
@if($comment->user->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<a data-te-toggle="tooltip" title="Admin"><i class="fa-solid fa-crown text-yellow-600"></i></a>
|
||||
@endif
|
||||
@if($comment->user->is_patreon)
|
||||
@if($comment->user->hasRole(\App\Enums\UserRole::SUPPORTER))
|
||||
<a data-te-toggle="tooltip" title="Badge of appreciation for the horny people supporting us! :3"><i class="fa-solid fa-hand-holding-heart text-rose-600"></i></a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
@else
|
||||
<p class="text-lg">Episode {{ $episodeNumber }}</p>
|
||||
@endif
|
||||
<p class="text-xs">HEVC MKV {{ $fileSize ?? '' }}</p>
|
||||
<p class="text-xs">{{ $fileExtension }} MKV {{ $fileSize ?? '' }}</p>
|
||||
<p class="text-xs" id="count-{{ $downloadId }}">Downloaded {{ $downloadCount }} times</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -235,6 +235,11 @@
|
||||
'/' .
|
||||
$expire;
|
||||
}
|
||||
|
||||
$fileExtension = "HEVC";
|
||||
if (str_contains($download->url, 'AV1')) {
|
||||
$fileExtension = "AV1";
|
||||
}
|
||||
@endphp
|
||||
<a href="{{ $downloadURL }}" wire:click="clicked({{ $download->id }})"
|
||||
download>
|
||||
@@ -249,7 +254,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex flex-col text-center w-full">
|
||||
<p class="text-xs">HEVC MKV</p>
|
||||
<p class="text-xs">{{ $fileExtension }} MKV</p>
|
||||
</div>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
<a href="{{ $dldomains[array_rand($dldomains)] }}/{{ $hdl->getDownloadByType('FHD')->url }}">
|
||||
@php
|
||||
$fileExtension = "HEVC";
|
||||
if (str_contains($hdl->getDownloadByType('FHD')->url, 'AV1')) {
|
||||
$fileExtension = "AV1";
|
||||
}
|
||||
@endphp
|
||||
<button class="group rounded-md shadow bg-rose-600 text-white cursor-pointer flex justify-between items-center overflow-hidden transition-all hover:glow m-1 w-[150px]">
|
||||
<div class="relative w-12 h-12 bg-white bg-opacity-20 text-white flex justify-center items-center transition-all"><svg id="arrow" class="w-4 h-4 transition-all group-hover:-translate-y-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
|
||||
@@ -7,7 +13,7 @@
|
||||
</div>
|
||||
<div class="w-32 text-center">
|
||||
<p class="px-5 text-sm row-span-2">Episode {{ $hdl->episode }}</p>
|
||||
<p class="px-5 text-xs">HEVC MKV</p>
|
||||
<p class="px-5 text-xs">{{ $fileExtension }} MKV</p>
|
||||
</div>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
169
resources/views/matrix/index.blade.php
Normal file
169
resources/views/matrix/index.blade.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<x-app-layout>
|
||||
<div class="min-h-screen">
|
||||
<div class="max-w-5xl mx-auto py-16 px-6">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
We're Moving to Matrix 🚀
|
||||
</h1>
|
||||
<p class="text-lg text-gray-600 dark:text-neutral-200 max-w-3xl mx-auto">
|
||||
Due to recent changes with Discord, we are transitioning our community to
|
||||
<span class="font-semibold text-indigo-600">Matrix</span> —
|
||||
an open, decentralized communication network.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Why Matrix -->
|
||||
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm p-8 mb-8">
|
||||
<h2 class="text-2xl dark:text-white font-semibold mb-4">What is Matrix?</h2>
|
||||
|
||||
<p class="text-gray-700 dark:text-neutral-300 mb-4">
|
||||
Matrix is an open-source messaging system. Unlike Discord, it is
|
||||
<strong>decentralized</strong>. That means no single company controls it.
|
||||
</p>
|
||||
|
||||
<ul class="list-disc pl-6 text-gray-700 dark:text-neutral-300 space-y-2">
|
||||
<li>You can choose which server (called a “homeserver”) you register on.</li>
|
||||
<li>All servers communicate with each other (this is called federation).</li>
|
||||
<li>You are <strong>not required</strong> to use our server to join our rooms.</li>
|
||||
</ul>
|
||||
|
||||
<div class="mt-6 p-4 bg-pink-50 dark:bg-pink-950 border border-pink-100 dark:border-pink-700 rounded-lg">
|
||||
<p class="text-pink-500 text-sm">
|
||||
Example: You can register at a different server like matrix.org and still join our rooms.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Our Server -->
|
||||
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm p-8 mb-8">
|
||||
<h2 class="text-2xl dark:text-white font-semibold mb-4">Our Matrix Server</h2>
|
||||
|
||||
<p class="text-gray-700 dark:text-neutral-300 mb-4">
|
||||
We provide our own Matrix homeserver for community members.
|
||||
</p>
|
||||
|
||||
<ul class="list-disc pl-6 text-gray-700 dark:text-neutral-300 space-y-2 mb-6">
|
||||
<li>Available to users registered for more than 1 month</li>
|
||||
<li>Fully federated with the entire Matrix network</li>
|
||||
<li>No obligation to use it - it’s optional</li>
|
||||
<li>Anonymized IP addresses to protect your privacy</li>
|
||||
</ul>
|
||||
|
||||
@auth
|
||||
@if(auth()->user()->created_at->lt(now()->subMonth()))
|
||||
@if(auth()->user()->matrix_id)
|
||||
<div class="bg-green-50 dark:bg-green-950 dark:border-green-700 border border-green-200 p-6 rounded-xl">
|
||||
<h3 class="font-semibold text-green-800 dark:text-green-300 mb-2">
|
||||
✅ You are registered!
|
||||
</h3>
|
||||
<p class="text-green-700 dark:text-green-400 mb-4">
|
||||
Your Matrix account has been created successfully.
|
||||
</p>
|
||||
<p class="text-green-700 dark:text-green-400 mb-4">
|
||||
Make sure to store your password, as we don't have a password reset function!
|
||||
</p>
|
||||
<p class="text-green-700 dark:text-green-400 mb-4">
|
||||
You can now log in using any Matrix client of your choice.
|
||||
</p>
|
||||
<p class="text-green-700 dark:text-green-400 mb-4">
|
||||
For the best experience, we recommend:
|
||||
</p>
|
||||
<ul class="list-disc text-green-700 dark:text-green-400 pl-6 space-y-2 mb-6">
|
||||
<li>Downloading the official <strong><a href="https://element.io/download" target="_blank" class="underline">Element</a></strong> app for desktop or mobile</li>
|
||||
<li>Or using our web client via <strong><a href="https://element.hstream.moe/" target="_blank" class="underline">Element Web</a></strong></li>
|
||||
</ul>
|
||||
<p class="text-green-700 dark:text-green-400 mb-4">
|
||||
Simply sign in with your new username (<code>{{ $user->matrix_id }}</code>) and password to get started.
|
||||
</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="bg-green-50 dark:bg-green-950 dark:border-green-700 border border-green-200 p-6 rounded-xl">
|
||||
<h3 class="font-semibold text-green-800 dark:text-green-300 mb-2">
|
||||
🎉 You are eligible!
|
||||
</h3>
|
||||
<p class="text-green-700 dark:text-green-400 mb-2">
|
||||
Your account is older than one month. You can create your account now.
|
||||
</p>
|
||||
|
||||
<div class="p-4 bg-red-100 dark:bg-red-950 border border-red-100 dark:border-red-700 rounded-lg mb-4">
|
||||
<p class="text-red-500 text-sm font-bold">
|
||||
Important: Save your password! We don't have a password reset function yet!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@include('matrix.register')
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900 border border-yellow-200 dark:border-yellow-700 p-6 rounded-xl">
|
||||
<h3 class="font-semibold text-yellow-800 dark:text-yellow-300 mb-2">
|
||||
⏳ Not Yet Eligible
|
||||
</h3>
|
||||
<p class="text-yellow-700 dark:text-yellow-400">
|
||||
Your account must be at least one month old to register
|
||||
on our Matrix server.
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="bg-gray-100 dark:bg-neutral-700 p-6 rounded-xl text-center">
|
||||
<p class="text-gray-700 dark:text-gray-100">
|
||||
Please log in to check if you're eligible for our Matrix server.
|
||||
</p>
|
||||
</div>
|
||||
@endauth
|
||||
</div>
|
||||
|
||||
<!-- Our Space -->
|
||||
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm p-8 mb-8">
|
||||
<h2 class="text-2xl dark:text-white font-semibold mb-4">Our Matrix Space</h2>
|
||||
|
||||
<p class="text-gray-700 dark:text-neutral-300 mb-4">
|
||||
All of our rooms are organized inside our main Matrix Space:
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-100 dark:bg-neutral-700 dark:text-neutral-300 p-4 rounded-lg font-mono text-sm break-all">
|
||||
<a href="https://matrix.to/#/#hstream:hstream.moe" target="_blank">https://matrix.to/#/#hstream:hstream.moe</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Room List -->
|
||||
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm p-8 mb-8">
|
||||
<h2 class="text-2xl dark:text-white font-semibold mb-6">Our Matrix Rooms</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
@foreach($rooms as $room)
|
||||
<div class="border dark:border-neutral-900 rounded-xl p-6 hover:shadow-md dark:bg-neutral-800 hover:dark:bg-neutral-900/60 transition">
|
||||
<h3 class="font-semibold dark:text-neutral-100 text-lg mb-2">
|
||||
{{ $room['name'] }}
|
||||
</h3>
|
||||
|
||||
<p class="text-gray-600 dark:text-neutral-300 text-sm mb-4">
|
||||
{{ $room['description'] }}
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-100 dark:bg-neutral-700 dark:text-neutral-300 p-3 rounded text-xs font-mono break-all">
|
||||
<a href="{{ $room['alias'] }}" target="_blank">{{ $room['alias'] }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Guide -->
|
||||
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm p-8">
|
||||
<h2 class="text-2xl dark:text-white font-semibold mb-4">Matrix Guide</h2>
|
||||
|
||||
<p class="text-gray-700 dark:text-neutral-300 mb-4">
|
||||
Matrix and Element can be overwhelming for new users, so here is a guide from Element:
|
||||
</p>
|
||||
|
||||
<ul class="list-disc pl-6 text-gray-700 dark:text-neutral-300 space-y-2 mb-6">
|
||||
<li>Element Guide: <a class="font-bold text-green-800 dark:text-green-300 underline" referrerPolicy="no-referrer" href="https://static.element.io/pdfs/element-user-guide.pdf" target="_blank">element-user-guide.pdf</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
31
resources/views/matrix/register.blade.php
Normal file
31
resources/views/matrix/register.blade.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<form method="POST" action="{{ route('join.matrix.create') }}">
|
||||
@csrf
|
||||
<div>
|
||||
<x-input-label for="username" :value="__('Username')" />
|
||||
<x-text-input id="username" class="block mt-1 w-full" type="text" name="username" :value="old('username')" required autofocus autocomplete="username" />
|
||||
<x-input-error :messages="$errors->get('username')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
<x-text-input id="password" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password"
|
||||
required/>
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||
<x-text-input id="password_confirmation" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password_confirmation"
|
||||
required />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-primary-button class="ms-4">
|
||||
{{ __('Create Matrix Account') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -52,7 +52,19 @@
|
||||
<br>
|
||||
@php $download = $episode->getDownloadByType('UHD'); @endphp
|
||||
@isset($download)
|
||||
@if (!Auth::user()->is_patreon)
|
||||
@if (Auth::user()->hasRole(\App\Enums\UserRole::SUPPORTER) || Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<p class="font-bold text-gray-800 dark:text-gray-200">
|
||||
<i class="fa-solid fa-lock-open pr-[4px] text-green-400"></i> 4k
|
||||
</p>
|
||||
|
||||
@php $dlpdomains = config('hstream.download_domain_4k'); @endphp
|
||||
|
||||
<div class="flex flex-wrap justify-around">
|
||||
@foreach ($dlList as $hdl)
|
||||
@include('modals.partials.download-button-patreon')
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
@if (config('hstream.free_downloads'))
|
||||
<p class="font-bold text-gray-800 dark:text-gray-200">
|
||||
<i class="fa-solid fa-lock-open pr-[4px] text-yellow-600"></i> 4k
|
||||
@@ -67,18 +79,6 @@
|
||||
on our Discord server, you have to logout and login again.
|
||||
</p>
|
||||
@endif
|
||||
@else
|
||||
<p class="font-bold text-gray-800 dark:text-gray-200">
|
||||
<i class="fa-solid fa-lock-open pr-[4px] text-green-400"></i> 4k
|
||||
</p>
|
||||
|
||||
@php $dlpdomains = config('hstream.download_domain_4k'); @endphp
|
||||
|
||||
<div class="flex flex-wrap justify-around">
|
||||
@foreach ($dlList as $hdl)
|
||||
@include('modals.partials.download-button-patreon')
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<p class="text-gray-800 dark:text-gray-200 font-bold">
|
||||
@@ -91,7 +91,19 @@
|
||||
|
||||
<br>
|
||||
@if ($episode->interpolated_uhd)
|
||||
@if (!Auth::user()->is_patreon)
|
||||
@if (Auth::user()->hasRole(\App\Enums\UserRole::SUPPORTER) || Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR) )
|
||||
<p class="font-bold text-gray-800 dark:text-gray-200">
|
||||
<i class="fa-solid fa-lock-open pr-[4px] text-green-400"></i> 4k 48fps
|
||||
</p>
|
||||
|
||||
@php $dlpdomains = config('hstream.download_domain_4k'); @endphp
|
||||
|
||||
<div class="flex flex-wrap justify-around">
|
||||
@foreach ($dlList as $hdl)
|
||||
@include('modals.partials.download-button-patreon-interpolated')
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
@if (config('hstream.free_downloads'))
|
||||
<p class="font-bold text-gray-800 dark:text-gray-200">
|
||||
<i class="fa-solid fa-lock-open pr-[4px] text-yellow-600"></i> 4k 48fps
|
||||
@@ -106,18 +118,6 @@
|
||||
on our Discord server, you have to logout and login again.
|
||||
</p>
|
||||
@endif
|
||||
@else
|
||||
<p class="font-bold text-gray-800 dark:text-gray-200">
|
||||
<i class="fa-solid fa-lock-open pr-[4px] text-green-400"></i> 4k 48fps
|
||||
</p>
|
||||
|
||||
@php $dlpdomains = config('hstream.download_domain_4k'); @endphp
|
||||
|
||||
<div class="flex flex-wrap justify-around">
|
||||
@foreach ($dlList as $hdl)
|
||||
@include('modals.partials.download-button-patreon-interpolated')
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
|
||||
@@ -8,18 +8,7 @@
|
||||
<p id="message" class="text-red-600">
|
||||
</p>
|
||||
<div class="flex pt-2">
|
||||
<div id="captchaImg">
|
||||
{!! captcha_img() !!}
|
||||
</div>
|
||||
<button type="button" class="inline-flex items-center ml-2 px-2 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-500 rounded-md font-semibold text-xs text-gray-700 dark:text-gray-300 uppercase tracking-widest shadow-sm hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 disabled:opacity-25 transition ease-in-out duration-150" id="reloadcaptcha" >
|
||||
<i class="fa-solid fa-rotate-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex pt-2 mt-1">
|
||||
<x-text-input id="captcha_text" class="block " type="text" name="captcha_text"/>
|
||||
<button type="button" class="inline-flex items-center ml-2 px-2 -pt-1 bg-rose-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-rose-700 active:bg-rose-900 focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150" id="submitcaptcha" >
|
||||
Submit
|
||||
</button>
|
||||
<altcha-widget id="altcha" challengeurl="/altcha-challenge"></altcha-widget>
|
||||
</div>
|
||||
<br>
|
||||
<p class="text-gray-800 dark:text-gray-200 text-sm">
|
||||
@@ -39,9 +28,15 @@
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path>
|
||||
</svg>
|
||||
</div>
|
||||
@php
|
||||
$fileExtension = "HEVC";
|
||||
if (str_contains($episode->getDownloadByType('FHD')->url, 'AV1')) {
|
||||
$fileExtension = "AV1";
|
||||
}
|
||||
@endphp
|
||||
<div class="flex flex-col text-center w-full">
|
||||
<p class="text-xl">Episode {{ $episode->episode }}</p>
|
||||
<p class="text-xs">HEVC MKV {{ $episode->getDownloadByType('FHD')->getFileSize() ?? '' }}</p>
|
||||
<p class="text-xs">{{ $fileExtension }} MKV {{ $episode->getDownloadByType('FHD')->getFileSize() ?? '' }}</p>
|
||||
<p class="text-xs" id="downloadCount">Downloaded {{ $episode->getDownloadByType('FHD')->count }} times</p>
|
||||
</div>
|
||||
</button>
|
||||
@@ -51,21 +46,12 @@
|
||||
|
||||
<script>
|
||||
var downloadCounter = 0;
|
||||
function reloadCaptcha() {
|
||||
window.axios.get('/reload-captcha').then(function (response) {
|
||||
if (response.status == 200) {
|
||||
document.querySelector("#captchaImg").innerHTML = response.data.captcha;
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
function submitCaptcha() {
|
||||
function submitCaptcha(captchaToken) {
|
||||
document.querySelector("#message").innerHTML = '';
|
||||
window.axios.post('/get-download', {
|
||||
captcha: document.getElementById('captcha_text').value,
|
||||
episode_id: document.getElementById('e_id').value
|
||||
episode_id: document.getElementById('e_id').value,
|
||||
captcha: captchaToken,
|
||||
}).then(function (response) {
|
||||
document.querySelector("#captcharequired").style.display = "none";
|
||||
document.querySelector("#captchsolved").style.display = "block";
|
||||
@@ -89,6 +75,16 @@
|
||||
|
||||
document.querySelector("#downloadEpisode").addEventListener("click", increaseDownloadCounter);
|
||||
|
||||
document.querySelector("#reloadcaptcha").addEventListener("click", reloadCaptcha);
|
||||
document.querySelector("#submitcaptcha").addEventListener("click", submitCaptcha);
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const altcha = document.querySelector("#altcha");
|
||||
|
||||
altcha.addEventListener("statechange", (ev) => {
|
||||
if (ev.detail.state === "verified") {
|
||||
submitCaptcha(ev.detail.payload);
|
||||
|
||||
// Remove captcha from DOM
|
||||
altcha.remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
<div class="flex-grow">
|
||||
<div class="flex gap-2">
|
||||
<p class="font-medium text-gray-900 dark:text-gray-100">{{ $comment->user->name }}</p>
|
||||
@if($comment->user->is_admin)
|
||||
@if($comment->user->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<a data-te-toggle="tooltip" title="Admin"><i class="fa-solid fa-crown text-yellow-600"></i></a>
|
||||
@endif
|
||||
@if($comment->user->is_patreon)
|
||||
@if($comment->user->hasRole(\App\Enums\UserRole::SUPPORTER))
|
||||
<a data-te-toggle="tooltip" title="Badge of appreciation for the horny people supporting us! :3"><i class="fa-solid fa-hand-holding-heart text-rose-600"></i></a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
@include('modals.share')
|
||||
|
||||
@auth
|
||||
@if(Auth::user()->is_admin)
|
||||
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
@include('admin.modals.upload-episode')
|
||||
@include('admin.modals.add-subtitles')
|
||||
@include('admin.modals.edit-episode')
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="flex flex-col py-5 pl-24">
|
||||
<strong class="text-slate-900 text-xl font-bold dark:text-slate-200">
|
||||
{{ $user->name }}
|
||||
@if ($user->is_patreon)
|
||||
@if ($user->hasRole(\App\Enums\UserRole::SUPPORTER))
|
||||
<a data-te-toggle="tooltip" title="Badge of appreciation for the horny people supporting us! :3"><i
|
||||
class="fa-solid fa-hand-holding-heart text-rose-600 animate-pulse"></i></a>
|
||||
@endif
|
||||
|
||||
62
routes/admin.php
Normal file
62
routes/admin.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Admin\AlertController;
|
||||
use App\Http\Controllers\Admin\ContactController;
|
||||
use App\Http\Controllers\Admin\CommentsController;
|
||||
use App\Http\Controllers\Admin\EpisodeController;
|
||||
use App\Http\Controllers\Admin\ReleaseController;
|
||||
use App\Http\Controllers\Admin\UserController;
|
||||
use App\Http\Controllers\Admin\SubtitleController;
|
||||
use App\Http\Controllers\Admin\SiteBackgroundController;
|
||||
use App\Http\Controllers\Api\AdminApiController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------------
|
||||
| Admin Routes
|
||||
|---------------------------------------------------------------------------------
|
||||
*/
|
||||
Route::group(['middleware' => ['auth', 'auth.admin']], function () {
|
||||
// Site alerts
|
||||
Route::get('/admin/alert', [AlertController::class, 'index'])->name('admin.alert.index');
|
||||
Route::post('/admin/alert', [AlertController::class, 'store'])->name('admin.alert.create');
|
||||
Route::delete('/admin/alert/{alert_id}', [AlertController::class, 'delete'])->name('admin.alert.delete');
|
||||
|
||||
// Users
|
||||
Route::get('/admin/users', [UserController::class, 'index'])->name('admin.user.index');
|
||||
Route::post('/admin/users', [UserController::class, 'update'])->name('admin.user.update');
|
||||
|
||||
// Comments
|
||||
Route::get('/admin/comments', [CommentsController::class, 'index'])->name('admin.comments.index');
|
||||
|
||||
// Contact page overview
|
||||
Route::get('/admin/contact', [ContactController::class, 'index'])->name('admin.contact.index');
|
||||
Route::delete('/admin/contact/{contact_id}', [ContactController::class, 'delete'])->name('admin.contact.delete');
|
||||
|
||||
// Site background settings
|
||||
Route::get('/admin/background', [SiteBackgroundController::class, 'index'])->name('admin.background.index');
|
||||
Route::post('/admin/background', [SiteBackgroundController::class, 'create'])->name('admin.background.create');
|
||||
Route::put('/admin/background', [SiteBackgroundController::class, 'update'])->name('admin.background.update');
|
||||
Route::delete('/admin/background', [SiteBackgroundController::class, 'delete'])->name('admin.background.delete');
|
||||
|
||||
// Release
|
||||
Route::get('/admin/release', [ReleaseController::class, 'index'])->name('admin.upload.index');
|
||||
Route::post('/admin/release/upload', [ReleaseController::class, 'store'])->name('admin.upload');
|
||||
|
||||
// Episode
|
||||
Route::post('/admin/episode/upload', [EpisodeController::class, 'store'])->name('admin.upload.episode');
|
||||
Route::post('/admin/episode/edit', [EpisodeController::class, 'update'])->name('admin.edit');
|
||||
|
||||
// Get Tags used for Upload Form
|
||||
Route::get('/admin/tags', [AdminApiController::class, 'getTags'])->name('admin.tags');
|
||||
Route::get('/admin/studios', [AdminApiController::class, 'getStudios'])->name('admin.studios');
|
||||
|
||||
// Get Tags for editing Episode
|
||||
Route::get('/admin/tags/{episode_id}', [AdminApiController::class, 'getEpisodeTags'])->name('admin.tags.episode');
|
||||
Route::get('/admin/studio/{episode_id}', [AdminApiController::class, 'getEpisodeStudio'])->name('admin.studio.episode');
|
||||
|
||||
// Subtitles
|
||||
Route::get('/admin/subtitles/{episode_id}', [AdminApiController::class, 'getSubtitles'])->name('admin.subtitles');
|
||||
Route::post('/admin/add-new-subtitle', [SubtitleController::class, 'store'])->name('admin.add.new.subtitle');
|
||||
Route::post('/admin/update-subtitles', [SubtitleController::class, 'update'])->name('admin.update.subtitles');
|
||||
});
|
||||
52
routes/user.php
Normal file
52
routes/user.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\HomeController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\PlaylistController;
|
||||
use App\Http\Controllers\Api\UserApiController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------------
|
||||
| User Routes
|
||||
|---------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// Matrix
|
||||
Route::get('/join-matrix', [App\Http\Controllers\MatrixController::class, 'index'])->name('join.matrix');
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
// Matrix
|
||||
Route::post('/join-matrix', [App\Http\Controllers\MatrixController::class, 'store'])->name('join.matrix.create');
|
||||
|
||||
Route::get('/user/profile', [ProfileController::class, 'index'])->name('profile.show');
|
||||
Route::get('/user/comments', [ProfileController::class, 'comments'])->name('profile.comments');
|
||||
Route::get('/user/likes', [ProfileController::class, 'likes'])->name('profile.likes');
|
||||
Route::get('/user/watched', [ProfileController::class, 'watched'])->name('user.watched');
|
||||
|
||||
// Notifications
|
||||
Route::get('/user/notifications', [App\Http\Controllers\NotificationController::class, 'index'])->name('profile.notifications');
|
||||
Route::delete('/user/notifications', [App\Http\Controllers\NotificationController::class, 'delete'])->name('profile.notifications.delete');
|
||||
|
||||
// User Profile Actions
|
||||
Route::get('/user/settings', [ProfileController::class, 'settings'])->name('profile.settings');
|
||||
Route::patch('/user/settings', [ProfileController::class, 'update'])->name('profile.update');
|
||||
Route::delete('/user/delete', [ProfileController::class, 'destroy'])->name('profile.delete');
|
||||
Route::post('/user/settings', [ProfileController::class, 'saveSettings'])->name('profile.settings.save');
|
||||
Route::get('/user/blacklist', [UserApiController::class, 'getBlacklist'])->name('profile.blacklist');
|
||||
Route::post('/user/blacklist', [ProfileController::class, 'saveBlacklist'])->name('profile.blacklist.save');
|
||||
|
||||
// Playlist Routes for User Page
|
||||
Route::get('/user/playlists', [PlaylistController::class, 'playlists'])->name('profile.playlists');
|
||||
Route::get('/user/playlist/{playlist_id}', [PlaylistController::class, 'showPlaylist'])->name('profile.playlist.show');
|
||||
Route::post('/create-playlist', [PlaylistController::class, 'createPlaylist'])->name('profile.playlists.create');
|
||||
Route::delete('/user/playlist/{playlist_id}', [PlaylistController::class, 'deletePlaylist'])->name('profile.playlist.delete');
|
||||
Route::post('/user/playlist-episode', [PlaylistController::class, 'deleteEpisodeFromPlaylist'])->name('playlist.delete.episode');
|
||||
|
||||
// Playlist Routes for Modals on Stream Page
|
||||
Route::post('/hentai/add-to-playlist', [PlaylistController::class, 'addPlaylistApi'])->name('hentai.playlists.add');
|
||||
Route::post('/hentai/create-playlist', [PlaylistController::class, 'createPlaylistApi'])->name('hentai.playlists.create');
|
||||
|
||||
// Download Page
|
||||
Route::get('/download-search', [HomeController::class, 'downloadSearch'])->name('download.search');
|
||||
});
|
||||
@@ -3,14 +3,12 @@
|
||||
use App\Http\Controllers\ContactController;
|
||||
use App\Http\Controllers\HomeController;
|
||||
use App\Http\Controllers\PlaylistController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
|
||||
use App\Http\Controllers\StreamController;
|
||||
use App\Http\Controllers\UserController;
|
||||
use App\Http\Controllers\Api\AdminApiController;
|
||||
use App\Http\Controllers\Api\DownloadApiController;
|
||||
use App\Http\Controllers\Api\HentaiApiController;
|
||||
use App\Http\Controllers\Api\StreamApiController;
|
||||
use App\Http\Controllers\Api\UserApiController;
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
@@ -40,100 +38,15 @@ Route::post('/search', [HomeController::class, 'searchRedirect'])->name('hentai.
|
||||
Route::get('/contact', [ContactController::class, 'index'])->name('contact.index');
|
||||
Route::post('/contact', [ContactController::class, 'store'])->name('contact.store');
|
||||
|
||||
// Public Playlistts
|
||||
// Public Playlists
|
||||
Route::get('/playlists', [PlaylistController::class, 'index'])->name('playlist.index');
|
||||
Route::get('/playlist/{playlist_id}', [PlaylistController::class, 'show'])->name('playlist.show');
|
||||
|
||||
// Captcha Reload
|
||||
Route::get('/reload-captcha', [ContactController::class, 'reloadCaptcha']);
|
||||
|
||||
// Download
|
||||
Route::post('/get-download', [DownloadApiController::class, 'getDownload']);
|
||||
|
||||
Route::post('/update-language', [HomeController::class, 'updateLanguage'])->name('update.language');
|
||||
|
||||
// User Routes
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::get('/user/profile', [ProfileController::class, 'index'])->name('profile.show');
|
||||
Route::get('/user/comments', [ProfileController::class, 'comments'])->name('profile.comments');
|
||||
Route::get('/user/likes', [ProfileController::class, 'likes'])->name('profile.likes');
|
||||
Route::get('/user/watched', [ProfileController::class, 'watched'])->name('user.watched');
|
||||
|
||||
// Notifications
|
||||
Route::get('/user/notifications', [App\Http\Controllers\NotificationController::class, 'index'])->name('profile.notifications');
|
||||
Route::delete('/user/notifications', [App\Http\Controllers\NotificationController::class, 'delete'])->name('profile.notifications.delete');
|
||||
|
||||
// User Profile Actions
|
||||
Route::get('/user/settings', [ProfileController::class, 'settings'])->name('profile.settings');
|
||||
Route::patch('/user/settings', [ProfileController::class, 'update'])->name('profile.update');
|
||||
Route::delete('/user/delete', [ProfileController::class, 'destroy'])->name('profile.delete');
|
||||
Route::post('/user/settings', [ProfileController::class, 'saveSettings'])->name('profile.settings.save');
|
||||
Route::get('/user/blacklist', [UserApiController::class, 'getBlacklist'])->name('profile.blacklist');
|
||||
Route::post('/user/blacklist', [ProfileController::class, 'saveBlacklist'])->name('profile.blacklist.save');
|
||||
|
||||
// Playlist Routes for User Page
|
||||
Route::get('/user/playlists', [PlaylistController::class, 'playlists'])->name('profile.playlists');
|
||||
Route::get('/user/playlist/{playlist_id}', [PlaylistController::class, 'showPlaylist'])->name('profile.playlist.show');
|
||||
Route::post('/create-playlist', [PlaylistController::class, 'createPlaylist'])->name('profile.playlists.create');
|
||||
Route::delete('/user/playlist/{playlist_id}', [PlaylistController::class, 'deletePlaylist'])->name('profile.playlist.delete');
|
||||
Route::post('/user/playlist-episode', [PlaylistController::class, 'deleteEpisodeFromPlaylist'])->name('playlist.delete.episode');
|
||||
|
||||
// Playlist Routes for Modals on Stream Page
|
||||
Route::post('/hentai/add-to-playlist', [PlaylistController::class, 'addPlaylistApi'])->name('hentai.playlists.add');
|
||||
Route::post('/hentai/create-playlist', [PlaylistController::class, 'createPlaylistApi'])->name('hentai.playlists.create');
|
||||
|
||||
// Download Page
|
||||
Route::get('/download-search', [HomeController::class, 'downloadSearch'])->name('download.search');
|
||||
});
|
||||
|
||||
/*
|
||||
|---------------------------------------------------------------------------------
|
||||
| Admin Pages
|
||||
|---------------------------------------------------------------------------------
|
||||
*/
|
||||
Route::group(['middleware' => ['auth', 'auth.admin']], function () {
|
||||
// Site alerts
|
||||
Route::get('/admin/alert', [App\Http\Controllers\Admin\AlertController::class, 'index'])->name('admin.alert.index');
|
||||
Route::post('/admin/alert', [App\Http\Controllers\Admin\AlertController::class, 'store'])->name('admin.alert.create');
|
||||
Route::delete('/admin/alert/{alert_id}', [App\Http\Controllers\Admin\AlertController::class, 'delete'])->name('admin.alert.delete');
|
||||
|
||||
// Users
|
||||
Route::get('/admin/users', [App\Http\Controllers\Admin\UserController::class, 'index'])->name('admin.user.index');
|
||||
Route::post('/admin/users', [App\Http\Controllers\Admin\UserController::class, 'update'])->name('admin.user.update');
|
||||
|
||||
// Comments
|
||||
Route::get('/admin/comments', [App\Http\Controllers\Admin\CommentsController::class, 'index'])->name('admin.comments.index');
|
||||
|
||||
// Contact page overview
|
||||
Route::get('/admin/contact', [App\Http\Controllers\Admin\ContactController::class, 'index'])->name('admin.contact.index');
|
||||
Route::delete('/admin/contact/{contact_id}', [App\Http\Controllers\Admin\ContactController::class, 'delete'])->name('admin.contact.delete');
|
||||
|
||||
// Site background settings
|
||||
Route::get('/admin/background', [App\Http\Controllers\Admin\SiteBackgroundController::class, 'index'])->name('admin.background.index');
|
||||
Route::post('/admin/background', [App\Http\Controllers\Admin\SiteBackgroundController::class, 'create'])->name('admin.background.create');
|
||||
Route::put('/admin/background', [App\Http\Controllers\Admin\SiteBackgroundController::class, 'update'])->name('admin.background.update');
|
||||
Route::delete('/admin/background', [App\Http\Controllers\Admin\SiteBackgroundController::class, 'delete'])->name('admin.background.delete');
|
||||
|
||||
// Release
|
||||
Route::get('/admin/release', [App\Http\Controllers\Admin\ReleaseController::class, 'index'])->name('admin.upload.index');
|
||||
Route::post('/admin/release/upload', [App\Http\Controllers\Admin\ReleaseController::class, 'store'])->name('admin.upload');
|
||||
|
||||
// Episode
|
||||
Route::post('/admin/episode/upload', [App\Http\Controllers\Admin\EpisodeController::class, 'store'])->name('admin.upload.episode');
|
||||
Route::post('/admin/episode/edit', [App\Http\Controllers\Admin\EpisodeController::class, 'update'])->name('admin.edit');
|
||||
|
||||
// Get Tags used for Upload Form
|
||||
Route::get('/admin/tags', [AdminApiController::class, 'getTags'])->name('admin.tags');
|
||||
Route::get('/admin/studios', [AdminApiController::class, 'getStudios'])->name('admin.studios');
|
||||
|
||||
// Get Tags for editing Episode
|
||||
Route::get('/admin/tags/{episode_id}', [AdminApiController::class, 'getEpisodeTags'])->name('admin.tags.episode');
|
||||
Route::get('/admin/studio/{episode_id}', [AdminApiController::class, 'getEpisodeStudio'])->name('admin.studio.episode');
|
||||
|
||||
// Subtitles
|
||||
Route::get('/admin/subtitles/{episode_id}', [AdminApiController::class, 'getSubtitles'])->name('admin.subtitles');
|
||||
Route::post('/admin/add-new-subtitle', [App\Http\Controllers\Admin\SubtitleController::class, 'store'])->name('admin.add.new.subtitle');
|
||||
Route::post('/admin/update-subtitles', [App\Http\Controllers\Admin\SubtitleController::class, 'update'])->name('admin.update.subtitles');
|
||||
});
|
||||
|
||||
require __DIR__.'/user.php';
|
||||
require __DIR__.'/admin.php';
|
||||
require __DIR__.'/auth.php';
|
||||
|
||||
Reference in New Issue
Block a user