diff --git a/app/Http/Controllers/ExploreController.php b/app/Http/Controllers/ExploreController.php index aa31b1c..7317887 100644 --- a/app/Http/Controllers/ExploreController.php +++ b/app/Http/Controllers/ExploreController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Models\Artist; use App\Models\Ranking; use Illuminate\Http\JsonResponse; use Illuminate\View\View; @@ -11,9 +12,10 @@ class ExploreController extends Controller public function pages(): JsonResponse { return response()->json([ + 'top_artists' => Artist::getTopArtists(), 'rankings' => Ranking::query() ->forExplorePage(request()->search) - ->paginate(6), + ->paginate(5), ], 200); } } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index b64a16f..fa9ee7b 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -13,11 +13,21 @@ public function show(string $id): View { $user = User::where('spotify_id', $id)->first(); + /* no profile found for user */ + if (is_null($user)) { + return view('profile.show', [ + 'user' => null, + 'name' => get_formatted_name($id), + 'rankings' => null, + ]); + } + return view('profile.show', [ 'user' => $user, + 'name' => get_formatted_name($user->name), 'rankings' => Ranking::query() ->forProfilePage($user) - ->paginate(5) + ->get() ]); } } diff --git a/app/Http/Controllers/RankingController.php b/app/Http/Controllers/RankingController.php index ad82397..d82259a 100644 --- a/app/Http/Controllers/RankingController.php +++ b/app/Http/Controllers/RankingController.php @@ -75,7 +75,7 @@ public function destroy(DestroyRankingRequest $request): JsonResponse Song::where('ranking_id', $request->rankingId)->delete(); return response()->json([ - 'message' => 'Your ranking has been tossed into the void, never to return. Or will it?', + 'message' => 'Your ranking has been deleted.', 'rankings' => Ranking::query() ->where('user_id', auth()->id()) ->with('user', 'artist') @@ -85,22 +85,4 @@ public function destroy(DestroyRankingRequest $request): JsonResponse ->paginate(5), ], 200); } - - public function pages(): JsonResponse - { - $user = User::where('spotify_id', request()->spotify_id)->first(); - - $response = [ - 'success' => true, - 'rankings' => Ranking::query()->forProfilePage($user)->paginate(5), - 'name' => $user ? get_formatted_name($user->name) : null, - ]; - - if (is_null($user)) { - $response['success'] = false; - $response['message'] = "No ranking results for user: {$spotify_id}."; - } - - return response()->json($response, 200); - } } diff --git a/app/Models/Artist.php b/app/Models/Artist.php index 370418c..76facd5 100644 --- a/app/Models/Artist.php +++ b/app/Models/Artist.php @@ -17,4 +17,24 @@ public function ranking(): BelongsTo { return $this->belongsTo(Ranking::class); } + + public static function getTopArtists() + { + return self::query() + ->selectRaw(' + count(rankings.artist_id) as artist_rankings_count, + artists.id, + artists.artist_name + ') + ->join('rankings', function($join) { + $join->on('rankings.artist_id', '=', 'artists.id') + ->where('rankings.is_ranked', true) + ->where('rankings.is_public', true); + }) + ->groupBy('rankings.artist_id') + ->orderBy('artist_rankings_count', 'desc') + ->orderBy('artists.artist_name', 'asc') + ->limit(10) + ->get(); + } } diff --git a/app/Models/Ranking.php b/app/Models/Ranking.php index b9be828..18c7e4f 100644 --- a/app/Models/Ranking.php +++ b/app/Models/Ranking.php @@ -126,10 +126,8 @@ public function scopeForExplorePage(Builder $query, ?string $search = null) ->where('is_ranked', true) ->where('is_public', true) ->when($search != null, function($query) use ($search) { - $query->where(function($query2) use ($search) { - $query2->newQuery() - ->whereHas('artist', fn($q) => $q->where('artist_name', 'LIKE', "%{$search}%")) - ->orWhere('name', 'LIKE', "%{$search}%"); + $query->whereHas('artist', function($q) use ($search) { + $q->where('artist_name', 'LIKE', "%{$search}%")->orWhere('id', $search); }); }) ->with('user', 'artist') diff --git a/bootstrap/app.php b/bootstrap/app.php index f94b6a0..26ff8bf 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -25,6 +25,10 @@ $middleware->redirectUsersTo(AppServiceProvider::HOME); $middleware->throttleApi(); + + $middleware->validateCsrfTokens(except: [ + 'support-bubble', + ]); }) ->withSchedule(function (Schedule $schedule) { }) diff --git a/composer.json b/composer.json index 944ef10..3676d6e 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "laravel/tinker": "^2.9", "maatwebsite/excel": "^3.1", "marvinlabs/laravel-discord-logger": "^1.4", - "socialiteproviders/spotify": "^4.1" + "socialiteproviders/spotify": "^4.1", + "spatie/laravel-support-bubble": "^1.8" }, "require-dev": { "barryvdh/laravel-debugbar": "^3.10", diff --git a/composer.lock b/composer.lock index 2d51e43..c9a8eaf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ca4009e890726c0f1cf58bbddc9a3b2d", + "content-hash": "88078f2a66241b98335b6f231306321d", "packages": [ { "name": "brick/math", @@ -4958,6 +4958,226 @@ }, "time": "2020-12-01T23:10:59+00:00" }, + { + "name": "spatie/laravel-honeypot", + "version": "4.5.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-honeypot.git", + "reference": "57727836997ae7351a4f56008bbaf4e2801ce4a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-honeypot/zipball/57727836997ae7351a4f56008bbaf4e2801ce4a0", + "reference": "57727836997ae7351a4f56008bbaf4e2801ce4a0", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0", + "illuminate/encryption": "^8.0|^9.0|^10.0|^11.0", + "illuminate/http": "^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "illuminate/validation": "^8.0|^9.0|^10.0|^11.0", + "nesbot/carbon": "^2.0|^3.0", + "php": "^8.0", + "spatie/laravel-package-tools": "^1.9", + "symfony/http-foundation": "^5.1.2|^6.0|^7.0" + }, + "require-dev": { + "livewire/livewire": "^2.10|^3.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", + "pestphp/pest-plugin-livewire": "^1.0|^2.1", + "phpunit/phpunit": "^9.6|^10.5", + "spatie/pest-plugin-snapshots": "^1.1|^2.1", + "spatie/phpunit-snapshot-assertions": "^4.2|^5.1", + "spatie/test-time": "^1.2.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Honeypot\\HoneypotServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\Honeypot\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Preventing spam submitted through forms", + "homepage": "https://github.com/spatie/laravel-honeypot", + "keywords": [ + "laravel-honeypot", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/laravel-honeypot/tree/4.5.3" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], + "time": "2024-09-20T13:45:00+00:00" + }, + { + "name": "spatie/laravel-package-tools", + "version": "1.18.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "ba67eee37d86ed775dab7dad58a7cbaf9a6cfe78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/ba67eee37d86ed775dab7dad58a7cbaf9a6cfe78", + "reference": "ba67eee37d86ed775dab7dad58a7cbaf9a6cfe78", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^9.28|^10.0|^11.0", + "php": "^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "orchestra/testbench": "^7.7|^8.0|^9.0", + "pestphp/pest": "^1.22|^2", + "phpunit/phpunit": "^9.5.24|^10.5", + "spatie/pest-plugin-test-time": "^1.1|^2.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.18.3" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-01-22T08:51:18+00:00" + }, + { + "name": "spatie/laravel-support-bubble", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-support-bubble.git", + "reference": "f00b0219cb18ea939f122e5a6b79a0700d62883d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-support-bubble/zipball/f00b0219cb18ea939f122e5a6b79a0700d62883d", + "reference": "f00b0219cb18ea939f122e5a6b79a0700d62883d", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^8.56|^9.0|^10.0|^11.0", + "php": "^8.0", + "spatie/laravel-honeypot": "^4.0", + "spatie/laravel-package-tools": "^1.9" + }, + "require-dev": { + "brianium/paratest": "^6.2|^7.4", + "nunomaduro/collision": "^5.9|^6.0|^8.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", + "pestphp/pest": "^1.15|^2.34", + "pestphp/pest-plugin-laravel": "^1.1|^2.3", + "phpunit/phpunit": "^9.3|^10.5", + "spatie/laravel-ray": "^1.23", + "spatie/pest-plugin-snapshots": "^1.1|^2.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\SupportBubble\\SupportBubbleServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\SupportBubble\\": "src", + "Spatie\\SupportBubble\\Database\\Factories\\": "database/factories" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "role": "Developer" + }, + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + }, + { + "name": "Ruben Van Assche", + "email": "ruben@spatie.be", + "role": "Developer" + } + ], + "description": "A non-intrusive support chat bubble that can be displayed on any page", + "homepage": "https://github.com/spatie/laravel-support-bubble", + "keywords": [ + "laravel", + "laravel-support-bubble", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-support-bubble/issues", + "source": "https://github.com/spatie/laravel-support-bubble/tree/1.8.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-12-30T09:06:58+00:00" + }, { "name": "symfony/clock", "version": "v7.2.0", diff --git a/config/support-bubble.php b/config/support-bubble.php new file mode 100644 index 0000000..477e89a --- /dev/null +++ b/config/support-bubble.php @@ -0,0 +1,61 @@ + [ + 'name' => true, + 'email' => true, + 'subject' => true, + 'message' => true, + ], + + /* + * All chat bubble responses will be sent to this email address. + */ + 'mail_to' => env("HELP_EMAIL"), + 'mail_from' => null, // by default this is config('mail.from.address') + 'mailer' => null, // by default this is config('mail.default') + + /** + * This queue will be used when sending out mails. + * When set to null, the default queue will be used. + */ + 'queue_name' => null, + + /* + * When set to true use currently logged in user to fill in + * the name and email fields. Both fields will also be hidden. + */ + 'prefill_logged_in_user' => true, + + /* + * The TailwindCSS classes used on a couple of key components. + * + * To customize the components further, you can publish + * the views of this package. + */ + 'classes' => [ + 'container' => 'text-base items-end z-30 flex-col m-4 gap-3', + 'bubble' => 'hidden sm:block | bg-purple-400 rounded-full shadow-lg w-14 h-14 text-white p-4', + 'input' => 'bg-gray-100 border border-gray-200 w-full max-w-full p-2 rounded-sm shadow-input text-gray-800 text-base', + 'button' => 'inline-flex place-center px-4 py-3 h-10 border-0 bg-purple-500 hover:bg-purple-600 active:bg-purple-600 overflow-hidden rounded-sm text-white leading-none no-underline', + ], + + /* + * The default route and controller will be registered using this route name. + * This is a good place to hook in your own route and controller if necessary. + */ + 'form_action_route' => 'supportBubble.submit', + + /** + * The positioning of the bubble and the form, change this between `right-to-left` and `left-to-right`. + * If you want to use RTL, you must have your layout set to RTL like this + * + * By default, the value of this is `left-to-right`. + */ + 'direction' => 'left-to-right' +]; diff --git a/lang/vendor/support-bubble/en/support-bubble.php b/lang/vendor/support-bubble/en/support-bubble.php new file mode 100644 index 0000000..da16c1a --- /dev/null +++ b/lang/vendor/support-bubble/en/support-bubble.php @@ -0,0 +1,13 @@ + '

Do you have a question, suggestion or bug to report?.

If you would like a ranking un-deleted or a ranking\'s ordering updated, ask here and we can help you!

', + + 'success' => 'Thank you for your message. We usually answer within one working day.', + + 'name_label' => 'Name', + 'email_label' => 'E-mail', + 'subject_label' => 'Subject', + 'message_label' => 'How can we help?', + 'submit_label' => 'Submit', +]; diff --git a/resources/js/components/Explore/ExploreItem.vue b/resources/js/components/Explore/ExploreItem.vue index d341c58..88ef92b 100644 --- a/resources/js/components/Explore/ExploreItem.vue +++ b/resources/js/components/Explore/ExploreItem.vue @@ -10,7 +10,7 @@ height="120" :alt="ranking.user.name" > - +
diff --git a/resources/js/components/Explore/Explorer.vue b/resources/js/components/Explore/Explorer.vue index 3ab1a3a..f5cc9c0 100644 --- a/resources/js/components/Explore/Explorer.vue +++ b/resources/js/components/Explore/Explorer.vue @@ -1,62 +1,129 @@ + - \ No newline at end of file + \ No newline at end of file diff --git a/resources/js/components/Profile.vue b/resources/js/components/Profile.vue index 7836f9e..d5b06b0 100644 --- a/resources/js/components/Profile.vue +++ b/resources/js/components/Profile.vue @@ -1,9 +1,12 @@ @@ -61,14 +42,13 @@ export default { name: 'Profile', - props: ['user'], + props: ['user', 'rankings', 'name'], data() { return { - ranks: [], - display_name: "", - profile_name: this.user.name, - no_rankings_msg: "No rankings found. Go make one!" + ranks: this.rankings, + display_name: this.name, + no_rankings_msg: "No rankings found." } }, @@ -112,32 +92,6 @@ return Object.values(ranking.songs).find(song => song.rank === 1)?.title; }, - pageRankings(uri) { - axios.get(uri, { - params: { - spotify_id: this.user.spotify_id - } - }) - .then(response => { - const data = response.data; - - this.display_name = data.name; - - if (data.success == false) { - this.no_rankings_msg = data.message; - return; - } - - if (data.success == true && data.rankings) { - this.ranks = data.rankings; - } - }) - .catch(error => { - this.flash('Error Fetching Rankings', `Could not get rankings for ${this.profile_name} at this time. Please try again later.`, 'error') - console.log(error); - }); - }, - getEditURI(rankingid) { return '/rank/' + rankingid + '/edit'; }, @@ -146,10 +100,6 @@ return ranking.is_public == 0 ? 'No' : 'Yes'; } }, - - mounted() { - this.pageRankings('/ranks/pages'); - } }