From 0b2e95ea65293c75910aaca120d74a1a48736dc0 Mon Sep 17 00:00:00 2001 From: ghassan Date: Wed, 13 May 2026 13:24:32 +0300 Subject: [PATCH] Add NAS file manager integration and all pending platform changes - Installed p7h/nas-file-manager package via private VCS repo - Published config/nas-file-manager.php with super_admin middleware restriction - Added NAS env vars to .env.example - Created admin/nas-storage page with connection info panel and file browser widget - Added NAS Storage link to admin sidebar (super_admin only) - Added SuperAdminController@nasStorage method and admin.nas-storage route - Includes all accumulated branch changes: profile wall, 2FA, audit logs, settings panel, country/phone/timezone components, posts, slideshow, playlist shares, video downloads/shares, comment likes, notifications, social links, and more Co-Authored-By: Claude Sonnet 4.6 --- .claude/component-usage.md | 186 + .claude/settings.json | 5 + .env.example | 11 + CLAUDE.md | 156 + README_cleanup.md | 27 + TODO_admin_cleanup_dashboard.md | 10 + app/Data/Countries.php | 361 ++ app/Exceptions/Handler.php | 20 +- app/Helpers/Horoscope.php | 101 + .../Auth/AuthenticatedSessionController.php | 30 + .../Auth/RegisteredUserController.php | 27 +- app/Http/Controllers/CommentController.php | 74 +- app/Http/Controllers/Controller.php | 25 + .../Controllers/ImageUploadController.php | 45 + app/Http/Controllers/PlaylistController.php | 124 +- app/Http/Controllers/PostController.php | 126 + app/Http/Controllers/SuperAdminController.php | 700 +++- app/Http/Controllers/TwoFactorController.php | 171 + app/Http/Controllers/UserController.php | 320 +- app/Http/Controllers/VideoController.php | 1528 ++++++- app/Http/Kernel.php | 1 + app/Http/Middleware/TrustProxies.php | 2 +- app/Http/Middleware/VerifyCsrfToken.php | 4 +- app/Jobs/CompressVideoJob.php | 46 +- app/Jobs/GenerateHlsJob.php | 160 +- app/Mail/NewVideoNotification.php | 34 + app/Mail/TwoFactorDisableConfirmation.php | 33 + app/Models/AuditLog.php | 86 + app/Models/Comment.php | 5 + app/Models/CommentLike.php | 22 + app/Models/Playlist.php | 24 +- app/Models/Post.php | 51 + app/Models/PostImage.php | 20 + app/Models/PostReaction.php | 20 + app/Models/PostVideo.php | 20 + app/Models/Setting.php | 92 + app/Models/ShareAccess.php | 16 + app/Models/User.php | 125 +- app/Models/UserSocialLink.php | 17 + app/Models/Video.php | 103 +- app/Models/VideoShare.php | 26 + app/Models/VideoSlide.php | 20 + .../NewCommentLikeNotification.php | 38 + app/Notifications/NewCommentNotification.php | 38 + app/Notifications/NewReplyNotification.php | 38 + app/Notifications/NewVideoUploaded.php | 33 + app/Notifications/VerifyEmail.php | 39 + app/Providers/AppServiceProvider.php | 5 + app/Rules/NotDisposableEmail.php | 141 + app/Services/GeoIpService.php | 46 + composer.json | 14 +- composer.lock | 672 ++- config/ffmpeg.php | 4 +- config/nas-file-manager.php | 57 + ..._110233_add_geo_columns_to_video_views.php | 30 + ...04_28_000001_add_gender_to_users_table.php | 22 + ...0040_add_contact_fields_to_users_table.php | 25 + ...add_extra_social_fields_to_users_table.php | 28 + ..._135244_create_user_social_links_table.php | 52 + ..._visibility_to_user_social_links_table.php | 32 + ...829_add_allow_download_to_videos_table.php | 25 + ...064829_create_user_subscriptions_table.php | 30 + ...d_with_download_access_on_videos_table.php | 36 + ...4_30_211555_create_notifications_table.php | 31 + .../2026_04_30_220000_create_posts_table.php | 24 + ..._30_220001_create_post_reactions_table.php | 24 + ...01_add_download_share_counts_to_videos.php | 23 + ...03_000001_create_video_downloads_table.php | 30 + ...05_04_000001_create_video_shares_table.php | 36 + ...5_04_000002_create_comment_likes_table.php | 24 + ...5_003048_add_two_factor_to_users_table.php | 26 + ..._05_05_091101_create_slideshow_feature.php | 34 + ...026_05_06_000002_create_settings_table.php | 38 + ...0_add_unlisted_to_playlists_visibility.php | 61 + ...08_000001_add_share_token_to_playlists.php | 29 + ...8_000002_create_playlist_shares_tables.php | 36 + ...05_08_000003_add_share_token_to_videos.php | 29 + ..._05_10_000001_create_post_images_table.php | 24 + ..._05_10_000002_create_post_videos_table.php | 24 + ..._12_000000_add_username_to_users_table.php | 34 + ...05_12_000001_add_banner_to_users_table.php | 22 + ...6_05_13_113043_create_audit_logs_table.php | 35 + public/css/cropme.min.css | 9 + public/js/cropme.min.js | 10 + public/js/jquery-4.0.0.min.js | 2 + public/sounds/chime.wav | Bin 0 -> 44144 bytes resources/views/admin/audit-logs.blade.php | 385 ++ resources/views/admin/dashboard.blade.php | 1245 +++++- resources/views/admin/edit-user.blade.php | 365 +- resources/views/admin/edit-video.blade.php | 404 +- resources/views/admin/layout.blade.php | 1331 +++--- resources/views/admin/logs.blade.php | 100 + resources/views/admin/nas-storage.blade.php | 89 + .../views/admin/partials/gpu-cards.blade.php | 25 + .../views/admin/partials/pagination.blade.php | 47 + resources/views/admin/settings.blade.php | 430 ++ resources/views/admin/users.blade.php | 288 +- .../views/admin/video-analytics.blade.php | 590 +++ resources/views/admin/videos.blade.php | 362 +- resources/views/auth/2fa-challenge.blade.php | 31 + resources/views/auth/login.blade.php | 208 +- resources/views/auth/register.blade.php | 209 +- resources/views/auth/verify-email.blade.php | 37 + .../views/components/channel-row.blade.php | 31 +- .../views/components/country-select.blade.php | 234 ++ .../views/components/date-picker.blade.php | 416 ++ .../views/components/emails/layout.blade.php | 218 + .../views/components/gender-select.blade.php | 172 + .../views/components/image-cropper.blade.php | 406 ++ .../components/phone-code-select.blade.php | 307 ++ .../views/components/playlist-card.blade.php | 78 + .../components/social-links-editor.blade.php | 324 ++ .../components/timezone-select.blade.php | 234 ++ .../views/components/video-actions.blade.php | 446 +- .../views/components/video-card.blade.php | 220 +- .../views/components/video-comments.blade.php | 2075 +++++---- .../views/components/video-insights.blade.php | 785 ++++ .../views/components/video-player.blade.php | 1269 ++++++ .../emails/2fa-disable-confirm.blade.php | 47 + resources/views/emails/layout.blade.php | 218 + .../emails/new-video-notification.blade.php | 60 + resources/views/emails/verify-email.blade.php | 38 + .../views/emails/video-uploaded.blade.php | 143 +- resources/views/layouts/app.blade.php | 1097 ++++- resources/views/layouts/auth.blade.php | 195 + .../partials/add-to-playlist-modal.blade.php | 10 +- .../partials/confirm-delete-modal.blade.php | 6 +- .../partials/edit-video-modal.blade.php | 364 +- .../views/layouts/partials/header.blade.php | 304 +- .../layouts/partials/share-modal.blade.php | 155 +- .../views/layouts/partials/sidebar.blade.php | 94 +- .../layouts/partials/upload-modal.blade.php | 318 +- resources/views/partials/pagination.blade.php | 79 + resources/views/playlists/index.blade.php | 131 +- resources/views/playlists/show.blade.php | 1371 +++--- resources/views/user/channel.blade.php | 3712 +++++++++++++---- resources/views/user/history.blade.php | 207 +- resources/views/user/liked.blade.php | 2 +- resources/views/user/profile.blade.php | 698 +++- resources/views/user/settings.blade.php | 28 +- resources/views/videos/create.blade.php | 448 +- resources/views/videos/edit.blade.php | 1070 +++-- resources/views/videos/index.blade.php | 234 +- .../videos/partials/audio-player.blade.php | 843 ++++ .../views/videos/partials/comment.blade.php | 2 +- .../videos/partials/description-box.blade.php | 85 + .../videos/partials/video-details.blade.php | 52 +- resources/views/videos/shorts.blade.php | 882 ++-- resources/views/videos/show.blade.php | 262 +- .../videos/types/backup/generic.blade.php | 8 +- .../views/videos/types/backup/match.blade.php | 48 +- .../views/videos/types/backup/music.blade.php | 8 +- .../views/videos/types/generic.blade.php | 254 +- resources/views/videos/types/match.blade.php | 296 +- resources/views/videos/types/music.blade.php | 284 +- routes/auth.php | 49 +- routes/web.php | 108 +- screenshots/Screenshot 2026-05-01 104419.png | Bin 0 -> 34895 bytes ...03-73_6012fa4d4ddec268fc5c7112cbb265e7.jpg | Bin 0 -> 293864 bytes scripts/fix-gpu-host.sh | 59 + scripts/fix-gpu-vm.sh | 132 + 161 files changed, 27496 insertions(+), 7250 deletions(-) create mode 100644 .claude/component-usage.md create mode 100644 .claude/settings.json create mode 100644 CLAUDE.md create mode 100644 README_cleanup.md create mode 100644 TODO_admin_cleanup_dashboard.md create mode 100644 app/Data/Countries.php create mode 100644 app/Helpers/Horoscope.php create mode 100644 app/Http/Controllers/ImageUploadController.php create mode 100644 app/Http/Controllers/PostController.php create mode 100644 app/Http/Controllers/TwoFactorController.php create mode 100644 app/Mail/NewVideoNotification.php create mode 100644 app/Mail/TwoFactorDisableConfirmation.php create mode 100644 app/Models/AuditLog.php create mode 100644 app/Models/CommentLike.php create mode 100644 app/Models/Post.php create mode 100644 app/Models/PostImage.php create mode 100644 app/Models/PostReaction.php create mode 100644 app/Models/PostVideo.php create mode 100644 app/Models/Setting.php create mode 100644 app/Models/ShareAccess.php create mode 100644 app/Models/UserSocialLink.php create mode 100644 app/Models/VideoShare.php create mode 100644 app/Models/VideoSlide.php create mode 100644 app/Notifications/NewCommentLikeNotification.php create mode 100644 app/Notifications/NewCommentNotification.php create mode 100644 app/Notifications/NewReplyNotification.php create mode 100644 app/Notifications/NewVideoUploaded.php create mode 100644 app/Notifications/VerifyEmail.php create mode 100644 app/Rules/NotDisposableEmail.php create mode 100644 app/Services/GeoIpService.php create mode 100644 config/nas-file-manager.php create mode 100644 database/migrations/2026_04_27_110233_add_geo_columns_to_video_views.php create mode 100644 database/migrations/2026_04_28_000001_add_gender_to_users_table.php create mode 100644 database/migrations/2026_04_28_220040_add_contact_fields_to_users_table.php create mode 100644 database/migrations/2026_04_29_134426_add_extra_social_fields_to_users_table.php create mode 100644 database/migrations/2026_04_29_135244_create_user_social_links_table.php create mode 100644 database/migrations/2026_04_29_140001_add_visibility_to_user_social_links_table.php create mode 100644 database/migrations/2026_04_30_064829_add_allow_download_to_videos_table.php create mode 100644 database/migrations/2026_04_30_064829_create_user_subscriptions_table.php create mode 100644 database/migrations/2026_04_30_154745_replace_allow_download_with_download_access_on_videos_table.php create mode 100644 database/migrations/2026_04_30_211555_create_notifications_table.php create mode 100644 database/migrations/2026_04_30_220000_create_posts_table.php create mode 100644 database/migrations/2026_04_30_220001_create_post_reactions_table.php create mode 100644 database/migrations/2026_05_01_000001_add_download_share_counts_to_videos.php create mode 100644 database/migrations/2026_05_03_000001_create_video_downloads_table.php create mode 100644 database/migrations/2026_05_04_000001_create_video_shares_table.php create mode 100644 database/migrations/2026_05_04_000002_create_comment_likes_table.php create mode 100644 database/migrations/2026_05_05_003048_add_two_factor_to_users_table.php create mode 100644 database/migrations/2026_05_05_091101_create_slideshow_feature.php create mode 100644 database/migrations/2026_05_06_000002_create_settings_table.php create mode 100644 database/migrations/2026_05_08_000000_add_unlisted_to_playlists_visibility.php create mode 100644 database/migrations/2026_05_08_000001_add_share_token_to_playlists.php create mode 100644 database/migrations/2026_05_08_000002_create_playlist_shares_tables.php create mode 100644 database/migrations/2026_05_08_000003_add_share_token_to_videos.php create mode 100644 database/migrations/2026_05_10_000001_create_post_images_table.php create mode 100644 database/migrations/2026_05_10_000002_create_post_videos_table.php create mode 100644 database/migrations/2026_05_12_000000_add_username_to_users_table.php create mode 100644 database/migrations/2026_05_12_000001_add_banner_to_users_table.php create mode 100644 database/migrations/2026_05_13_113043_create_audit_logs_table.php create mode 100644 public/css/cropme.min.css create mode 100644 public/js/cropme.min.js create mode 100644 public/js/jquery-4.0.0.min.js create mode 100644 public/sounds/chime.wav create mode 100644 resources/views/admin/audit-logs.blade.php create mode 100644 resources/views/admin/logs.blade.php create mode 100644 resources/views/admin/nas-storage.blade.php create mode 100644 resources/views/admin/partials/gpu-cards.blade.php create mode 100644 resources/views/admin/partials/pagination.blade.php create mode 100644 resources/views/admin/settings.blade.php create mode 100644 resources/views/admin/video-analytics.blade.php create mode 100644 resources/views/auth/2fa-challenge.blade.php create mode 100644 resources/views/auth/verify-email.blade.php create mode 100644 resources/views/components/country-select.blade.php create mode 100644 resources/views/components/date-picker.blade.php create mode 100644 resources/views/components/emails/layout.blade.php create mode 100644 resources/views/components/gender-select.blade.php create mode 100644 resources/views/components/image-cropper.blade.php create mode 100644 resources/views/components/phone-code-select.blade.php create mode 100644 resources/views/components/playlist-card.blade.php create mode 100644 resources/views/components/social-links-editor.blade.php create mode 100644 resources/views/components/timezone-select.blade.php create mode 100644 resources/views/components/video-insights.blade.php create mode 100644 resources/views/components/video-player.blade.php create mode 100644 resources/views/emails/2fa-disable-confirm.blade.php create mode 100644 resources/views/emails/layout.blade.php create mode 100644 resources/views/emails/new-video-notification.blade.php create mode 100644 resources/views/emails/verify-email.blade.php create mode 100644 resources/views/layouts/auth.blade.php create mode 100644 resources/views/partials/pagination.blade.php create mode 100644 resources/views/videos/partials/audio-player.blade.php create mode 100644 resources/views/videos/partials/description-box.blade.php create mode 100644 screenshots/Screenshot 2026-05-01 104419.png create mode 100644 screenshots/Screenshot_2026-05-04-00-00-03-73_6012fa4d4ddec268fc5c7112cbb265e7.jpg create mode 100755 scripts/fix-gpu-host.sh create mode 100755 scripts/fix-gpu-vm.sh diff --git a/.claude/component-usage.md b/.claude/component-usage.md new file mode 100644 index 0000000..dcab428 --- /dev/null +++ b/.claude/component-usage.md @@ -0,0 +1,186 @@ +# Reusable Select Component Usage + +This file tracks every page/partial that uses ``, ``, or ``. +**Update this file whenever you add or remove a component from a view.** + +When modifying any component or its data source (`app/Data/Countries.php`), check all pages in the relevant section below and verify the change works correctly in each context. + +--- + +## Data source + +**`app/Data/Countries.php`** — `App\Data\Countries` + +| Method | Used by component | +|---|---| +| `Countries::forPhoneCode()` | `` | +| `Countries::forCountry()` | `` | +| `Countries::forTimezone()` | `` | +| `Countries::all()` | All three (via the above methods) | + +Adding or renaming a field in `Countries::all()` requires updating the corresponding `for*()` method too. + +--- + +## Shared CSS / JS + +The `.csd-*` CSS rules and the `window.CSD` class are duplicated across all three component files inside `@once` guards. If you change the look or behaviour of the dropdown, **update all three component files**: + +- `resources/views/components/phone-code-select.blade.php` +- `resources/views/components/country-select.blade.php` +- `resources/views/components/timezone-select.blade.php` + +The `@once` Blade directive ensures the browser only receives one copy of the CSS/JS even when multiple components are on the same page. + +--- + +## `` + +**File:** `resources/views/components/video-insights.blade.php` +**Props:** `:video` — `Video` model instance. +**Behaviour:** Renders the Insights tab panel (`
`), the drill-down modal, all `.ins-*` CSS, and all insights JS (`loadInsights`, `renderInsights`, modal openers, country/day/downloader drill-downs). Only renders if `Auth::id() === $video->user_id`. Must be placed **inside `.vdb-wrap`**, after the About panel, so the tab-switch CSS applies. The parent view must call `loadInsights()` (global, defined by this component) when the Insights tab is activated. +**Data source:** `GET /videos/{video}/insights` (JSON) + drill-down routes `/insights/country/{code}`, `/insights/day/{date}`, `/insights/downloader/{userId}`. + +| View file | Placement | Notes | +|---|---|---| +| `resources/views/videos/partials/description-box.blade.php` | Inside `.vdb-wrap`, after About panel | Used by all three video type views (generic, match, music) | +| `resources/views/videos/show.blade.php` | Inside `.vdb-wrap`, after About panel | Legacy view (not rendered by controller — kept in sync) | + +--- + +## `` + +**File:** `resources/views/components/social-links-editor.blade.php` +**Props:** `existing` — associative array keyed by platform name (e.g. `['twitter' => 'handle', 'whatsapp' => '97312345678']`). +**Behaviour:** Dynamic add/remove rows; each row has a custom icon dropdown to pick the platform and a text input for the value. Supported platforms: `twitter`, `instagram`, `facebook`, `youtube`, `linkedin`, `tiktok`, `whatsapp`, `website`, `google_location`, `social_phone`, `social_email`. Hidden clear inputs ensure removed entries are cleared on save. Must be placed **inside a `
`**. +**DB columns:** `twitter`, `instagram`, `facebook`, `youtube`, `linkedin`, `tiktok`, `website` (legacy), `whatsapp`, `google_location`, `social_phone`, `social_email`. + +| View file | Placement | Notes | +|---|---|---| +| `resources/views/user/profile.blade.php` | Social tab of Edit Profile modal | `$socialExisting` array passed from `@php` block above `@section('scripts')` | + +--- + +## `` + +**File:** `resources/views/components/date-picker.blade.php` +**Stored value:** `YYYY-MM-DD` string in a hidden input (same format as ``). +**Props:** `name`, `id`, `value`, `label`, `required`, `class`, `style`, `minYear` (default 1900), `maxYear` (default current year). +**Behaviour:** Day grid (5 columns, 1–31), month list (January–December), year searchable list (descending). Days auto-constrain on month/year change; invalid selected day resets automatically. + +| View file | Field name | Notes | +|---|---|---| +| `resources/views/user/profile.blade.php` | `birthday` | Replaces `` | +| `resources/views/auth/register.blade.php` | `birthday` | Registration form — mandatory | + +--- + +## `` + +**File:** `resources/views/components/image-cropper.blade.php` +**Props:** `id` (unique, required), `width` (px, default 300), `height` (px, default 300), `shape` (`circle`|`square`, default `circle`), `folder` (storage subfolder), `filename` (base name without extension), `callback` (JS function name called with URL on success), `update-url` (endpoint to POST `{path}` after crop to update DB), `title` (modal heading). +**Behaviour:** Renders a full-screen dark-themed modal with Cropme.js. Shows camera icon on avatar/banner hover (owner only). After crop: POSTs base64 to `/image-upload`, optionally POSTs the path to `update-url`, then calls `callback(url)`. Uses local assets (`public/js/cropme.min.js`, `public/css/cropme.min.css`). No jQuery required. +**Assets needed:** `public/js/cropme.min.js`, `public/css/cropme.min.css`. +**Routes needed:** `image.upload` (POST `/image-upload`). + +| View file | id / use | Notes | +|---|---|---| +| `resources/views/user/channel.blade.php` | `avatar` — circle 300×300 | Owner only; `update-url = profile.updateAvatar`; callback `onAvatarSaved` | +| `resources/views/user/channel.blade.php` | `banner` — square 500×160 | Owner only; `update-url = profile.updateBanner`; callback `onBannerSaved` | +| `resources/views/layouts/partials/upload-modal.blade.php` | `thumb_upload` — square 448×252 | Form mode; `target-input=thumbnail-modal`; output 1280px | +| `resources/views/layouts/partials/edit-video-modal.blade.php` | `thumb_edit` — square 448×252 | Form mode; `target-input=edit-thumbnail-input`; output 1280px | +| `resources/views/videos/create.blade.php` | `thumb_create_mobile` — square 448×252 | Mobile; `target-input=thumbnail`; output 1280px | +| `resources/views/videos/edit.blade.php` | `thumb_edit_mobile` — square 448×252 | Mobile; `target-input=edit-thumbnail`; output 1280px | +| `resources/views/playlists/index.blade.php` | `thumb_pl_create` — square 448×252 | Form mode; `target-input=playlist-thumbnail-input`; output 1280px | +| `resources/views/playlists/show.blade.php` | `thumb_pl_edit` — square 448×252 | Form mode; `target-input=playlistThumbnailInput`; output 1280px | + +--- + +## `` + +**File:** `resources/views/components/gender-select.blade.php` +**Props:** `name`, `id`, `value` (ISO string: `"male"` or `"female"`), `label`, `required`, `class`, `style`. +**Behaviour:** Custom dropdown with blue ♂ (male) and pink ♀ (female) symbols. No search needed. Stores value as `"male"` or `"female"`. + +| View file | Field name | Notes | +|---|---|---| +| `resources/views/auth/register.blade.php` | `gender` | Registration form — mandatory | + +--- + +## `` + +Stored value format: `"+973|BH"` (dial_code + pipe + ISO2). +To read only the dial code from a stored value: `explode('|', $value)[0]`. + +| View file | Field name | Notes | +|---|---|---| +| `resources/views/user/profile.blade.php` | `phone_code` | Paired with `phone_number` text input | + +--- + +## `` + +Stored value: ISO2 code (e.g. `"BH"`). + +| View file | Field name | Notes | +|---|---|---| +| `resources/views/user/profile.blade.php` | `nationality` | Edit Profile form | +| `resources/views/auth/register.blade.php` | `nationality` | Registration form — mandatory | + +--- + +## `` + +Stored value: IANA timezone string (e.g. `"Asia/Bahrain"`). + +| View file | Field name | Notes | +|---|---|---| +| `resources/views/user/profile.blade.php` | `timezone` | Edit Profile form | + +--- + +## Usage example + +```blade +{{-- Phone code + number side by side --}} +
+ + +
+ +{{-- Country / nationality --}} + + +{{-- Timezone --}} + +``` + +--- + +## Modification checklist + +When you modify any of these components, work through this list: + +- [ ] Update `app/Data/Countries.php` if the data structure changes +- [ ] Update all three `.blade.php` component files if shared CSS/JS changes +- [ ] Update the `for*()` method in `Countries.php` that feeds the changed component +- [ ] Re-test every page listed in the usage tables above +- [ ] Add/remove rows from the usage tables if views were added or removed diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..9030888 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,5 @@ +{ + "enabledPlugins": { + "frontend-design@claude-plugins-official": true + } +} diff --git a/.env.example b/.env.example index ea0665b..ba27d4a 100755 --- a/.env.example +++ b/.env.example @@ -57,3 +57,14 @@ VITE_PUSHER_HOST="${PUSHER_HOST}" VITE_PUSHER_PORT="${PUSHER_PORT}" VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" + +# NAS File Manager +NAS_PROTOCOL=smb +NAS_HOST= +NAS_PORT=445 +NAS_USERNAME= +NAS_PASSWORD= +NAS_PATH=/media +NAS_SMB_SHARE= +NAS_SMB_DOMAIN= +NAS_FM_ROUTE_PREFIX=nas-file-manager diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ecc9239 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,156 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**TAKEONE** (Play) is a Laravel 10 video-sharing platform with sports match annotation, HLS adaptive streaming, GPU-accelerated video processing, playlists, threaded comments, and a super-admin panel. Live at `https://video.takeone.bh`. + +Tech stack: PHP 8.1+, Laravel 10, Blade templating, Vite 5, Axios, FFmpeg/FFProbe with NVIDIA NVENC, SQLite (dev) / MySQL (prod), Laravel Sanctum. + +--- + +## Essential Commands + +### Local Development +```bash +php artisan serve # Backend on http://localhost:8000 +npm run dev # Vite dev server with HMR +npm run build # Production frontend build → public/build/ +``` + +### Database +```bash +php artisan migrate +php artisan db:seed +php artisan tinker +``` + +### Background Workers +```bash +# Video processing (CompressVideoJob, GenerateHlsJob) +php artisan queue:work --queue=video-processing + +# Orphaned file cleanup scheduler (every CLEANUP_INTERVAL_MINUTES, default 30) +php artisan schedule:run # run this every minute via cron +php artisan cleanup:orphaned-videos --dry-run # preview only +php artisan cleanup:orphaned-videos --force # delete orphans +``` + +### Testing +```bash +./vendor/bin/phpunit +./vendor/bin/phpunit --filter "TestName" +./vendor/bin/phpunit tests/Feature/ExampleTest.php +``` +Tests use in-memory SQLite, array mail/cache/session drivers, sync queue, and BCRYPT_ROUNDS=4. Set `APP_ENV=testing`. + +--- + +## Architecture + +### Role System +Three roles stored on `users.role`: `super_admin`, `admin`, `user` (null = user). The `IsSuperAdmin` middleware guards all `/admin/*` routes. Helper methods on the User model: `isSuperAdmin()`, `isAdmin()`, `isUser()`. + +### Video Lifecycle +1. `VideoController@store` validates upload, stores file, extracts metadata via FFProbe (duration, dimensions, orientation). +2. Status column transitions: `pending → processing → ready` (or `failed`). +3. `CompressVideoJob` re-encodes with NVIDIA NVENC (`h264_nvenc`, CRF 23), replaces original if smaller. +4. `GenerateHlsJob` produces 480p / 720p / 1080p HLS variants (`h264_nvenc`, preset p4) as `.m3u8` + `.ts` files. +5. Streaming served at `GET /videos/{video}/hls/{file?}` (master playlist → segments). + +Queue connection is `sync` by default (runs inline); switch `QUEUE_CONNECTION=database` or `redis` for true async. + +### Shorts Auto-Detection +A video is a Short when duration ≤ 60 s **and** portrait orientation. The `is_shorts` DB column allows manual override. Use model scopes `Video::shorts()` / `Video::notShorts()`. + +### Trending Algorithm +`Video::scopeTrending($hours=48, $limit=50)` — weighted score: 70% recent views, 15% view velocity, 10% recency bonus, 5% likes. Only applies to videos < 10 days old with ≥ 5 recent views. + +### Playlist System +- `playlist_videos` pivot carries `position`, `watched_seconds`, `watched`, `added_at`, `last_watched_at`. +- Each user has one `is_default = true` playlist ("Watch Later"). +- `Playlist` model methods: `addVideo()`, `removeVideo()`, `reorder()`, `getNextVideo()`, `getPreviousVideo()`, `updateWatchProgress()`, `canView()`, `canEdit()`. + +### Comment Threading & Mentions +Comments have a `parent_id` for one-level threading (replies). The `parsed_body` accessor converts `@username` syntax into clickable profile links. + +### Comment Timestamp Badges +The `enhanceBody()` function in `components/video-comments.blade.php` converts timestamp syntax written in comments into clickable `._comment-time-badge` spans at render time (client-side). Supported formats: +- `@mm:ss` — single timestamp, jumps to that point (colon separator) +- `@mm.ss` — same, dot separator (legacy, still supported) +- `@mm:ss-mm:ss` or `@mm.ss-mm.ss` — time range; plays from start to end then pauses + +Clicking a badge: scrolls to `#ytpWrap` (smooth), waits 500 ms, seeks `#videoPlayer` to `start`, calls `.play()`, and if a range is specified stops at `end` via a `timeupdate` listener. `@username` mention detection requires the first char to be a letter so it never collides with numeric timestamps. + +### Sports Match Annotation +Videos of type `match` support three related models: +- `MatchRound` — round number, name, `start_time_seconds` +- `MatchPoint` — `timestamp_seconds`, action, competitor (blue/red), running score +- `CoachReview` — time-range segment with coach note and emoji + +All managed via `MatchEventController` under authenticated routes. + +### Key Model Scopes & Accessors +`Video` scopes: `public()`, `visibleTo($user)`, `shorts()`, `notShorts()`, `trending()`. +`Video` accessors: `url`, `thumbnail_url`, `like_count`, `view_count`, `formatted_duration`, `iso_duration`, `open_graph_image`. +`Playlist` accessors: `thumbnail_url`, `video_count`, `total_duration`, `formatted_duration`. + +## Rules + +**Database changes require confirmation** — if any task requires a migration, schema change, or new column, always ask before proceeding. + +**Never use `alert()`, `confirm()`, or `prompt()`** — use toast notifications or inline UI feedback instead. + +**All buttons must use the global `.action-btn` system** — never add custom button CSS. Use these classes: +- `.action-btn` — default (bordered, bg-secondary) +- `.action-btn.action-btn-primary` or `.action-btn.primary` — brand red, for primary/submit actions +- `.action-btn.action-btn-danger` or `.action-btn.danger` — red border/text, for destructive actions +- `.action-btn.icon-only` — square padding, for icon-only buttons + +Structure: ``. The global CSS lives in `layouts/app.blade.php`. + +**Mobile layout uses a native-app scroll model** — on `max-width: 768px`, `html` and `body` are locked (`overflow: hidden; position: fixed`) so the window never scrolls. `.yt-main` is `position: fixed` spanning `top: 56px` to `bottom: calc(56px + env(safe-area-inset-bottom))` with `overflow-y: auto; -webkit-overflow-scrolling: touch`. This keeps the header and bottom nav truly fixed without any JavaScript. Consequences to remember: +- **Never use `position: sticky` inside `.yt-main` on mobile** — sticky elements float over content because the scroll container changed. Override with `position: relative !important` in the mobile media query. +- **Never rely on `window.scrollY` or `window.scroll` events on mobile** — the window doesn't scroll; listen on `document.getElementById('main')` instead. +- **Bottom nav needs no JS transform** — since the window is locked, browser chrome animation never shifts fixed elements. + +**Upload modal and upload page must always stay in sync** — the desktop upload UI lives in `resources/views/layouts/partials/upload-modal.blade.php` and the mobile upload UI lives in `resources/views/videos/create.blade.php`. On mobile, `openUploadModal()` redirects to the create page instead of showing the modal. **Any change made to one must be applied to the other immediately in the same task.** + +**Edit modal and edit page must always stay in sync** — the desktop edit UI lives in `resources/views/layouts/partials/edit-video-modal.blade.php` and the mobile edit UI lives in `resources/views/videos/edit.blade.php`. On mobile (< 992px), `openEditVideoModal(videoId)` redirects to `/videos/{id}/edit` instead of opening the modal. **Any change made to one must be applied to the other immediately in the same task.** — the desktop upload UI lives in `resources/views/layouts/partials/upload-modal.blade.php` and the mobile upload UI lives in `resources/views/videos/create.blade.php`. On mobile, `openUploadModal()` redirects to the create page instead of showing the modal. **Any change made to one must be applied to the other immediately in the same task** — this includes new fields, validation logic, file-type support, JS behaviour, labels, and error handling. Never update only one side. + +**Never build custom dropdowns for country, nationality, phone code, timezone, or currency** — reusable Blade components already exist for these. Always use them; never roll a new ` - @error('name') -
{{ $message }}
- @enderror + + {{-- Name --}} +
+ + + @error('name')
{{ $message }}
@enderror
- -
- - - @error('email') -
{{ $message }}
- @enderror + + {{-- Email --}} +
+ + + @error('email')
{{ $message }}
@enderror
- -
- - + + + - @error('role') -
{{ $message }}
- @enderror + @error('role')
{{ $message }}
@enderror
- -
- -
Change Password (Optional)
- -
- - - @error('new_password') -
{{ $message }}
- @enderror + + {{-- Password section --}} +
+ Change Password + — leave blank to keep current password
- -
- - + +
+
+ + + @error('new_password')
{{ $message }}
@enderror +
+
+ + +
- -
- - Cancel + Cancel
- -
- -
-
-
User Info
-
- -
- {{ $user->name }} -
{{ $user->name }}
-

{{ $user->email }}

+ + {{-- ── Right: user info sidebar ── --}} +
+ + {{-- Profile card --}} +
+
+ {{ $user->name }} +
{{ $user->name }}
+
{{ $user->email }}
@if($user->role === 'super_admin') - Super Admin + Super Admin @elseif($user->role === 'admin') - Admin + Admin @else - User + User @endif
- -
- -
- User ID - #{{ $user->id }} +
+ + {{-- Stats card --}} +
+
+
Account Info
-
- Joined - {{ $user->created_at->format('M d, Y') }} +
+
+ User ID + #{{ $user->id }} +
+
+ Joined + {{ $user->created_at->format('M d, Y') }} +
+
+ Last active + {{ $user->updated_at->diffForHumans() }} +
+
+ Videos + {{ $user->videos->count() }} +
+
+ Email verified + @if($user->email_verified_at) + Verified + @else + Unverified + @endif +
-
- Total Videos - {{ $user->videos->count() }} +
+ + {{-- Impersonate --}} + @if(!$user->isSuperAdmin()) +
+ @csrf + +
+ @endif + + {{-- Danger zone --}} + @if(!$user->isSuperAdmin()) +
+
Danger Zone
+ +
+ @endif + +
+
+ +{{-- ── Delete confirmation dialog ── --}} +@if(!$user->isSuperAdmin()) +
+
+
+
+ Delete User
-
- Email Verified - {{ $user->email_verified_at ? 'Yes' : 'No' }} + +
+
+

You are about to permanently delete {{ $user->name }}.

+
+ + All videos uploaded by this user will also be deleted. This cannot be undone.
+
+@endif + +@endsection + +@section('scripts') + @endsection diff --git a/resources/views/admin/edit-video.blade.php b/resources/views/admin/edit-video.blade.php index 79f9936..eed5ba9 100644 --- a/resources/views/admin/edit-video.blade.php +++ b/resources/views/admin/edit-video.blade.php @@ -3,164 +3,312 @@ @section('title', 'Edit Video') @section('page_title', 'Edit Video') +@section('extra_styles') + +@endsection + @section('content') - + +{{-- ── Page header ── --}} +
+
+ + + +

+ Edit Video +

+
+
+ +{{-- ── Alerts ── --}} @if(session('success')) -