diff --git a/.env.example b/.env.example index bcf22c3..57dbc0d 100644 --- a/.env.example +++ b/.env.example @@ -60,7 +60,7 @@ TWITCH_OAUTH_SCOPES="user:read:email" GITHUB_OAUTH_BASE_URI="https://id.twitch.tv/oauth2" -GITHUB_OAUTH_ID="" +GITHUB_OAUTH_ID="6b82a4205e7ce901190b" GITHUB_REDIRECT_URI="http://localhost:8000/auth/oauth/github" GITHUB_OAUTH_SECRET="" GITHUB_OAUTH_SCOPES="user:email" diff --git a/app/Contracts/OAuthContract.php b/app/Contracts/OAuthContract.php new file mode 100644 index 0000000..fdb1c9e --- /dev/null +++ b/app/Contracts/OAuthContract.php @@ -0,0 +1,8 @@ +query('code')) { - return redirect('/'); - } - - $service = new TwitchService(); - $response = $service->twitchAuth($code); - $providerUser = $service->getTwitchUser($response['access_token']); - - $user = $this->findOrCreate('twitch', $providerUser); - Auth::login($user); - - return redirect('/dashboard'); + $this->_repository = $repository; } - public function getGithubProvider(Request $request) - { + public function auth(Request $request, string $provider) { if (!$code = $request->query('code')) { return redirect('/'); } - $service = new GithubService(); - $response = $service->githubAuth($code); - $providerUser = $service->getGithubUser($response['access_token']); - - $user = $this->findOrCreate('github', $providerUser); - Auth::login($user); + $this->_repository->authenticate($provider, $code); return redirect('/dashboard'); } - private function findOrCreate(string $provider, array $providerUser) - { - $payload = [ - $provider . '_username' => $providerUser['login'] ?? $providerUser['data'][0]['login'], - "name" => $providerUser['name'] ?? $providerUser['data'][0]['display_name'], - $provider . "_id" => $providerUser['id'] ?? $providerUser['data'][0]['id'], - "email" => $providerUser['email'] ?? $providerUser['data'][0]['email'], - 'image' => $providerUser['avatar_url'] ?? $providerUser['data'][0]['profile_image_url'] - ]; - - - if ($user = User::where($provider . "_id", $payload[$provider . '_id'])->first()) { - return $user; - } - - if ($user = User::where('email', $payload['email'])->first()) { - $user->update([ - $provider . "_id" => $payload[$provider . '_id'], - $provider . "_username" => $payload[$provider . '_username'] - ]); - - return $user; - } - $imagePath = 'avatars/' . Uuid::uuid4()->toString() . '.png'; - Storage::put('public/' . $imagePath, file_get_contents($payload['image'])); - $payload['image_path'] = $imagePath; - - return User::create($payload); - } - public function getLogout() { - Auth::logout(); + $this->_repository->logout(); return redirect('/'); } diff --git a/app/Http/Controllers/MeController.php b/app/Http/Controllers/MeController.php index b512a94..b38f74e 100644 --- a/app/Http/Controllers/MeController.php +++ b/app/Http/Controllers/MeController.php @@ -2,44 +2,27 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Storage; -use Ramsey\Uuid\Uuid; +use App\Http\Requests\PostAvatarRequest; +use App\Repositories\MeRepository; class MeController extends Controller { - public function __construct() + public function __construct(MeRepository $repository) { $this->middleware('auth'); + $this->_repository = $repository; } - public function postProfileAvatar(Request $request) + public function postProfileAvatar(PostAvatarRequest $request) { - $this->validate($request, [ - 'image' => 'required|image' - ]); - - $file = $request->file('image'); - - $imageName = Uuid::uuid4()->toString() . '.' . $file->getClientOriginalExtension(); - $file->storePubliclyAs('public/avatars', $imageName); - - Storage::delete('public/' . Auth::user()->image_path); - - $imagePath = 'avatars/' . $imageName; - - Auth::user()->update([ - 'image_path' => $imagePath - ]); + $this->_repository->postAvatar($request->file('image')); return response()->json([], 200); } public function deleteMe() { - Auth::user()->delete(); - Auth::logout(); + $this->_repository->delete(); return redirect('/'); } diff --git a/app/Http/Controllers/MessagesController.php b/app/Http/Controllers/MessagesController.php index a0af238..888f549 100644 --- a/app/Http/Controllers/MessagesController.php +++ b/app/Http/Controllers/MessagesController.php @@ -2,27 +2,25 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; +use App\Http\Requests\CreateMessageRequest; +use App\Repositories\MessageRepository; +use Exception; class MessagesController extends Controller { - public function __construct() + public function __construct(MessageRepository $repository) { $this->middleware('auth'); + $this->_repository = $repository; } - public function postMessage(Request $request) + public function postMessage(CreateMessageRequest $request) { - $fields = $this->validate($request, [ - 'content' => 'required', - 'receiver_username' => 'string|nullable' - ]); - - $fields['is_private'] = (bool) $request->input('is_private'); - $fields['receiver_username'] = strtolower($fields['receiver_username']); - - Auth::user()->messages()->create($fields); + try { + $this->_repository->createMessage($request->validated()); + } catch (Exception $e) { + return back()->withErrors($e->getMessage()); + } return back(); } diff --git a/app/Http/Controllers/ViewController.php b/app/Http/Controllers/ViewController.php index 5299368..a0bfc1a 100644 --- a/app/Http/Controllers/ViewController.php +++ b/app/Http/Controllers/ViewController.php @@ -2,32 +2,27 @@ namespace App\Http\Controllers; -use App\Models\Message; -use App\Models\User; +use App\Repositories\ViewRepository; class ViewController extends Controller { - public function __construct() + public function __construct(ViewRepository $repository) { $this->middleware('auth:web', ['except' => 'viewLanding']); + $this->_repository = $repository; } public function viewLanding() { - $users = User::orderByDesc('created_at')->paginate(4); - $registeredUsers = User::count(); - $messagesSent = Message::count(); + [ $users, $registeredUsers, $messagesSent ] = $this->_repository->getLandingContent(); return view('welcome', compact(['users', 'registeredUsers', 'messagesSent'])); } public function viewDashboard() { - $messages = Message::orderByDesc('created_at') - ->where('is_private', false) - ->orWhere('receiver_username', '=', auth()->user()->github_username) - ->orWhere('user_id', auth()->user()->id) - ->paginate(15); + [ $messages ] = $this->_repository->getDashboardContent(); + return view('dashboard', compact('messages')); } diff --git a/app/Http/Requests/CreateMessageRequest.php b/app/Http/Requests/CreateMessageRequest.php new file mode 100644 index 0000000..da55334 --- /dev/null +++ b/app/Http/Requests/CreateMessageRequest.php @@ -0,0 +1,31 @@ + 'required', + 'receiver_username' => 'string|nullable' + ]; + } +} diff --git a/app/Http/Requests/PostAvatarRequest.php b/app/Http/Requests/PostAvatarRequest.php new file mode 100644 index 0000000..cd5d3f8 --- /dev/null +++ b/app/Http/Requests/PostAvatarRequest.php @@ -0,0 +1,30 @@ + 'required|image' + ]; + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 267d47d..38dc62c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -23,8 +23,10 @@ class User extends Authenticatable 'password', 'github_id', 'twitch_id', + 'discord_id', 'github_username', 'twitch_username', + 'discord_username', 'image_path' ]; diff --git a/app/Repositories/AuthRepository.php b/app/Repositories/AuthRepository.php new file mode 100644 index 0000000..fb8054e --- /dev/null +++ b/app/Repositories/AuthRepository.php @@ -0,0 +1,66 @@ +getProvider($provider); + + $response = $service->auth($code); + $providerUser = $service->getAuthenticatedUser($response['access_token']); + + $user = $this->findOrCreate($provider, $providerUser); + Auth::login($user); + } + + public function logout() { + Auth::logout(); + } + + private function getProvider($provider) { + return match ($provider) { + 'twitch' => new TwitchService, + 'github' => new GithubService, + 'discord' => new DiscordService + }; + } + + private function findOrCreate(string $provider, array $providerUser) + { + $payload = [ + $provider . '_username' => $providerUser['login'], + "name" => $providerUser['name'], + $provider . "_id" => $providerUser['id'], + "email" => $providerUser['email'], + 'image' => $providerUser['avatar_url'] + ]; + + if ($user = User::where($provider . "_id", $payload[$provider . '_id'])->first()) { + return $user; + } + + if ($user = User::where('email', $payload['email'])->first()) { + $user->update([ + $provider . "_id" => $payload[$provider . '_id'], + $provider . "_username" => $payload[$provider . '_username'] + ]); + + return $user; + } + $imagePath = 'avatars/' . Uuid::uuid4()->toString() . '.png'; + Storage::put('public/' . $imagePath, file_get_contents($payload['image'])); + $payload['image_path'] = $imagePath; + + return User::create($payload); + } +} \ No newline at end of file diff --git a/app/Repositories/MeRepository.php b/app/Repositories/MeRepository.php new file mode 100644 index 0000000..cdad43c --- /dev/null +++ b/app/Repositories/MeRepository.php @@ -0,0 +1,30 @@ +toString() . '.' . $image->getClientOriginalExtension(); + $image->storePubliclyAs('public/avatars', $imageName); + + Storage::delete('public/' . Auth::user()->image_path); + + $imagePath = 'avatars/' . $imageName; + + Auth::user()->update([ + 'image_path' => $imagePath + ]); + } + + function delete(): void { + Auth::user()->delete(); + Auth::logout(); + } + +} \ No newline at end of file diff --git a/app/Repositories/MessageRepository.php b/app/Repositories/MessageRepository.php new file mode 100644 index 0000000..f478acc --- /dev/null +++ b/app/Repositories/MessageRepository.php @@ -0,0 +1,46 @@ +_service = $service; + } + + public function createMessage($fields): Message { + $fields['is_private'] = isset($fields['receiver_username']); + $fields['receiver_username'] = strtolower($fields['receiver_username']); + + if (!$fields['is_private']) + return Auth::user()->messages()->create($fields); + + $user = $this->_service->findUser($fields['receiver_username']); + + if (empty($user)) + throw new Exception('Github user does not exist.'); + + return Auth::user()->messages()->create($fields); + } + + public function getLatestMessages() { + $messages = Message::orderByDesc('created_at') + ->where('is_private', false) + ->orWhere('receiver_username', '=', auth()->user()->github_username) + ->orWhere('user_id', auth()->user()->id) + ->paginate(15); + + return $messages; + } + + public function getTotalMessagesSent(): int { + return Message::count(); + } + +} \ No newline at end of file diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php new file mode 100644 index 0000000..227015f --- /dev/null +++ b/app/Repositories/UserRepository.php @@ -0,0 +1,17 @@ +paginate(4); + } + + public function getTotalRegisteredUsers(): int { + return User::count(); + } + +} \ No newline at end of file diff --git a/app/Repositories/ViewRepository.php b/app/Repositories/ViewRepository.php new file mode 100644 index 0000000..bf9c547 --- /dev/null +++ b/app/Repositories/ViewRepository.php @@ -0,0 +1,30 @@ +_userRepository = $userRepository; + $this->_messageRepository = $messageRepository; + } + + public function getLandingContent(): array { + return [ + $this->_userRepository->getLatestUsers(), + $this->_userRepository->getTotalRegisteredUsers(), + $this->_messageRepository->getTotalMessagesSent() + ]; + } + + public function getDashboardContent(): array { + return [ + $this->_messageRepository->getLatestMessages() + ]; + } + +} \ No newline at end of file diff --git a/app/Services/OAuth/DiscordService.php b/app/Services/OAuth/DiscordService.php new file mode 100644 index 0000000..113a4c7 --- /dev/null +++ b/app/Services/OAuth/DiscordService.php @@ -0,0 +1,68 @@ +client = new Client([ + 'base_uri' => 'https://discord.com/api/', + 'timeout' => 5.0 + ]); + } + + public function auth(string $code): array + { + $url = "https://discord.com/api/oauth2/token"; + try { + $response = $this->client->request('POST', $url, [ + 'form_params' => [ + 'client_id' => config('providers.discord.client_id'), + 'client_secret' => config('providers.discord.secret'), + 'redirect_uri' => config('providers.discord.redirect_uri'), + 'grant_type' => 'authorization_code', + 'scope' => config('providers.discord.scope'), + 'code' => $code, + ], + 'headers' => [ + 'Accept' => 'application/json' + ] + ]); + + $payload = json_decode($response->getBody(), true); + + return [ + 'access_token' => $payload['access_token'] + ]; + } catch (GuzzleException $exception) { + return ["deu merda: " . $exception->getMessage()]; + } + } + + public function getAuthenticatedUser(string $token): array + { + $uri = 'users/@me'; + $response = $this->client->request('GET', $uri, [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $token + ] + ]); + + $payload = json_decode($response->getBody(), true); + + return [ + 'login' => $payload['username'], + 'name' => $payload['username'], + 'id' => $payload['id'], + 'email' => $payload['email'], + 'avatar_url' => 'https://cdn.discordapp.com/avatars/' . $payload['id'] . '/' . $payload['avatar'] + ]; + } +} diff --git a/app/Services/OAuth/GithubService.php b/app/Services/OAuth/GithubService.php index 0359719..10a133b 100644 --- a/app/Services/OAuth/GithubService.php +++ b/app/Services/OAuth/GithubService.php @@ -2,10 +2,12 @@ namespace App\Services\OAuth; +use App\Contracts\OAuthContract; +use App\Contracts\SocialContract; use GuzzleHttp\Client; use GuzzleHttp\Exception\GuzzleException; -class GithubService +class GithubService implements OAuthContract, SocialContract { private Client $client; @@ -17,27 +19,32 @@ public function __construct() ]); } - public function githubAuth(string $code): array + public function auth(string $code): array { $url = "https://github.com/login/oauth/access_token"; try { $response = $this->client->request('POST', $url, [ 'form_params' => [ - 'client_id' => env('GITHUB_OAUTH_ID'), - 'client_secret' => env('GITHUB_OAUTH_SECRET'), + 'client_id' => config('providers.github.client_id'), + 'client_secret' => config('providers.github.secret'), 'code' => $code, ], 'headers' => [ 'Accept' => 'application/json' ] ]); - return json_decode($response->getBody(), true); + + $payload = json_decode($response->getBody(), true); + + return [ + 'access_token' => $payload['access_token'] + ]; } catch (GuzzleException $exception) { return ["deu merda: " . $exception->getMessage()]; } } - public function getGithubUser(string $token): array + public function getAuthenticatedUser(string $token): array { $uri = "/user"; $response = $this->client->request('GET', $uri, [ @@ -46,6 +53,34 @@ public function getGithubUser(string $token): array ] ]); - return json_decode($response->getBody(), true); + $payload = json_decode($response->getBody(), true); + + return [ + 'login' => $payload['login'], + 'name' => $payload['name'], + 'id' => $payload['id'], + 'email' => $payload['email'] ?? 'teste@teste.com.br', + 'avatar_url' => $payload['avatar_url'] + ]; + } + + public function findUser(string $username): array + { + $uri = "/users/$username"; + + try { + $response = $this->client->request('GET', $uri); + } catch (GuzzleException $e) { + return []; + } + + $payload = json_decode($response->getBody(), true); + + return [ + 'id' => $payload['id'], + 'login' => $payload['login'], + 'avatar_url' => $payload['avatar_url'], + 'email' => $payload['email'] + ]; } } diff --git a/app/Services/OAuth/TwitchService.php b/app/Services/OAuth/TwitchService.php index f3616fd..461ab88 100644 --- a/app/Services/OAuth/TwitchService.php +++ b/app/Services/OAuth/TwitchService.php @@ -2,9 +2,10 @@ namespace App\Services\OAuth; +use App\Contracts\OAuthContract; use GuzzleHttp\Client; -class TwitchService +class TwitchService implements OAuthContract { private Client $client; @@ -16,32 +17,45 @@ public function __construct() ]); } - public function twitchAuth(string $code): array + public function auth(string $code): array { $uri = "https://id.twitch.tv/oauth2/token"; $response = $this->client->request('POST', $uri, [ 'form_params' => [ - 'client_id' => env('TWITCH_OAUTH_ID'), - 'client_secret' => env('TWITCH_OAUTH_SECRET'), + 'client_id' => config('providers.twitch.client_id'), + 'client_secret' => config('providers.twitch.secret'), 'grant_type' => 'authorization_code', 'code' => $code, - 'redirect_uri' => env('TWITCH_REDIRECT_URI') + 'redirect_uri' => config('providers.twitch.redirect_uri') ] ]); - return json_decode($response->getBody(), true); + + $payload = json_decode($response->getBody(), true); + + return [ + 'access_token' => $payload['access_token'] + ]; } - public function getTwitchUser(string $token): array + public function getAuthenticatedUser(string $token): array { $uri = "users"; $response = $this->client->request('GET', $uri, [ 'headers' => [ - 'Client-ID' => env('TWITCH_OAUTH_ID'), + 'Client-ID' => config('providers.twitch.client_id'), 'Authorization' => 'Bearer ' . $token, 'Accept' => 'application/vnd.twitchtv.v5+json' ] ]); - return json_decode($response->getBody(), true); + $payload = json_decode($response->getBody(), true); + + return [ + 'login' => $payload['data'][0]['login'], + 'name' => $payload['data'][0]['display_name'], + 'id' => $payload['data'][0]['id'], + 'email' => $payload['data'][0]['email'], + 'avatar_url' => $payload['data'][0]['profile_image_url'] + ]; } } diff --git a/config/providers.php b/config/providers.php new file mode 100644 index 0000000..5f298bb --- /dev/null +++ b/config/providers.php @@ -0,0 +1,33 @@ + [ + 'name' => 'Twitch', + 'enabled' => env('TWITCH_OAUTH_ENABLED', false), + 'base_uri' => env('TWITCH_OAUTH_BASE_URI'), + 'client_id' => env('TWITCH_OAUTH_ID'), + 'redirect_uri' => env('TWITCH_REDIRECT_URI'), + 'secret' => env('TWITCH_OAUTH_SECRET'), + 'scope' => env('TWITCH_OAUTH_SCOPES') + ], + + 'github' => [ + 'name' => 'Github', + 'enabled' => env('GITHUB_OAUTH_ENABLED', false), + 'base_uri' => env('GITHUB_OAUTH_BASE_URI'), + 'client_id' => env('GITHUB_OAUTH_ID'), + 'redirect_uri' => env('GITHUB_REDIRECT_URI'), + 'secret' => env('GITHUB_OAUTH_SECRET'), + 'scope' => env('GITHUB_OAUTH_SCOPES') + ], + + 'discord' => [ + 'name' => 'Discord', + 'enabled' => env('DISCORD_OAUTH_ENABLED', false), + 'base_uri' => env('DISCORD_OAUTH_BASE_URI'), + 'client_id' => env('DISCORD_OAUTH_ID'), + 'redirect_uri' => env('DISCORD_REDIRECT_URI'), + 'secret' => env('DISCORD_OAUTH_SECRET'), + 'scope' => env('DISCORD_OAUTH_SCOPES') + ] +]; \ No newline at end of file diff --git a/database/migrations/2021_11_15_004734_add_discord_id_to_users_table.php b/database/migrations/2021_11_15_004734_add_discord_id_to_users_table.php new file mode 100644 index 0000000..0ddf77a --- /dev/null +++ b/database/migrations/2021_11_15_004734_add_discord_id_to_users_table.php @@ -0,0 +1,32 @@ +string('discord_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('discord_id'); + }); + } +} diff --git a/database/migrations/2021_11_15_004851_add_discord_username_to_users_table.php b/database/migrations/2021_11_15_004851_add_discord_username_to_users_table.php new file mode 100644 index 0000000..1b9ed95 --- /dev/null +++ b/database/migrations/2021_11_15_004851_add_discord_username_to_users_table.php @@ -0,0 +1,32 @@ +string('discord_username')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('discord_username'); + }); + } +} diff --git a/resources/views/components/template.blade.php b/resources/views/components/template.blade.php index 0b2023c..11727f0 100644 --- a/resources/views/components/template.blade.php +++ b/resources/views/components/template.blade.php @@ -28,8 +28,14 @@ diff --git a/resources/views/profile.blade.php b/resources/views/profile.blade.php index f20c611..768282b 100644 --- a/resources/views/profile.blade.php +++ b/resources/views/profile.blade.php @@ -62,6 +62,20 @@ +
+
+
+ + +
+
+
+
+ + +
+
+
@csrf @method('DELETE') diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php index bb1772a..ea813af 100644 --- a/resources/views/welcome.blade.php +++ b/resources/views/welcome.blade.php @@ -64,6 +64,11 @@ @endif + + @if($user->discord_id) + + + @endif

@endforeach diff --git a/routes/web.php b/routes/web.php index 96c2533..1b87750 100644 --- a/routes/web.php +++ b/routes/web.php @@ -26,8 +26,7 @@ // Auth Routes -Route::get('/auth/oauth/twitch', [AuthController::class, 'getTwitchProvider']); -Route::get('/auth/oauth/github', [AuthController::class, 'getGithubProvider']); +Route::get('/auth/oauth/{provider}', [AuthController::class, 'auth']); Route::get('/auth/logout', [AuthController::class, 'getLogout'])->name('logout'); // User Routes