├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── .styleci.yml ├── README.md ├── app ├── Actions │ ├── Fortify │ │ ├── CreateNewUser.php │ │ ├── PasswordValidationRules.php │ │ ├── ResetUserPassword.php │ │ ├── UpdateUserPassword.php │ │ └── UpdateUserProfileInformation.php │ └── Jetstream │ │ └── DeleteUser.php ├── Console │ └── Kernel.php ├── Events │ ├── FriendRequestAcceptedEvent.php │ ├── FriendRequestReceivedEvent.php │ ├── NewChatMessageEvent.php │ └── SomeonePostedEvent.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── Controller.php │ │ ├── User │ │ │ ├── CommentController.php │ │ │ ├── CommentLikeController.php │ │ │ ├── DashboardController.php │ │ │ ├── FriendController.php │ │ │ ├── MemberController.php │ │ │ ├── NotificationController.php │ │ │ ├── PostController.php │ │ │ ├── PostLikeController.php │ │ │ ├── ProfileController.php │ │ │ └── RoomController.php │ │ └── WelcomeController.php │ ├── Kernel.php │ ├── Middleware │ │ ├── Authenticate.php │ │ ├── EncryptCookies.php │ │ ├── HandleInertiaRequests.php │ │ ├── PreventRequestsDuringMaintenance.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── TrimStrings.php │ │ ├── TrustHosts.php │ │ ├── TrustProxies.php │ │ └── VerifyCsrfToken.php │ └── Requests │ │ └── PostFormRequest.php ├── Listeners │ ├── FriendRequestAcceptedListener.php │ ├── FriendRequestReceivedListener.php │ └── SomeonePostedListener.php ├── Models │ ├── Comment.php │ ├── Friend.php │ ├── Like.php │ ├── Message.php │ ├── Post.php │ ├── Profile.php │ ├── Room.php │ └── User.php ├── Notifications │ ├── FriendRequestAccepted.php │ ├── FriendRequestReceived.php │ └── SomeonePosted.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ ├── FortifyServiceProvider.php │ ├── JetstreamServiceProvider.php │ └── RouteServiceProvider.php └── Traits │ ├── Friendable.php │ └── Likeable.php ├── artisan ├── bootstrap ├── app.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── cors.php ├── database.php ├── filesystems.php ├── fortify.php ├── hashing.php ├── jetstream.php ├── logging.php ├── mail.php ├── queue.php ├── sanctum.php ├── services.php ├── session.php └── view.php ├── database ├── .gitignore ├── factories │ ├── CommentFactory.php │ ├── PostFactory.php │ ├── ProfileFactory.php │ ├── RoomFactory.php │ └── UserFactory.php ├── migrations │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2014_10_12_200000_add_two_factor_columns_to_users_table.php │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ ├── 2019_12_14_000001_create_personal_access_tokens_table.php │ ├── 2021_02_07_023232_create_sessions_table.php │ ├── 2021_02_10_030125_create_profiles_table.php │ ├── 2021_02_18_034602_create_friends_table.php │ ├── 2021_02_22_040444_create_posts_table.php │ ├── 2021_02_25_025722_create_likes_table.php │ ├── 2021_02_26_030724_create_comments_table.php │ ├── 2021_03_03_013938_create_notifications_table.php │ ├── 2021_03_06_022301_create_jobs_table.php │ ├── 2021_03_07_025508_create_rooms_table.php │ └── 2021_03_09_032244_create_messages_table.php └── seeders │ └── DatabaseSeeder.php ├── package-lock.json ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── css │ ├── app.css │ └── app.css.map ├── favicon.ico ├── index.php ├── js │ ├── app.js │ ├── app.js.LICENSE.txt │ └── app.js.map ├── mix-manifest.json ├── robots.txt └── web.config ├── resources ├── css │ └── app.css ├── js │ ├── Components │ │ ├── Buttons │ │ │ ├── BlueButton.vue │ │ │ └── GreenButton.vue │ │ ├── Chat │ │ │ ├── ChatActives.vue │ │ │ ├── ChatBox.vue │ │ │ └── ChatInput.vue │ │ ├── FriendStatus │ │ │ ├── Accept.vue │ │ │ ├── Ignore.vue │ │ │ └── Status.vue │ │ ├── Icon.vue │ │ ├── InfiniteScroll.vue │ │ ├── NotificationItem.vue │ │ ├── Notifications.vue │ │ ├── PostComment │ │ │ ├── CombinedComments.vue │ │ │ ├── CombinedPosts.vue │ │ │ ├── CommentItem.vue │ │ │ ├── Likes │ │ │ │ ├── Dislike.vue │ │ │ │ └── Like.vue │ │ │ ├── PostForm.vue │ │ │ └── PostItem.vue │ │ ├── SideBar.vue │ │ ├── SuggestionBlock.vue │ │ └── UserBlock.vue │ ├── Jetstream │ │ ├── ActionMessage.vue │ │ ├── ActionSection.vue │ │ ├── ApplicationLogo.vue │ │ ├── ApplicationMark.vue │ │ ├── AuthenticationCard.vue │ │ ├── AuthenticationCardLogo.vue │ │ ├── Banner.vue │ │ ├── Button.vue │ │ ├── Checkbox.vue │ │ ├── ConfirmationModal.vue │ │ ├── ConfirmsPassword.vue │ │ ├── DangerButton.vue │ │ ├── DialogModal.vue │ │ ├── Dropdown.vue │ │ ├── DropdownLink.vue │ │ ├── FormSection.vue │ │ ├── Input.vue │ │ ├── InputError.vue │ │ ├── Label.vue │ │ ├── Modal.vue │ │ ├── NavLink.vue │ │ ├── ResponsiveNavLink.vue │ │ ├── SecondaryButton.vue │ │ ├── SectionBorder.vue │ │ ├── SectionTitle.vue │ │ └── ValidationErrors.vue │ ├── Layouts │ │ ├── AppLayout.vue │ │ └── PagesLayout.vue │ ├── Pages │ │ ├── API │ │ │ ├── ApiTokenManager.vue │ │ │ └── Index.vue │ │ ├── Auth │ │ │ ├── ConfirmPassword.vue │ │ │ ├── ForgotPassword.vue │ │ │ ├── Login.vue │ │ │ ├── Register.vue │ │ │ ├── ResetPassword.vue │ │ │ ├── TwoFactorChallenge.vue │ │ │ └── VerifyEmail.vue │ │ ├── Dashboard.vue │ │ ├── PrivacyPolicy.vue │ │ ├── Profile │ │ │ ├── DeleteUserForm.vue │ │ │ ├── LogoutOtherBrowserSessionsForm.vue │ │ │ ├── Show.vue │ │ │ ├── TwoFactorAuthenticationForm.vue │ │ │ ├── UpdatePasswordForm.vue │ │ │ └── UpdateProfileInformationForm.vue │ │ ├── TermsOfService.vue │ │ ├── User │ │ │ ├── ChatRooms │ │ │ │ ├── Index.vue │ │ │ │ └── Show.vue │ │ │ ├── Friends │ │ │ │ └── Index.vue │ │ │ ├── Members │ │ │ │ └── Index.vue │ │ │ └── Profile │ │ │ │ └── Show.vue │ │ └── Welcome.vue │ ├── app.js │ └── bootstrap.js ├── lang │ └── en │ │ ├── auth.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php ├── markdown │ ├── policy.md │ └── terms.md └── views │ ├── app.blade.php │ └── mail │ ├── friendrequest │ ├── accepted.blade.php │ └── received.blade.php │ └── posts │ └── posted.blade.php ├── routes ├── api.php ├── channels.php ├── console.php └── web.php ├── server.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tailwind.config.js ├── tests ├── CreatesApplication.php ├── Feature │ ├── ApiTokenPermissionsTest.php │ ├── AuthenticationTest.php │ ├── BrowserSessionsTest.php │ ├── CreateApiTokenTest.php │ ├── DeleteAccountTest.php │ ├── DeleteApiTokenTest.php │ ├── EmailVerificationTest.php │ ├── ExampleTest.php │ ├── PasswordConfirmationTest.php │ ├── PasswordResetTest.php │ ├── ProfileInformationTest.php │ ├── RegistrationTest.php │ ├── TwoFactorAuthenticationSettingsTest.php │ └── UpdatePasswordTest.php ├── TestCase.php └── Unit │ └── ExampleTest.php ├── webpack.config.js └── webpack.mix.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME="Social Network" 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost:8000 6 | 7 | LOG_CHANNEL=stack 8 | LOG_LEVEL=debug 9 | 10 | DB_CONNECTION=mysql 11 | DB_HOST=127.0.0.1 12 | DB_PORT=3306 13 | DB_DATABASE= 14 | DB_USERNAME= 15 | DB_PASSWORD= 16 | 17 | BROADCAST_DRIVER=pusher 18 | CACHE_DRIVER=file 19 | QUEUE_CONNECTION=database 20 | SESSION_DRIVER=database 21 | SESSION_LIFETIME=120 22 | 23 | MEMCACHED_HOST=127.0.0.1 24 | 25 | REDIS_HOST=127.0.0.1 26 | REDIS_PASSWORD=null 27 | REDIS_PORT=6379 28 | 29 | MAIL_MAILER=smtp 30 | MAIL_HOST=smtp.mailtrap.io 31 | MAIL_PORT=2525 32 | MAIL_USERNAME=null 33 | MAIL_PASSWORD=null 34 | MAIL_ENCRYPTION=tls 35 | MAIL_FROM_ADDRESS=null 36 | MAIL_FROM_NAME="${APP_NAME}" 37 | 38 | AWS_ACCESS_KEY_ID= 39 | AWS_SECRET_ACCESS_KEY= 40 | AWS_DEFAULT_REGION=us-east-1 41 | AWS_BUCKET= 42 | 43 | PUSHER_APP_ID= 44 | PUSHER_APP_KEY= 45 | PUSHER_APP_SECRET= 46 | PUSHER_APP_CLUSTER=mt1 47 | 48 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 49 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 50 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/hot 3 | /public/storage 4 | /storage/*.key 5 | /vendor 6 | .env 7 | .env.backup 8 | .phpunit.result.cache 9 | docker-compose.override.yml 10 | Homestead.json 11 | Homestead.yaml 12 | npm-debug.log 13 | yarn-error.log 14 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | php: 2 | preset: laravel 3 | disabled: 4 | - no_unused_imports 5 | finder: 6 | not-name: 7 | - index.php 8 | - server.php 9 | js: 10 | finder: 11 | not-name: 12 | - webpack.mix.js 13 | css: true 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Social Network Series on Youtube 2 | 3 | Social Network built with Laravel 8, Jetstream, Fortify, Inertia, Tailwind CSS 4 | 5 | [Introduction Episode] 6 | 7 | The Vault Intro 10 | -------------------------------------------------------------------------------- /app/Actions/Fortify/CreateNewUser.php: -------------------------------------------------------------------------------- 1 | ['required', 'string', 'max:100'], 27 | 'username' => ['required', 'string', 'max:50'], 28 | 'email' => ['required', 'string', 'email', 'max:100', 'unique:users'], 29 | 'password' => $this->passwordRules(), 30 | 'gender' => ['required'], 31 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['required', 'accepted'] : '', 32 | ])->validate(); 33 | 34 | $user = User::create([ 35 | 'name' => $input['name'], 36 | 'email' => $input['email'], 37 | 'username' => $input['username'], 38 | 'password' => Hash::make($input['password']), 39 | ]); 40 | 41 | Profile::create([ 42 | 'user_id' => $user->id, 43 | 'slug' => Str::of($user->name)->slug('-'), 44 | 'gender' => $input['gender'], 45 | ]); 46 | 47 | return $user; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/Actions/Fortify/PasswordValidationRules.php: -------------------------------------------------------------------------------- 1 | $this->passwordRules(), 24 | ])->validate(); 25 | 26 | $user->forceFill([ 27 | 'password' => Hash::make($input['password']), 28 | ])->save(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Actions/Fortify/UpdateUserPassword.php: -------------------------------------------------------------------------------- 1 | ['required', 'string'], 24 | 'password' => $this->passwordRules(), 25 | ])->after(function ($validator) use ($user, $input) { 26 | if (! isset($input['current_password']) || ! Hash::check($input['current_password'], $user->password)) { 27 | $validator->errors()->add('current_password', __('The provided password does not match your current password.')); 28 | } 29 | })->validateWithBag('updatePassword'); 30 | 31 | $user->forceFill([ 32 | 'password' => Hash::make($input['password']), 33 | ])->save(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Actions/Fortify/UpdateUserProfileInformation.php: -------------------------------------------------------------------------------- 1 | ['required', 'string', 'max:100'], 23 | 'username' => ['required', 'string', 'max:50'], 24 | 'email' => ['required', 'email', 'max:100', Rule::unique('users')->ignore($user->id)], 25 | 'photo' => ['nullable', 'image', 'max:1024'], 26 | ])->validateWithBag('updateProfileInformation'); 27 | 28 | if (isset($input['photo'])) { 29 | $user->updateProfilePhoto($input['photo']); 30 | } 31 | 32 | if ($input['email'] !== $user->email && 33 | $user instanceof MustVerifyEmail) { 34 | $this->updateVerifiedUser($user, $input); 35 | } else { 36 | $user->forceFill([ 37 | 'name' => $input['name'], 38 | 'username' => $input['username'], 39 | 'email' => $input['email'], 40 | ])->save(); 41 | $user->profile->forceFill([ 42 | 'gender' => $input['gender'], 43 | ])->save(); 44 | } 45 | } 46 | 47 | /** 48 | * Update the given verified user's profile information. 49 | * 50 | * @param mixed $user 51 | * @param array $input 52 | * @return void 53 | */ 54 | protected function updateVerifiedUser($user, array $input) 55 | { 56 | $user->forceFill([ 57 | 'name' => $input['name'], 58 | 'email' => $input['email'], 59 | 'email_verified_at' => null, 60 | ])->save(); 61 | 62 | $user->sendEmailVerificationNotification(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/DeleteUser.php: -------------------------------------------------------------------------------- 1 | deleteProfilePhoto(); 18 | $user->tokens->each->delete(); 19 | $user->delete(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire')->hourly(); 28 | } 29 | 30 | /** 31 | * Register the commands for the application. 32 | * 33 | * @return void 34 | */ 35 | protected function commands() 36 | { 37 | $this->load(__DIR__.'/Commands'); 38 | 39 | require base_path('routes/console.php'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Events/FriendRequestAcceptedEvent.php: -------------------------------------------------------------------------------- 1 | user = $user; 26 | } 27 | 28 | /** 29 | * Get the channels the event should broadcast on. 30 | * 31 | * @return \Illuminate\Broadcasting\Channel|array 32 | */ 33 | public function broadcastOn() 34 | { 35 | return new PrivateChannel('channel-name'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Events/FriendRequestReceivedEvent.php: -------------------------------------------------------------------------------- 1 | user = $user; 26 | } 27 | 28 | /** 29 | * Get the channels the event should broadcast on. 30 | * 31 | * @return \Illuminate\Broadcasting\Channel|array 32 | */ 33 | public function broadcastOn() 34 | { 35 | return new PrivateChannel('channel-name'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Events/NewChatMessageEvent.php: -------------------------------------------------------------------------------- 1 | message = $message; 28 | $this->user = $user; 29 | } 30 | 31 | /** 32 | * Get the channels the event should broadcast on. 33 | * 34 | * @return \Illuminate\Broadcasting\Channel|array 35 | */ 36 | public function broadcastOn() { 37 | return new PresenceChannel('chat.'.$this->message->room_id); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Events/SomeonePostedEvent.php: -------------------------------------------------------------------------------- 1 | user = $user; 26 | } 27 | 28 | /** 29 | * Get the channels the event should broadcast on. 30 | * 31 | * @return \Illuminate\Broadcasting\Channel|array 32 | */ 33 | public function broadcastOn() 34 | { 35 | return new PrivateChannel('channel-name'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | reportable(function (Throwable $e) { 37 | // 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | user()->like($comment); 40 | } 41 | 42 | /** 43 | * Display the specified resource. 44 | * 45 | * @param int $id 46 | * @return \Illuminate\Http\Response 47 | */ 48 | public function show($id) 49 | { 50 | // 51 | } 52 | 53 | /** 54 | * Show the form for editing the specified resource. 55 | * 56 | * @param int $id 57 | * @return \Illuminate\Http\Response 58 | */ 59 | public function edit($id) 60 | { 61 | // 62 | } 63 | 64 | /** 65 | * Update the specified resource in storage. 66 | * 67 | * @param \Illuminate\Http\Request $request 68 | * @param int $id 69 | * @return \Illuminate\Http\Response 70 | */ 71 | public function update(Request $request, $id) 72 | { 73 | // 74 | } 75 | 76 | /** 77 | * Remove the specified resource from storage. 78 | * 79 | * @param \App\Models\Comment $comment 80 | * @return \Illuminate\Http\Response 81 | */ 82 | public function destroy(Comment $comment) { 83 | return auth()->user()->dislike($comment); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/Http/Controllers/User/DashboardController.php: -------------------------------------------------------------------------------- 1 | latest()->paginate(5); 20 | if ($request->wantsJson()) { 21 | return $combinedPosts; 22 | } 23 | return Inertia::render('Dashboard', [ 24 | 'combinedPosts' => $combinedPosts, 25 | 'suggestions' => User::suggestions()->take(5)->inRandomOrder()->get(), 26 | ]); 27 | } 28 | 29 | /** 30 | * Show the form for creating a new resource. 31 | * 32 | * @return \Illuminate\Http\Response 33 | */ 34 | public function create() 35 | { 36 | // 37 | } 38 | 39 | /** 40 | * Store a newly created resource in storage. 41 | * 42 | * @param \Illuminate\Http\Request $request 43 | * @return \Illuminate\Http\Response 44 | */ 45 | public function store(Request $request) 46 | { 47 | // 48 | } 49 | 50 | /** 51 | * Display the specified resource. 52 | * 53 | * @param int $id 54 | * @return \Illuminate\Http\Response 55 | */ 56 | public function show($id) 57 | { 58 | // 59 | } 60 | 61 | /** 62 | * Show the form for editing the specified resource. 63 | * 64 | * @param int $id 65 | * @return \Illuminate\Http\Response 66 | */ 67 | public function edit($id) 68 | { 69 | // 70 | } 71 | 72 | /** 73 | * Update the specified resource in storage. 74 | * 75 | * @param \Illuminate\Http\Request $request 76 | * @param int $id 77 | * @return \Illuminate\Http\Response 78 | */ 79 | public function update(Request $request, $id) 80 | { 81 | // 82 | } 83 | 84 | /** 85 | * Remove the specified resource from storage. 86 | * 87 | * @param int $id 88 | * @return \Illuminate\Http\Response 89 | */ 90 | public function destroy($id) 91 | { 92 | // 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/Http/Controllers/User/MemberController.php: -------------------------------------------------------------------------------- 1 | paginate(20); 19 | if ($request->wantsJson()) { 20 | return $members; 21 | } 22 | return Inertia::render('User/Members/Index', [ 23 | 'members' => $members, 24 | ]); 25 | } 26 | 27 | /** 28 | * Show the form for creating a new resource. 29 | * 30 | * @return \Illuminate\Http\Response 31 | */ 32 | public function create() 33 | { 34 | // 35 | } 36 | 37 | /** 38 | * Store a newly created resource in storage. 39 | * 40 | * @param \Illuminate\Http\Request $request 41 | * @return \Illuminate\Http\Response 42 | */ 43 | public function store(Request $request) 44 | { 45 | // 46 | } 47 | 48 | /** 49 | * Display the specified resource. 50 | * 51 | * @param int $id 52 | * @return \Illuminate\Http\Response 53 | */ 54 | public function show($id) 55 | { 56 | // 57 | } 58 | 59 | /** 60 | * Show the form for editing the specified resource. 61 | * 62 | * @param int $id 63 | * @return \Illuminate\Http\Response 64 | */ 65 | public function edit($id) 66 | { 67 | // 68 | } 69 | 70 | /** 71 | * Update the specified resource in storage. 72 | * 73 | * @param \Illuminate\Http\Request $request 74 | * @param int $id 75 | * @return \Illuminate\Http\Response 76 | */ 77 | public function update(Request $request, $id) 78 | { 79 | // 80 | } 81 | 82 | /** 83 | * Remove the specified resource from storage. 84 | * 85 | * @param int $id 86 | * @return \Illuminate\Http\Response 87 | */ 88 | public function destroy($id) 89 | { 90 | // 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/Http/Controllers/User/NotificationController.php: -------------------------------------------------------------------------------- 1 | user()->unreadNotifications->find($id); 39 | return $notification->markAsRead(); 40 | } 41 | 42 | /** 43 | * Display the specified resource. 44 | * 45 | * @param int $id 46 | * @return \Illuminate\Http\Response 47 | */ 48 | public function show($id) 49 | { 50 | // 51 | } 52 | 53 | /** 54 | * Show the form for editing the specified resource. 55 | * 56 | * @param int $id 57 | * @return \Illuminate\Http\Response 58 | */ 59 | public function edit($id) 60 | { 61 | // 62 | } 63 | 64 | /** 65 | * Update the specified resource in storage. 66 | * 67 | * @param \Illuminate\Http\Request $request 68 | * @return \Illuminate\Http\Response 69 | */ 70 | public function update(Request $request) { 71 | $notifications = auth()->user()->unreadNotifications; 72 | $notifications->markAsRead(); 73 | return back(); 74 | } 75 | 76 | /** 77 | * Remove the specified resource from storage. 78 | * 79 | * @param int $id 80 | * @return \Illuminate\Http\Response 81 | */ 82 | public function destroy($id) { 83 | $notification = auth()->user()->notifications->find($id); 84 | $notification->delete(); 85 | return back(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/Http/Controllers/User/PostLikeController.php: -------------------------------------------------------------------------------- 1 | user()->like($post); 40 | } 41 | 42 | /** 43 | * Display the specified resource. 44 | * 45 | * @param \App\Models\Post $post 46 | * @return \Illuminate\Http\Response 47 | */ 48 | public function show(Post $post) 49 | { 50 | // 51 | } 52 | 53 | /** 54 | * Show the form for editing the specified resource. 55 | * 56 | * @param \App\Models\Post $post 57 | * @return \Illuminate\Http\Response 58 | */ 59 | public function edit(Post $post) 60 | { 61 | // 62 | } 63 | 64 | /** 65 | * Update the specified resource in storage. 66 | * 67 | * @param \Illuminate\Http\Request $request 68 | * @param \App\Models\Post $post 69 | * @return \Illuminate\Http\Response 70 | */ 71 | public function update(Request $request, Post $post) 72 | { 73 | // 74 | } 75 | 76 | /** 77 | * Remove the specified resource from storage. 78 | * 79 | * @param \App\Models\Post $post 80 | * @return \Illuminate\Http\Response 81 | */ 82 | public function destroy(Post $post) { 83 | return auth()->user()->dislike($post); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/Http/Controllers/WelcomeController.php: -------------------------------------------------------------------------------- 1 | User::count(), 52 | 'canLogin' => Route::has('login'), 53 | 'canRegister' => Route::has('register'), 54 | 'laravelVersion' => Application::VERSION, 55 | 'phpVersion' => PHP_VERSION, 56 | ]); 57 | } 58 | 59 | /** 60 | * Show the form for editing the specified resource. 61 | * 62 | * @param int $id 63 | * @return \Illuminate\Http\Response 64 | */ 65 | public function edit($id) 66 | { 67 | // 68 | } 69 | 70 | /** 71 | * Update the specified resource in storage. 72 | * 73 | * @param \Illuminate\Http\Request $request 74 | * @param int $id 75 | * @return \Illuminate\Http\Response 76 | */ 77 | public function update(Request $request, $id) 78 | { 79 | // 80 | } 81 | 82 | /** 83 | * Remove the specified resource from storage. 84 | * 85 | * @param int $id 86 | * @return \Illuminate\Http\Response 87 | */ 88 | public function destroy($id) 89 | { 90 | // 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/Http/Kernel.php: -------------------------------------------------------------------------------- 1 | [ 33 | \App\Http\Middleware\EncryptCookies::class, 34 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 35 | \Illuminate\Session\Middleware\StartSession::class, 36 | \Laravel\Jetstream\Http\Middleware\AuthenticateSession::class, 37 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 38 | \App\Http\Middleware\VerifyCsrfToken::class, 39 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 40 | \App\Http\Middleware\HandleInertiaRequests::class, 41 | ], 42 | 43 | 'api' => [ 44 | 'throttle:api', 45 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 46 | ], 47 | ]; 48 | 49 | /** 50 | * The application's route middleware. 51 | * 52 | * These middleware may be assigned to groups or used individually. 53 | * 54 | * @var array 55 | */ 56 | protected $routeMiddleware = [ 57 | 'auth' => \App\Http\Middleware\Authenticate::class, 58 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 59 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 60 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 61 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 62 | 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 63 | 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 64 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 65 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 66 | ]; 67 | } 68 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 18 | return route('login'); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | function() { 40 | $user = auth()->user(); 41 | return $user ? [ 42 | 'profile' => $user->profile, 43 | 'notifications' => $user->notifications, 44 | 'readNotifications' => $user->readNotifications, 45 | 'unreadNotifications'=> $user->unreadNotifications, 46 | ] : null; 47 | } 48 | ]); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Http/Middleware/PreventRequestsDuringMaintenance.php: -------------------------------------------------------------------------------- 1 | check()) { 26 | return redirect(RouteServiceProvider::HOME); 27 | } 28 | } 29 | 30 | return $next($request); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | allSubdomainsOfApplicationUrl(), 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustProxies.php: -------------------------------------------------------------------------------- 1 | 'required' 26 | ]; 27 | } 28 | 29 | /** 30 | * Get the error messages for the defined validation rules. 31 | * 32 | * @return array 33 | */ 34 | public function messages() { 35 | return [ 36 | 'body.required' => 'A message is required', 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Listeners/FriendRequestAcceptedListener.php: -------------------------------------------------------------------------------- 1 | user->notify(new FriendRequestAccepted(auth()->user())); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Listeners/FriendRequestReceivedListener.php: -------------------------------------------------------------------------------- 1 | user->notify(new FriendRequestReceived(auth()->user())); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Listeners/SomeonePostedListener.php: -------------------------------------------------------------------------------- 1 | user->notify(new SomeonePosted(auth()->user())); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Models/Comment.php: -------------------------------------------------------------------------------- 1 | created_at->diffForHumans(); 32 | } 33 | 34 | public function getLikedAttribute() { 35 | return $this->likes()->where('like', 1) 36 | ->where('likeable_id', $this->id) 37 | ->where('likeable_type', get_class($this)) 38 | ->count(); 39 | } 40 | 41 | public function getdislikedAttribute() { 42 | return $this->likes()->where('dislike', 1) 43 | ->where('likeable_id', $this->id) 44 | ->where('likeable_type', get_class($this)) 45 | ->count(); 46 | } 47 | 48 | public function post() { 49 | return $this->belongsTo(Post::class); 50 | } 51 | 52 | public function user() { 53 | return $this->belongsTo(User::class); 54 | } 55 | 56 | public function likes() { 57 | return $this->morphMany(Like::class, 'likeable'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/Models/Friend.php: -------------------------------------------------------------------------------- 1 | morphTo(); 22 | } 23 | 24 | public function user() { 25 | return $this->belongsTo(User::class); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Models/Message.php: -------------------------------------------------------------------------------- 1 | created_at->diffForHumans(); 23 | } 24 | 25 | public function user() { 26 | return $this->belongsTo(User::class); 27 | } 28 | 29 | public function room() { 30 | return $this->belongsTo(Room::class); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Models/Post.php: -------------------------------------------------------------------------------- 1 | created_at->diffForHumans(); 32 | } 33 | 34 | public function getLikedAttribute() { 35 | return $this->likes()->where('like', 1) 36 | ->where('likeable_id', $this->id) 37 | ->where('likeable_type', get_class($this)) 38 | ->count(); 39 | } 40 | 41 | public function getdislikedAttribute() { 42 | return $this->likes()->where('dislike', 1) 43 | ->where('likeable_id', $this->id) 44 | ->where('likeable_type', get_class($this)) 45 | ->count(); 46 | } 47 | 48 | public function scopeAllPosts($query) { 49 | return $query->where('user_id', auth()->id()) 50 | ->orWhereIn('user_id', auth()->user()->friends_ids()); 51 | } 52 | 53 | public function user() { 54 | return $this->belongsTo(User::class); 55 | } 56 | 57 | public function likes() { 58 | return $this->morphMany(Like::class, 'likeable'); 59 | } 60 | 61 | public function comments() { 62 | return $this->hasMany(Comment::class); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/Models/Profile.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Models/Room.php: -------------------------------------------------------------------------------- 1 | hasMany(Message::class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | 'datetime', 56 | ]; 57 | 58 | /** 59 | * The accessors to append to the model's array form. 60 | * 61 | * @var array 62 | */ 63 | protected $appends = [ 64 | 'profile_photo_url', 65 | ]; 66 | 67 | /** 68 | * Get the default profile photo URL if no profile photo has been uploaded. 69 | * 70 | * @return string 71 | */ 72 | protected function defaultProfilePhotoUrl() { 73 | return asset('/storage/images/default.png'); 74 | } 75 | 76 | public function scopeSuggestions($query) { 77 | return $query->notAuth()->orWhereIn('id', auth()->user()->friends_ids()); 78 | } 79 | 80 | public function scopeNotAuth() { 81 | return $this->where('id', '!=', auth()->id()); 82 | } 83 | 84 | public function profile() { 85 | return $this->hasOne(Profile::class); 86 | } 87 | 88 | public function posts() { 89 | return $this->hasMany(Post::class); 90 | } 91 | 92 | public function comments() { 93 | return $this->hasMany(Comment::class); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/Notifications/FriendRequestAccepted.php: -------------------------------------------------------------------------------- 1 | user = $user; 24 | } 25 | 26 | /** 27 | * Get the notification's delivery channels. 28 | * 29 | * @param mixed $notifiable 30 | * @return array 31 | */ 32 | public function via($notifiable) { 33 | return ['mail', 'database', 'broadcast']; 34 | } 35 | 36 | /** 37 | * Get the mail representation of the notification. 38 | * 39 | * @param mixed $notifiable 40 | * @return \Illuminate\Notifications\Messages\MailMessage 41 | */ 42 | public function toMail($notifiable) { 43 | return (new MailMessage)->markdown('mail.friendrequest.accepted', [ 44 | 'user' => $this->user, 45 | ]); 46 | } 47 | 48 | /** 49 | * Get the array representation of the notification. 50 | * 51 | * @param mixed $notifiable 52 | * @return array 53 | */ 54 | public function toArray($notifiable) { 55 | return [ 56 | 'info' => [ 57 | 'message' => $this->user->username." has accepted your friend request.", 58 | 'link' => route('profiles.show', $this->user->username), 59 | 'avatar' => $this->user->profile_photo_url, 60 | 'sent' => Carbon::now() 61 | ] 62 | ]; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/Notifications/FriendRequestReceived.php: -------------------------------------------------------------------------------- 1 | user = $user; 24 | } 25 | 26 | /** 27 | * Get the notification's delivery channels. 28 | * 29 | * @param mixed $notifiable 30 | * @return array 31 | */ 32 | public function via($notifiable) { 33 | return ['mail', 'database', 'broadcast']; 34 | } 35 | 36 | /** 37 | * Get the mail representation of the notification. 38 | * 39 | * @param mixed $notifiable 40 | * @return \Illuminate\Notifications\Messages\MailMessage 41 | */ 42 | public function toMail($notifiable) { 43 | return (new MailMessage)->markdown('mail.friendrequest.received', [ 44 | 'user' => $this->user, 45 | ]); 46 | } 47 | 48 | /** 49 | * Get the array representation of the notification. 50 | * 51 | * @param mixed $notifiable 52 | * @return array 53 | */ 54 | public function toArray($notifiable) { 55 | return [ 56 | 'info' => [ 57 | 'message' => "You have received a friend request from ".$this->user->username.".", 58 | 'link' => route('profiles.show', $this->user->username), 59 | 'avatar' => $this->user->profile_photo_url, 60 | 'sent' => Carbon::now() 61 | ] 62 | ]; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/Notifications/SomeonePosted.php: -------------------------------------------------------------------------------- 1 | user = $user; 24 | } 25 | 26 | /** 27 | * Get the notification's delivery channels. 28 | * 29 | * @param mixed $notifiable 30 | * @return array 31 | */ 32 | public function via($notifiable) { 33 | return ['mail', 'database', 'broadcast']; 34 | } 35 | 36 | /** 37 | * Get the mail representation of the notification. 38 | * 39 | * @param mixed $notifiable 40 | * @return \Illuminate\Notifications\Messages\MailMessage 41 | */ 42 | public function toMail($notifiable) { 43 | return (new MailMessage)->markdown('mail.posts.posted', [ 44 | 'user' => $this->user, 45 | ]); 46 | } 47 | 48 | /** 49 | * Get the array representation of the notification. 50 | * 51 | * @param mixed $notifiable 52 | * @return array 53 | */ 54 | public function toArray($notifiable) { 55 | return [ 56 | 'info' => [ 57 | 'message' => $this->user->username." has posted on your timeline.", 58 | 'link' => route('dashboard.index'), 59 | 'avatar' => $this->user->profile_photo_url, 60 | 'sent' => Carbon::now(), 61 | ] 62 | ]; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'App\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | * 22 | * @return void 23 | */ 24 | public function boot() 25 | { 26 | $this->registerPolicies(); 27 | 28 | // 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 19 | SendEmailVerificationNotification::class, 20 | ], 21 | \App\Events\SomeonePostedEvent::class => [ 22 | \App\Listeners\SomeonePostedListener::class, 23 | ], 24 | \App\Events\FriendRequestReceivedEvent::class => [ 25 | \App\Listeners\FriendRequestReceivedListener::class, 26 | ], 27 | \App\Events\FriendRequestAcceptedEvent::class => [ 28 | \App\Listeners\FriendRequestAcceptedListener::class, 29 | ], 30 | \App\Events\NewChatMessageEvent::class => [ 31 | ] 32 | ]; 33 | 34 | /** 35 | * Register any events for your application. 36 | * 37 | * @return void 38 | */ 39 | public function boot() 40 | { 41 | // 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Providers/FortifyServiceProvider.php: -------------------------------------------------------------------------------- 1 | by($request->email.$request->ip()); 41 | }); 42 | 43 | RateLimiter::for('two-factor', function (Request $request) { 44 | return Limit::perMinute(5)->by($request->session()->get('login.id')); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Providers/JetstreamServiceProvider.php: -------------------------------------------------------------------------------- 1 | configurePermissions(); 29 | 30 | Jetstream::deleteUsersUsing(DeleteUser::class); 31 | } 32 | 33 | /** 34 | * Configure the permissions that are available within the application. 35 | * 36 | * @return void 37 | */ 38 | protected function configurePermissions() 39 | { 40 | Jetstream::defaultApiTokenPermissions(['read']); 41 | 42 | Jetstream::permissions([ 43 | 'create', 44 | 'read', 45 | 'update', 46 | 'delete', 47 | ]); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | configureRateLimiting(); 39 | 40 | $this->routes(function () { 41 | Route::prefix('api') 42 | ->middleware('api') 43 | ->namespace($this->namespace) 44 | ->group(base_path('routes/api.php')); 45 | 46 | Route::middleware('web') 47 | ->namespace($this->namespace) 48 | ->group(base_path('routes/web.php')); 49 | }); 50 | } 51 | 52 | /** 53 | * Configure the rate limiters for the application. 54 | * 55 | * @return void 56 | */ 57 | protected function configureRateLimiting() 58 | { 59 | RateLimiter::for('api', function (Request $request) { 60 | return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip()); 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/Traits/Likeable.php: -------------------------------------------------------------------------------- 1 | likes 9 | ->where('like', 1) 10 | ->where('likeable_id', $query->id) 11 | ->where('likeable_type', get_class($query)) 12 | ->where('user_id', $this->id) 13 | ->count(); 14 | } 15 | 16 | public function hasDisliked($query) { 17 | return (bool) $query->likes 18 | ->where('dislike', 1) 19 | ->where('likeable_id', $query->id) 20 | ->where('likeable_type', get_class($query)) 21 | ->where('user_id', $this->id) 22 | ->count(); 23 | } 24 | 25 | public function changeToDislike($query) { 26 | $likes = $query->likes()->where('like', 1)->where('dislike', 0)->where('user_id', $this->id)->first(); 27 | $likes->like -=1; 28 | $likes->dislike +=1; 29 | $likes->update(); 30 | } 31 | 32 | public function changeToLike($query) { 33 | $likes = $query->likes()->where('dislike', 1)->where('like', 0)->where('user_id', $this->id)->first(); 34 | $likes->like +=1; 35 | $likes->dislike -=1; 36 | $likes->update(); 37 | } 38 | 39 | public function like($query) { 40 | if($this->id != $query->user_id && !$this->is_friends_with($query->user_id)) { 41 | return back(); 42 | } else { 43 | if($this->hasLiked($query)) { 44 | return back(); 45 | } 46 | if($this->hasdisliked($query)) { 47 | $this->changeToLike($query); 48 | return back(); 49 | } 50 | else { 51 | $query->likes()->create([ 52 | 'user_id' => $this->id, 53 | 'like' => 1, 54 | ]); 55 | return back(); 56 | } 57 | } 58 | } 59 | 60 | public function dislike($query) { 61 | if($this->id != $query->user_id && !$this->is_friends_with($query->user_id)) { 62 | return back(); 63 | } else { 64 | if($this->hasDisliked($query)) { 65 | return back(); 66 | } 67 | if($this->hasLiked($query)) { 68 | $this->changeToDislike($query); 69 | return back(); 70 | } 71 | else { 72 | $query->likes()->create([ 73 | 'user_id' => $this->id, 74 | 'dislike' => 1, 75 | ]); 76 | return back(); 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/laravel", 3 | "type": "project", 4 | "description": "The Laravel Framework.", 5 | "keywords": [ 6 | "framework", 7 | "laravel" 8 | ], 9 | "license": "MIT", 10 | "require": { 11 | "php": "^7.3|^8.0", 12 | "fideloper/proxy": "^4.4", 13 | "fruitcake/laravel-cors": "^2.0", 14 | "guzzlehttp/guzzle": "^7.0.1", 15 | "inertiajs/inertia-laravel": "^0.4.2", 16 | "laravel/framework": "^8.40", 17 | "laravel/jetstream": "^2.3", 18 | "laravel/sanctum": "^2.6", 19 | "laravel/tinker": "^2.5", 20 | "pusher/pusher-php-server": "~4.0", 21 | "tightenco/ziggy": "^1.3.5" 22 | }, 23 | "require-dev": { 24 | "facade/ignition": "^2.5", 25 | "fakerphp/faker": "^1.9.1", 26 | "laravel/sail": "^1.0.1", 27 | "mockery/mockery": "^1.4.2", 28 | "nunomaduro/collision": "^5.0", 29 | "phpunit/phpunit": "^9.3.3" 30 | }, 31 | "config": { 32 | "optimize-autoloader": true, 33 | "preferred-install": "dist", 34 | "sort-packages": true 35 | }, 36 | "extra": { 37 | "laravel": { 38 | "dont-discover": [] 39 | } 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "App\\": "app/", 44 | "Database\\Factories\\": "database/factories/", 45 | "Database\\Seeders\\": "database/seeders/" 46 | } 47 | }, 48 | "autoload-dev": { 49 | "psr-4": { 50 | "Tests\\": "tests/" 51 | } 52 | }, 53 | "minimum-stability": "dev", 54 | "prefer-stable": true, 55 | "scripts": { 56 | "post-autoload-dump": [ 57 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 58 | "@php artisan package:discover --ansi" 59 | ], 60 | "post-root-package-install": [ 61 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 62 | ], 63 | "post-create-project-cmd": [ 64 | "@php artisan key:generate --ansi" 65 | ] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_APP_KEY'), 36 | 'secret' => env('PUSHER_APP_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | 'cluster' => env('PUSHER_APP_CLUSTER'), 40 | 'useTLS' => true, 41 | ], 42 | ], 43 | 44 | 'ably' => [ 45 | 'driver' => 'ably', 46 | 'key' => env('ABLY_KEY'), 47 | ], 48 | 49 | 'redis' => [ 50 | 'driver' => 'redis', 51 | 'connection' => 'default', 52 | ], 53 | 54 | 'log' => [ 55 | 'driver' => 'log', 56 | ], 57 | 58 | 'null' => [ 59 | 'driver' => 'null', 60 | ], 61 | 62 | ], 63 | 64 | ]; 65 | -------------------------------------------------------------------------------- /config/cors.php: -------------------------------------------------------------------------------- 1 | ['api/*', 'sanctum/csrf-cookie'], 19 | 20 | 'allowed_methods' => ['*'], 21 | 22 | 'allowed_origins' => ['*'], 23 | 24 | 'allowed_origins_patterns' => [], 25 | 26 | 'allowed_headers' => ['*'], 27 | 28 | 'exposed_headers' => [], 29 | 30 | 'max_age' => 0, 31 | 32 | 'supports_credentials' => false, 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DRIVER', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Filesystem Disks 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure as many filesystem "disks" as you wish, and you 24 | | may even configure multiple disks of the same driver. Defaults have 25 | | been setup for each driver as an example of the required options. 26 | | 27 | | Supported Drivers: "local", "ftp", "sftp", "s3" 28 | | 29 | */ 30 | 31 | 'disks' => [ 32 | 33 | 'local' => [ 34 | 'driver' => 'local', 35 | 'root' => storage_path('app'), 36 | ], 37 | 38 | 'public' => [ 39 | 'driver' => 'local', 40 | 'root' => storage_path('app/public'), 41 | 'url' => env('APP_URL').'/storage', 42 | 'visibility' => 'public', 43 | ], 44 | 45 | 's3' => [ 46 | 'driver' => 's3', 47 | 'key' => env('AWS_ACCESS_KEY_ID'), 48 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 49 | 'region' => env('AWS_DEFAULT_REGION'), 50 | 'bucket' => env('AWS_BUCKET'), 51 | 'url' => env('AWS_URL'), 52 | 'endpoint' => env('AWS_ENDPOINT'), 53 | ], 54 | 55 | ], 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Symbolic Links 60 | |-------------------------------------------------------------------------- 61 | | 62 | | Here you may configure the symbolic links that will be created when the 63 | | `storage:link` Artisan command is executed. The array keys should be 64 | | the locations of the links and the values should be their targets. 65 | | 66 | */ 67 | 68 | 'links' => [ 69 | public_path('storage') => storage_path('app/public'), 70 | ], 71 | 72 | ]; 73 | -------------------------------------------------------------------------------- /config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Bcrypt Options 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may specify the configuration options that should be used when 26 | | passwords are hashed using the Bcrypt algorithm. This will allow you 27 | | to control the amount of time it takes to hash the given password. 28 | | 29 | */ 30 | 31 | 'bcrypt' => [ 32 | 'rounds' => env('BCRYPT_ROUNDS', 10), 33 | ], 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Argon Options 38 | |-------------------------------------------------------------------------- 39 | | 40 | | Here you may specify the configuration options that should be used when 41 | | passwords are hashed using the Argon algorithm. These will allow you 42 | | to control the amount of time it takes to hash the given password. 43 | | 44 | */ 45 | 46 | 'argon' => [ 47 | 'memory' => 1024, 48 | 'threads' => 2, 49 | 'time' => 2, 50 | ], 51 | 52 | ]; 53 | -------------------------------------------------------------------------------- /config/jetstream.php: -------------------------------------------------------------------------------- 1 | 'inertia', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Jetstream Route Middleware 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may specify which middleware Jetstream will assign to the routes 26 | | that it registers with the application. When necessary, you may modify 27 | | these middleware; however, this default value is usually sufficient. 28 | | 29 | */ 30 | 31 | 'middleware' => ['web'], 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Features 36 | |-------------------------------------------------------------------------- 37 | | 38 | | Some of Jetstream's features are optional. You may disable the features 39 | | by removing them from this array. You're free to only remove some of 40 | | these features or you can even remove all of these if you need to. 41 | | 42 | */ 43 | 44 | 'features' => [ 45 | // Features::termsAndPrivacyPolicy(), 46 | Features::profilePhotos(), 47 | // Features::api(), 48 | // Features::teams(['invitations' => true]), 49 | Features::accountDeletion(), 50 | ], 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Profile Photo Disk 55 | |-------------------------------------------------------------------------- 56 | | 57 | | This configuration value determines the default disk that will be used 58 | | when storing profile photos for your application's users. Typically 59 | | this will be the "public" disk but you may adjust this if needed. 60 | | 61 | */ 62 | 63 | 'profile_photo_disk' => 'public', 64 | 65 | ]; 66 | -------------------------------------------------------------------------------- /config/sanctum.php: -------------------------------------------------------------------------------- 1 | explode(',', env( 17 | 'SANCTUM_STATEFUL_DOMAINS', 18 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1' 19 | )), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Expiration Minutes 24 | |-------------------------------------------------------------------------- 25 | | 26 | | This value controls the number of minutes until an issued token will be 27 | | considered expired. If this value is null, personal access tokens do 28 | | not expire. This won't tweak the lifetime of first-party sessions. 29 | | 30 | */ 31 | 32 | 'expiration' => null, 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Sanctum Middleware 37 | |-------------------------------------------------------------------------- 38 | | 39 | | When authenticating your first-party SPA with Sanctum you may need to 40 | | customize some of the middleware Sanctum uses while processing the 41 | | request. You may change the middleware listed below as required. 42 | | 43 | */ 44 | 45 | 'middleware' => [ 46 | 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 47 | 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, 48 | ], 49 | 50 | ]; 51 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 21 | ], 22 | 23 | 'postmark' => [ 24 | 'token' => env('POSTMARK_TOKEN'), 25 | ], 26 | 27 | 'ses' => [ 28 | 'key' => env('AWS_ACCESS_KEY_ID'), 29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 31 | ], 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => env( 32 | 'VIEW_COMPILED_PATH', 33 | realpath(storage_path('framework/views')) 34 | ), 35 | 36 | ]; 37 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | *.sqlite-journal 3 | -------------------------------------------------------------------------------- /database/factories/CommentFactory.php: -------------------------------------------------------------------------------- 1 | 1, 25 | 'post_id' => 1004, 26 | 'body' => $this->faker->paragraph() 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/factories/PostFactory.php: -------------------------------------------------------------------------------- 1 | 1, 25 | 'parent_id' => null, 26 | 'body' => $this->faker->paragraph() 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/factories/ProfileFactory.php: -------------------------------------------------------------------------------- 1 | faker->name; 27 | return [ 28 | 'slug' => Str::of($slug)->slug('-'), 29 | 'gender' => Arr::random($gender), 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/factories/RoomFactory.php: -------------------------------------------------------------------------------- 1 | faker->state; 25 | return [ 26 | 'name' => $state, 27 | 'slug' => Str::of($state)->slug('-'), 28 | 'active' => rand(0,50) 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name, 26 | 'email' => $this->faker->unique()->safeEmail, 27 | 'username' => $this->faker->userName, 28 | 'email_verified_at' => now(), 29 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 30 | 'remember_token' => Str::random(10), 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->string('username'); 20 | $table->string('email')->unique(); 21 | $table->timestamp('email_verified_at')->nullable(); 22 | $table->string('password'); 23 | $table->rememberToken(); 24 | $table->foreignId('current_team_id')->nullable(); 25 | $table->text('profile_photo_path')->nullable(); 26 | $table->timestamps(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('users'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php: -------------------------------------------------------------------------------- 1 | text('two_factor_secret') 18 | ->after('password') 19 | ->nullable(); 20 | 21 | $table->text('two_factor_recovery_codes') 22 | ->after('two_factor_secret') 23 | ->nullable(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::table('users', function (Blueprint $table) { 35 | $table->dropColumn('two_factor_secret', 'two_factor_recovery_codes'); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/2019_08_19_000000_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('uuid')->unique(); 19 | $table->text('connection'); 20 | $table->text('queue'); 21 | $table->longText('payload'); 22 | $table->longText('exception'); 23 | $table->timestamp('failed_at')->useCurrent(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('failed_jobs'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->morphs('tokenable'); 19 | $table->string('name'); 20 | $table->string('token', 64)->unique(); 21 | $table->text('abilities')->nullable(); 22 | $table->timestamp('last_used_at')->nullable(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('personal_access_tokens'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2021_02_07_023232_create_sessions_table.php: -------------------------------------------------------------------------------- 1 | string('id')->primary(); 18 | $table->foreignId('user_id')->nullable()->index(); 19 | $table->string('ip_address', 45)->nullable(); 20 | $table->text('user_agent')->nullable(); 21 | $table->text('payload'); 22 | $table->integer('last_activity')->index(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('sessions'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/migrations/2021_02_10_030125_create_profiles_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); 19 | $table->string('slug'); 20 | $table->string('gender'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('profiles'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2021_02_18_034602_create_friends_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->unsignedBigInteger('requester'); 19 | $table->unsignedBigInteger('user_requested'); 20 | $table->boolean('status')->default(0); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('friends'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2021_02_22_040444_create_posts_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->unsignedBigInteger('user_id'); 19 | $table->unsignedBigInteger('parent_id')->nullable(); 20 | $table->text('body'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('posts'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2021_02_25_025722_create_likes_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->unsignedBigInteger('user_id'); 19 | $table->unsignedBigInteger('like')->default(0); 20 | $table->unsignedBigInteger('dislike')->default(0); 21 | $table->unsignedBigInteger('likeable_id')->default(0); 22 | $table->string('likeable_type'); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('likes'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2021_02_26_030724_create_comments_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); 19 | $table->foreignId('post_id')->constrained()->onDelete('cascade'); 20 | $table->text('body'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('comments'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2021_03_03_013938_create_notifications_table.php: -------------------------------------------------------------------------------- 1 | uuid('id')->primary(); 18 | $table->string('type'); 19 | $table->morphs('notifiable'); 20 | $table->text('data'); 21 | $table->timestamp('read_at')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('notifications'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/migrations/2021_03_06_022301_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('queue')->index(); 19 | $table->longText('payload'); 20 | $table->unsignedTinyInteger('attempts'); 21 | $table->unsignedInteger('reserved_at')->nullable(); 22 | $table->unsignedInteger('available_at'); 23 | $table->unsignedInteger('created_at'); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('jobs'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2021_03_07_025508_create_rooms_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->string('slug'); 20 | $table->unsignedBigInteger('active')->default(0); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('rooms'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2021_03_09_032244_create_messages_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->text('body'); 19 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); 20 | $table->foreignId('room_id')->constrained()->onDelete('cascade'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('messages'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | hasProfile()->hasPosts(5)->create(); 16 | \App\Models\Room::factory(5)->create(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "mix", 6 | "watch": "mix watch", 7 | "watch-poll": "mix watch -- --watch-options-poll=1000", 8 | "hot": "mix watch --hot", 9 | "prod": "npm run production", 10 | "production": "mix --production" 11 | }, 12 | "devDependencies": { 13 | "@inertiajs/inertia": "^0.9.1", 14 | "@inertiajs/inertia-vue3": "^0.4.2", 15 | "@inertiajs/progress": "^0.2.6", 16 | "@tailwindcss/forms": "^0.2.1", 17 | "@tailwindcss/typography": "^0.3.0", 18 | "@vue/compiler-sfc": "^3.1.5", 19 | "axios": "^0.21", 20 | "laravel-echo": "^1.10.0", 21 | "laravel-mix": "^6.0.6", 22 | "lodash": "^4.17.19", 23 | "postcss": "^8.1.14", 24 | "postcss-import": "^12.0.1", 25 | "pusher-js": "^7.0.3", 26 | "tailwindcss": "^2.0.1", 27 | "vue": "^3.0.5", 28 | "vue-loader": "^16.1.2" 29 | }, 30 | "dependencies": { 31 | "epic-spinners": "^1.1.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests/Unit 10 | 11 | 12 | ./tests/Feature 13 | 14 | 15 | 16 | 17 | ./app 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designatedcoder/social-network-demo/ce2caa081151c1a8734c384166d66403595c18b5/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class); 50 | 51 | $response = tap($kernel->handle( 52 | $request = Request::capture() 53 | ))->send(); 54 | 55 | $kernel->terminate($request, $response); 56 | -------------------------------------------------------------------------------- /public/js/app.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress 2 | * @license MIT */ 3 | 4 | /*! 5 | * Pusher JavaScript Library v7.0.3 6 | * https://pusher.com/ 7 | * 8 | * Copyright 2020, Pusher 9 | * Released under the MIT licence. 10 | */ 11 | 12 | /** 13 | * @license 14 | * Lodash 15 | * Copyright OpenJS Foundation and other contributors 16 | * Released under MIT license 17 | * Based on Underscore.js 1.8.3 18 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 19 | */ 20 | -------------------------------------------------------------------------------- /public/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/app.js": "/js/app.js?id=6745af0176b2b9fb5f79", 3 | "/css/app.css": "/css/app.css?id=0733599a266b62c15cd3" 4 | } 5 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | 5 | @import url('https://fonts.googleapis.com/css2?family=Montserrat&display=swap'); 6 | -------------------------------------------------------------------------------- /resources/js/Components/Buttons/BlueButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /resources/js/Components/Buttons/GreenButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 27 | -------------------------------------------------------------------------------- /resources/js/Components/Chat/ChatActives.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /resources/js/Components/Chat/ChatBox.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 25 | -------------------------------------------------------------------------------- /resources/js/Components/Chat/ChatInput.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /resources/js/Components/FriendStatus/Accept.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 52 | -------------------------------------------------------------------------------- /resources/js/Components/FriendStatus/Ignore.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 48 | -------------------------------------------------------------------------------- /resources/js/Components/InfiniteScroll.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /resources/js/Components/Notifications.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | -------------------------------------------------------------------------------- /resources/js/Components/PostComment/CombinedComments.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /resources/js/Components/PostComment/CombinedPosts.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | -------------------------------------------------------------------------------- /resources/js/Components/PostComment/CommentItem.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 66 | -------------------------------------------------------------------------------- /resources/js/Components/PostComment/Likes/Dislike.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /resources/js/Components/PostComment/Likes/Like.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /resources/js/Components/PostComment/PostForm.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 38 | -------------------------------------------------------------------------------- /resources/js/Components/SideBar.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 32 | -------------------------------------------------------------------------------- /resources/js/Components/SuggestionBlock.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | -------------------------------------------------------------------------------- /resources/js/Components/UserBlock.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /resources/js/Jetstream/ActionMessage.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /resources/js/Jetstream/ActionSection.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /resources/js/Jetstream/ApplicationMark.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /resources/js/Jetstream/AuthenticationCard.vue: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /resources/js/Jetstream/AuthenticationCardLogo.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /resources/js/Jetstream/Button.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /resources/js/Jetstream/Checkbox.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 35 | 36 | -------------------------------------------------------------------------------- /resources/js/Jetstream/ConfirmationModal.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 57 | -------------------------------------------------------------------------------- /resources/js/Jetstream/DangerButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /resources/js/Jetstream/DialogModal.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 49 | -------------------------------------------------------------------------------- /resources/js/Jetstream/Dropdown.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 84 | -------------------------------------------------------------------------------- /resources/js/Jetstream/DropdownLink.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | -------------------------------------------------------------------------------- /resources/js/Jetstream/FormSection.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 41 | -------------------------------------------------------------------------------- /resources/js/Jetstream/Input.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /resources/js/Jetstream/InputError.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /resources/js/Jetstream/Label.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /resources/js/Jetstream/NavLink.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /resources/js/Jetstream/ResponsiveNavLink.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /resources/js/Jetstream/SecondaryButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /resources/js/Jetstream/SectionBorder.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /resources/js/Jetstream/SectionTitle.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /resources/js/Jetstream/ValidationErrors.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 24 | -------------------------------------------------------------------------------- /resources/js/Layouts/PagesLayout.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 22 | -------------------------------------------------------------------------------- /resources/js/Pages/API/Index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 36 | -------------------------------------------------------------------------------- /resources/js/Pages/Auth/ConfirmPassword.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 63 | -------------------------------------------------------------------------------- /resources/js/Pages/Auth/ForgotPassword.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 69 | -------------------------------------------------------------------------------- /resources/js/Pages/Auth/ResetPassword.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 77 | -------------------------------------------------------------------------------- /resources/js/Pages/Auth/VerifyEmail.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 62 | -------------------------------------------------------------------------------- /resources/js/Pages/PrivacyPolicy.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 27 | -------------------------------------------------------------------------------- /resources/js/Pages/Profile/Show.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 64 | -------------------------------------------------------------------------------- /resources/js/Pages/TermsOfService.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 27 | -------------------------------------------------------------------------------- /resources/js/Pages/User/ChatRooms/Index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 44 | -------------------------------------------------------------------------------- /resources/js/Pages/User/Friends/Index.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 51 | -------------------------------------------------------------------------------- /resources/js/Pages/User/Members/Index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 56 | -------------------------------------------------------------------------------- /resources/js/Pages/Welcome.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 40 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | require('./bootstrap'); 2 | // Import modules... 3 | import { createApp, h } from 'vue'; 4 | import { App as InertiaApp, plugin as InertiaPlugin } from '@inertiajs/inertia-vue3'; 5 | import { InertiaProgress } from '@inertiajs/progress'; 6 | import Icon from './Components/Icon'; 7 | 8 | window.Toast = Swal.mixin({ 9 | toast: true, 10 | position: 'top-end', 11 | showConfirmButton: false, 12 | timer: 2000, 13 | timerProgressBar: false, 14 | didOpen: (toast) => { 15 | toast.addEventListener('mouseenter', Swal.stopTimer) 16 | toast.addEventListener('mouseleave', Swal.resumeTimer) 17 | } 18 | }) 19 | 20 | const el = document.getElementById('app'); 21 | 22 | createApp({ 23 | render: () => 24 | h(InertiaApp, { 25 | initialPage: JSON.parse(el.dataset.page), 26 | resolveComponent: (name) => require(`./Pages/${name}`).default, 27 | }), 28 | }) 29 | .mixin({ methods: { route } }) 30 | .use(InertiaPlugin) 31 | .component('icon', Icon) 32 | .mount(el); 33 | 34 | InertiaProgress.init({ color: '#4B5563' }); 35 | -------------------------------------------------------------------------------- /resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | window._ = require('lodash'); 2 | 3 | /** 4 | * We'll load the axios HTTP library which allows us to easily issue requests 5 | * to our Laravel back-end. This library automatically handles sending the 6 | * CSRF token as a header based on the value of the "XSRF" token cookie. 7 | */ 8 | 9 | window.axios = require('axios'); 10 | 11 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 12 | 13 | /** 14 | * Echo exposes an expressive API for subscribing to channels and listening 15 | * for events that are broadcast by Laravel. Echo and event broadcasting 16 | * allows your team to easily build robust real-time web applications. 17 | */ 18 | 19 | import Echo from 'laravel-echo'; 20 | 21 | window.Pusher = require('pusher-js'); 22 | 23 | window.Echo = new Echo({ 24 | broadcaster: 'pusher', 25 | key: process.env.MIX_PUSHER_APP_KEY, 26 | cluster: process.env.MIX_PUSHER_APP_CLUSTER, 27 | forceTLS: true 28 | }); 29 | 30 | // FOR TESTING 31 | // Pusher.log = function(message) { 32 | // window.console.log(message) 33 | // } 34 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'password' => 'The provided password is incorrect.', 18 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Your password has been reset!', 17 | 'sent' => 'We have emailed your password reset link!', 18 | 'throttled' => 'Please wait before retrying.', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that email address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /resources/markdown/policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | Edit this file to define the privacy policy for your application. 4 | -------------------------------------------------------------------------------- /resources/markdown/terms.md: -------------------------------------------------------------------------------- 1 | # Terms of Service 2 | 3 | Edit this file to define the terms of service for your application. 4 | -------------------------------------------------------------------------------- /resources/views/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{ config('app.name', 'Social Network') }} 11 | 12 | 13 | 14 | 15 | 16 | 17 | @routes 18 | 19 | 20 | 21 | 22 | @inertia 23 | 24 | 25 | -------------------------------------------------------------------------------- /resources/views/mail/friendrequest/accepted.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | # Friend Request Accepted 3 | 4 | **You're friend request has been accepted by:** 5 | 6 | **Name:** {{ $user->name }} 7 | 8 | **Username:** {{ $user->username }} 9 | 10 | @component('mail::button', ['url' => route('profiles.show', $user->username)]) 11 | Visit their profile 12 | @endcomponent 13 | 14 | Thanks,
15 | {{ config('app.name') }} 16 | @endcomponent 17 | -------------------------------------------------------------------------------- /resources/views/mail/friendrequest/received.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | # Friend Request Received 3 | 4 | **You have received a new friend request from:** 5 | 6 | **Name:** {{ $user->name }} 7 | 8 | **Username:** {{ $user->username }} 9 | 10 | @component('mail::button', ['url' => route('profiles.show', $user->username)]) 11 | Visit their profile 12 | @endcomponent 13 | 14 | Thanks,
15 | {{ config('app.name') }} 16 | @endcomponent 17 | -------------------------------------------------------------------------------- /resources/views/mail/posts/posted.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | # Someone Posted 3 | 4 | **{{ $user->username }}** has posted on your timeline 5 | 6 | @component('mail::button', ['url' => route('dashboard.index')]) 7 | Go see what they wrote 8 | @endcomponent 9 | 10 | Thanks,
11 | {{ config('app.name') }} 12 | @endcomponent 13 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | get('/user', function (Request $request) { 18 | return $request->user(); 19 | }); 20 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 19 | }); 20 | 21 | Broadcast::channel('chat.{roomId}', function ($user, $roomId) { 22 | if(Auth::check()) { 23 | return [ 24 | 'username' => $user->username, 25 | 'avatar' => $user->profile_photo_url 26 | ]; 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | })->purpose('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme'); 2 | 3 | module.exports = { 4 | purge: [ 5 | './vendor/laravel/jetstream/**/*.blade.php', 6 | './storage/framework/views/*.php', 7 | './resources/views/**/*.blade.php', 8 | './resources/js/**/*.vue', 9 | ], 10 | 11 | theme: { 12 | extend: { 13 | fontFamily: { 14 | sans: ['Nunito', ...defaultTheme.fontFamily.sans], 15 | primary: ['Montserrat'], 16 | }, 17 | spacing: { 18 | '128': '32rem', 19 | }, 20 | }, 21 | }, 22 | 23 | variants: { 24 | extend: { 25 | opacity: ['disabled'], 26 | }, 27 | }, 28 | 29 | plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')], 30 | }; 31 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 19 | 20 | return $app; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Feature/ApiTokenPermissionsTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 19 | } 20 | 21 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 22 | 23 | $token = $user->tokens()->create([ 24 | 'name' => 'Test Token', 25 | 'token' => Str::random(40), 26 | 'abilities' => ['create', 'read'], 27 | ]); 28 | 29 | $response = $this->put('/user/api-tokens/'.$token->id, [ 30 | 'name' => $token->name, 31 | 'permissions' => [ 32 | 'delete', 33 | 'missing-permission', 34 | ], 35 | ]); 36 | 37 | $this->assertTrue($user->fresh()->tokens->first()->can('delete')); 38 | $this->assertFalse($user->fresh()->tokens->first()->can('read')); 39 | $this->assertFalse($user->fresh()->tokens->first()->can('missing-permission')); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Feature/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | get('/login'); 17 | 18 | $response->assertStatus(200); 19 | } 20 | 21 | public function test_users_can_authenticate_using_the_login_screen() 22 | { 23 | $user = User::factory()->create(); 24 | 25 | $response = $this->post('/login', [ 26 | 'email' => $user->email, 27 | 'password' => 'password', 28 | ]); 29 | 30 | $this->assertAuthenticated(); 31 | $response->assertRedirect(RouteServiceProvider::HOME); 32 | } 33 | 34 | public function test_users_can_not_authenticate_with_invalid_password() 35 | { 36 | $user = User::factory()->create(); 37 | 38 | $this->post('/login', [ 39 | 'email' => $user->email, 40 | 'password' => 'wrong-password', 41 | ]); 42 | 43 | $this->assertGuest(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Feature/BrowserSessionsTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 16 | 17 | $response = $this->delete('/user/other-browser-sessions', [ 18 | 'password' => 'password', 19 | ]); 20 | 21 | $response->assertSessionHasNoErrors(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/CreateApiTokenTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 18 | } 19 | 20 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 21 | 22 | $response = $this->post('/user/api-tokens', [ 23 | 'name' => 'Test Token', 24 | 'permissions' => [ 25 | 'read', 26 | 'update', 27 | ], 28 | ]); 29 | 30 | $this->assertCount(1, $user->fresh()->tokens); 31 | $this->assertEquals('Test Token', $user->fresh()->tokens->first()->name); 32 | $this->assertTrue($user->fresh()->tokens->first()->can('read')); 33 | $this->assertFalse($user->fresh()->tokens->first()->can('delete')); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Feature/DeleteAccountTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Account deletion is not enabled.'); 18 | } 19 | 20 | $this->actingAs($user = User::factory()->create()); 21 | 22 | $response = $this->delete('/user', [ 23 | 'password' => 'password', 24 | ]); 25 | 26 | $this->assertNull($user->fresh()); 27 | } 28 | 29 | public function test_correct_password_must_be_provided_before_account_can_be_deleted() 30 | { 31 | if (! Features::hasAccountDeletionFeatures()) { 32 | return $this->markTestSkipped('Account deletion is not enabled.'); 33 | } 34 | 35 | $this->actingAs($user = User::factory()->create()); 36 | 37 | $response = $this->delete('/user', [ 38 | 'password' => 'wrong-password', 39 | ]); 40 | 41 | $this->assertNotNull($user->fresh()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Feature/DeleteApiTokenTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 19 | } 20 | 21 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 22 | 23 | $token = $user->tokens()->create([ 24 | 'name' => 'Test Token', 25 | 'token' => Str::random(40), 26 | 'abilities' => ['create', 'read'], 27 | ]); 28 | 29 | $response = $this->delete('/user/api-tokens/'.$token->id); 30 | 31 | $this->assertCount(0, $user->fresh()->tokens); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Feature/EmailVerificationTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Email verification not enabled.'); 22 | } 23 | 24 | $user = User::factory()->create([ 25 | 'email_verified_at' => null, 26 | ]); 27 | 28 | $response = $this->actingAs($user)->get('/email/verify'); 29 | 30 | $response->assertStatus(200); 31 | } 32 | 33 | public function test_email_can_be_verified() 34 | { 35 | if (! Features::enabled(Features::emailVerification())) { 36 | return $this->markTestSkipped('Email verification not enabled.'); 37 | } 38 | 39 | Event::fake(); 40 | 41 | $user = User::factory()->create([ 42 | 'email_verified_at' => null, 43 | ]); 44 | 45 | $verificationUrl = URL::temporarySignedRoute( 46 | 'verification.verify', 47 | now()->addMinutes(60), 48 | ['id' => $user->id, 'hash' => sha1($user->email)] 49 | ); 50 | 51 | $response = $this->actingAs($user)->get($verificationUrl); 52 | 53 | Event::assertDispatched(Verified::class); 54 | 55 | $this->assertTrue($user->fresh()->hasVerifiedEmail()); 56 | $response->assertRedirect(RouteServiceProvider::HOME.'?verified=1'); 57 | } 58 | 59 | public function test_email_can_not_verified_with_invalid_hash() 60 | { 61 | if (! Features::enabled(Features::emailVerification())) { 62 | return $this->markTestSkipped('Email verification not enabled.'); 63 | } 64 | 65 | $user = User::factory()->create([ 66 | 'email_verified_at' => null, 67 | ]); 68 | 69 | $verificationUrl = URL::temporarySignedRoute( 70 | 'verification.verify', 71 | now()->addMinutes(60), 72 | ['id' => $user->id, 'hash' => sha1('wrong-email')] 73 | ); 74 | 75 | $this->actingAs($user)->get($verificationUrl); 76 | 77 | $this->assertFalse($user->fresh()->hasVerifiedEmail()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Feature/PasswordConfirmationTest.php: -------------------------------------------------------------------------------- 1 | withPersonalTeam()->create() 18 | : User::factory()->create(); 19 | 20 | $response = $this->actingAs($user)->get('/user/confirm-password'); 21 | 22 | $response->assertStatus(200); 23 | } 24 | 25 | public function test_password_can_be_confirmed() 26 | { 27 | $user = User::factory()->create(); 28 | 29 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 30 | 'password' => 'password', 31 | ]); 32 | 33 | $response->assertRedirect(); 34 | $response->assertSessionHasNoErrors(); 35 | } 36 | 37 | public function test_password_is_not_confirmed_with_invalid_password() 38 | { 39 | $user = User::factory()->create(); 40 | 41 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 42 | 'password' => 'wrong-password', 43 | ]); 44 | 45 | $response->assertSessionHasErrors(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Feature/PasswordResetTest.php: -------------------------------------------------------------------------------- 1 | get('/forgot-password'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | 22 | public function test_reset_password_link_can_be_requested() 23 | { 24 | Notification::fake(); 25 | 26 | $user = User::factory()->create(); 27 | 28 | $response = $this->post('/forgot-password', [ 29 | 'email' => $user->email, 30 | ]); 31 | 32 | Notification::assertSentTo($user, ResetPassword::class); 33 | } 34 | 35 | public function test_reset_password_screen_can_be_rendered() 36 | { 37 | Notification::fake(); 38 | 39 | $user = User::factory()->create(); 40 | 41 | $response = $this->post('/forgot-password', [ 42 | 'email' => $user->email, 43 | ]); 44 | 45 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) { 46 | $response = $this->get('/reset-password/'.$notification->token); 47 | 48 | $response->assertStatus(200); 49 | 50 | return true; 51 | }); 52 | } 53 | 54 | public function test_password_can_be_reset_with_valid_token() 55 | { 56 | Notification::fake(); 57 | 58 | $user = User::factory()->create(); 59 | 60 | $response = $this->post('/forgot-password', [ 61 | 'email' => $user->email, 62 | ]); 63 | 64 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { 65 | $response = $this->post('/reset-password', [ 66 | 'token' => $notification->token, 67 | 'email' => $user->email, 68 | 'password' => 'password', 69 | 'password_confirmation' => 'password', 70 | ]); 71 | 72 | $response->assertSessionHasNoErrors(); 73 | 74 | return true; 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/Feature/ProfileInformationTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 16 | 17 | $response = $this->put('/user/profile-information', [ 18 | 'name' => 'Test Name', 19 | 'email' => 'test@example.com', 20 | ]); 21 | 22 | $this->assertEquals('Test Name', $user->fresh()->name); 23 | $this->assertEquals('test@example.com', $user->fresh()->email); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Feature/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | get('/register'); 17 | 18 | $response->assertStatus(200); 19 | } 20 | 21 | public function test_new_users_can_register() 22 | { 23 | $response = $this->post('/register', [ 24 | 'name' => 'Test User', 25 | 'email' => 'test@example.com', 26 | 'password' => 'password', 27 | 'password_confirmation' => 'password', 28 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature(), 29 | ]); 30 | 31 | $this->assertAuthenticated(); 32 | $response->assertRedirect(RouteServiceProvider::HOME); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Feature/TwoFactorAuthenticationSettingsTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 16 | 17 | $this->withSession(['auth.password_confirmed_at' => time()]); 18 | 19 | $response = $this->post('/user/two-factor-authentication'); 20 | 21 | $this->assertNotNull($user->fresh()->two_factor_secret); 22 | $this->assertCount(8, $user->fresh()->recoveryCodes()); 23 | } 24 | 25 | public function test_recovery_codes_can_be_regenerated() 26 | { 27 | $this->actingAs($user = User::factory()->create()); 28 | 29 | $this->withSession(['auth.password_confirmed_at' => time()]); 30 | 31 | $this->post('/user/two-factor-authentication'); 32 | $this->post('/user/two-factor-recovery-codes'); 33 | 34 | $user = $user->fresh(); 35 | 36 | $this->post('/user/two-factor-recovery-codes'); 37 | 38 | $this->assertCount(8, $user->recoveryCodes()); 39 | $this->assertCount(8, array_diff($user->recoveryCodes(), $user->fresh()->recoveryCodes())); 40 | } 41 | 42 | public function test_two_factor_authentication_can_be_disabled() 43 | { 44 | $this->actingAs($user = User::factory()->create()); 45 | 46 | $this->withSession(['auth.password_confirmed_at' => time()]); 47 | 48 | $this->post('/user/two-factor-authentication'); 49 | 50 | $this->assertNotNull($user->fresh()->two_factor_secret); 51 | 52 | $this->delete('/user/two-factor-authentication'); 53 | 54 | $this->assertNull($user->fresh()->two_factor_secret); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Feature/UpdatePasswordTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 17 | 18 | $response = $this->put('/user/password', [ 19 | 'current_password' => 'password', 20 | 'password' => 'new-password', 21 | 'password_confirmation' => 'new-password', 22 | ]); 23 | 24 | $this->assertTrue(Hash::check('new-password', $user->fresh()->password)); 25 | } 26 | 27 | public function test_current_password_must_be_correct() 28 | { 29 | $this->actingAs($user = User::factory()->create()); 30 | 31 | $response = $this->put('/user/password', [ 32 | 'current_password' => 'wrong-password', 33 | 'password' => 'new-password', 34 | 'password_confirmation' => 'new-password', 35 | ]); 36 | 37 | $response->assertSessionHasErrors(); 38 | 39 | $this->assertTrue(Hash::check('password', $user->fresh()->password)); 40 | } 41 | 42 | public function test_new_passwords_must_match() 43 | { 44 | $this->actingAs($user = User::factory()->create()); 45 | 46 | $response = $this->put('/user/password', [ 47 | 'current_password' => 'password', 48 | 'password' => 'new-password', 49 | 'password_confirmation' => 'wrong-password', 50 | ]); 51 | 52 | $response->assertSessionHasErrors(); 53 | 54 | $this->assertTrue(Hash::check('password', $user->fresh()->password)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | resolve: { 6 | alias: { 7 | '@': path.resolve('resources/js'), 8 | }, 9 | }, 10 | plugins: [ 11 | new webpack.DefinePlugin({ 12 | __VUE_OPTIONS_API__: true, 13 | __VUE_PROD_DEVTOOLS__: false, 14 | }) 15 | ] 16 | }; 17 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel applications. By default, we are compiling the CSS 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | mix.js('resources/js/app.js', 'public/js').vue() 15 | .postCss('resources/css/app.css', 'public/css', [ 16 | require('postcss-import'), 17 | require('tailwindcss'), 18 | ]) 19 | .sourceMaps() 20 | .webpackConfig(require('./webpack.config')) 21 | .disableNotifications(); 22 | 23 | if (mix.inProduction()) { 24 | mix.version(); 25 | } 26 | --------------------------------------------------------------------------------