├── public
├── favicon.ico
├── robots.txt
├── .htaccess
├── web.config
└── index.php
├── resources
├── sass
│ └── app.scss
├── js
│ ├── app.js
│ └── bootstrap.js
├── views
│ ├── emails
│ │ └── invitations
│ │ │ ├── invite-existing-user.blade.php
│ │ │ └── invite-new-user.blade.php
│ └── welcome.blade.php
└── lang
│ └── en
│ ├── pagination.php
│ ├── auth.php
│ └── passwords.php
├── bootstrap
├── cache
│ └── .gitignore
└── app.php
├── storage
├── logs
│ └── .gitignore
├── debugbar
│ └── .gitignore
├── app
│ ├── .gitignore
│ └── public
│ │ ├── .gitignore
│ │ └── uploads
│ │ ├── .gitignore
│ │ └── designs
│ │ ├── large
│ │ └── .gitignore
│ │ ├── original
│ │ └── .gitignore
│ │ ├── thumbnail
│ │ └── .gitignore
│ │ └── .gitignore
├── framework
│ ├── testing
│ │ └── .gitignore
│ ├── views
│ │ └── .gitignore
│ ├── cache
│ │ ├── data
│ │ │ └── .gitignore
│ │ └── .gitignore
│ ├── sessions
│ │ └── .gitignore
│ └── .gitignore
└── uploads
│ ├── large
│ └── .gitignore
│ ├── original
│ └── .gitignore
│ ├── thumbnail
│ └── .gitignore
│ └── .gitignore
├── database
├── .gitignore
├── seeds
│ └── DatabaseSeeder.php
├── migrations
│ ├── 2014_10_12_100000_create_password_resets_table.php
│ ├── 2020_01_23_003838_add_fields_to_designs.php
│ ├── 2020_02_01_011027_add_team_id_to_designs.php
│ ├── 2019_08_19_000000_create_failed_jobs_table.php
│ ├── 2020_01_31_224355_create_messages_table.php
│ ├── 2020_01_23_021358_create_jobs_table.php
│ ├── 2020_01_26_031336_create_comments_table.php
│ ├── 2020_01_27_232327_create_likes_table.php
│ ├── 2020_01_22_213725_create_designs_table.php
│ ├── 2020_01_29_234339_create_invitations_table.php
│ ├── 2014_10_12_000000_create_users_table.php
│ ├── 2020_01_31_224345_create_chats_table.php
│ ├── 2020_01_29_214651_create_teams_table.php
│ └── 2020_01_25_114136_create_taggable_table.php
└── factories
│ └── UserFactory.php
├── config
├── site.php
├── services.php
├── view.php
├── cors.php
├── hashing.php
├── broadcasting.php
├── filesystems.php
├── taggable.php
├── queue.php
├── logging.php
├── cache.php
├── auth.php
├── mail.php
├── database.php
└── session.php
├── .gitattributes
├── app
├── Repositories
│ ├── Contracts
│ │ ├── IComment.php
│ │ ├── IMessage.php
│ │ ├── ITeam.php
│ │ ├── IChat.php
│ │ ├── IUser.php
│ │ ├── IInvitation.php
│ │ ├── IDesign.php
│ │ └── IBase.php
│ ├── Criteria
│ │ ├── ICriterion.php
│ │ └── ICriteria.php
│ └── Eloquent
│ │ ├── Criteria
│ │ ├── LatestFirst.php
│ │ ├── IsLive.php
│ │ ├── WithTrashed.php
│ │ ├── ForUser.php
│ │ └── EagerLoad.php
│ │ ├── MessageRepository.php
│ │ ├── CommentRepository.php
│ │ ├── TeamRepository.php
│ │ ├── InvitationRepository.php
│ │ ├── ChatRepository.php
│ │ ├── UserRepository.php
│ │ ├── BaseRepository.php
│ │ └── DesignRepository.php
├── Exceptions
│ ├── ModelNotDefined.php
│ └── Handler.php
├── Models
│ ├── Like.php
│ ├── Comment.php
│ ├── Invitation.php
│ ├── Message.php
│ ├── Chat.php
│ ├── Traits
│ │ └── Likeable.php
│ ├── Team.php
│ ├── Design.php
│ └── User.php
├── Http
│ ├── Middleware
│ │ ├── EncryptCookies.php
│ │ ├── CheckForMaintenanceMode.php
│ │ ├── TrimStrings.php
│ │ ├── TrustProxies.php
│ │ ├── Authenticate.php
│ │ ├── VerifyCsrfToken.php
│ │ ├── RedirectIfAuthenticated.php
│ │ └── ProfileJsonResponse.php
│ ├── Controllers
│ │ ├── Controller.php
│ │ ├── User
│ │ │ ├── MeController.php
│ │ │ ├── UserController.php
│ │ │ └── SettingsController.php
│ │ ├── Auth
│ │ │ ├── ForgotPasswordController.php
│ │ │ ├── ResetPasswordController.php
│ │ │ ├── ConfirmPasswordController.php
│ │ │ ├── LoginController.php
│ │ │ ├── RegisterController.php
│ │ │ └── VerificationController.php
│ │ ├── Designs
│ │ │ ├── CommentController.php
│ │ │ ├── UploadController.php
│ │ │ └── DesignController.php
│ │ ├── Chats
│ │ │ └── ChatController.php
│ │ └── Teams
│ │ │ ├── TeamsController.php
│ │ │ └── InvitationsController.php
│ ├── Resources
│ │ ├── MessageResource.php
│ │ ├── TeamResource.php
│ │ ├── ChatResource.php
│ │ ├── CommentResource.php
│ │ ├── UserResource.php
│ │ └── DesignResource.php
│ └── Kernel.php
├── Providers
│ ├── BroadcastServiceProvider.php
│ ├── AppServiceProvider.php
│ ├── EventServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── RepositoryServiceProvider.php
│ └── RouteServiceProvider.php
├── Notifications
│ ├── VerifyEmail.php
│ └── ResetPassword.php
├── Rules
│ ├── MatchOldPassword.php
│ └── CheckSamePassword.php
├── Console
│ └── Kernel.php
├── Mail
│ └── SendInvitationToJoinTeam.php
├── Policies
│ ├── TeamPolicy.php
│ ├── MessagePolicy.php
│ ├── DesignPolicy.php
│ ├── CommentPolicy.php
│ └── InvitationPolicy.php
└── Jobs
│ └── UploadImage.php
├── tests
├── TestCase.php
├── Unit
│ └── ExampleTest.php
├── Feature
│ └── ExampleTest.php
└── CreatesApplication.php
├── .gitignore
├── .styleci.yml
├── .editorconfig
├── routes
├── channels.php
├── console.php
├── web.php
└── api.php
├── webpack.mix.js
├── server.php
├── .env.example
├── package.json
├── phpunit.xml
├── artisan
├── composer.json
└── README.md
/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/sass/app.scss:
--------------------------------------------------------------------------------
1 | //
2 |
--------------------------------------------------------------------------------
/bootstrap/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/resources/js/app.js:
--------------------------------------------------------------------------------
1 | require('./bootstrap');
2 |
--------------------------------------------------------------------------------
/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite
2 | *.sqlite-journal
3 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/storage/debugbar/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !public/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/framework/testing/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/uploads/large/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/uploads/original/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/uploads/thumbnail/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/cache/data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/sessions/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/app/public/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !uploads/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !data/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/app/public/uploads/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !designs/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/app/public/uploads/designs/large/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/app/public/uploads/designs/original/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/app/public/uploads/designs/thumbnail/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/uploads/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !large/
3 | !original/
4 | !thumbnail/
5 | !.gitignore
6 |
--------------------------------------------------------------------------------
/config/site.php:
--------------------------------------------------------------------------------
1 | 'public' // 'public', 's3'
5 | ];
--------------------------------------------------------------------------------
/storage/app/public/uploads/designs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !large/
3 | !original/
4 | !thumbnail/
5 | !.gitignore
6 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.css linguist-vendored
3 | *.scss linguist-vendored
4 | *.js linguist-vendored
5 | CHANGELOG.md export-ignore
6 |
--------------------------------------------------------------------------------
/app/Repositories/Contracts/IComment.php:
--------------------------------------------------------------------------------
1 | latest();
12 | }
13 | }
--------------------------------------------------------------------------------
/app/Repositories/Eloquent/Criteria/IsLive.php:
--------------------------------------------------------------------------------
1 | where('is_live', true);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/Repositories/Eloquent/Criteria/WithTrashed.php:
--------------------------------------------------------------------------------
1 | withTrashed();
12 | }
13 | }
--------------------------------------------------------------------------------
/app/Models/Like.php:
--------------------------------------------------------------------------------
1 | morphTo();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/Repositories/Eloquent/MessageRepository.php:
--------------------------------------------------------------------------------
1 | call(UsersTableSeeder::class);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Unit/ExampleTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(true);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/Repositories/Eloquent/CommentRepository.php:
--------------------------------------------------------------------------------
1 | get('/');
18 |
19 | $response->assertStatus(200);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Repositories/Eloquent/Criteria/ForUser.php:
--------------------------------------------------------------------------------
1 | user_id = $user_id;
14 | }
15 |
16 | public function apply($model)
17 | {
18 | return $model->where('user_id', $this->user_id);
19 | }
20 | }
--------------------------------------------------------------------------------
/app/Repositories/Contracts/IBase.php:
--------------------------------------------------------------------------------
1 | user()->teams;
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/app/Repositories/Eloquent/Criteria/EagerLoad.php:
--------------------------------------------------------------------------------
1 | relationships = $relationships;
14 | }
15 |
16 | public function apply($model)
17 | {
18 | return $model->with($this->relationships);
19 | }
20 | }
--------------------------------------------------------------------------------
/tests/CreatesApplication.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class)->bootstrap();
19 |
20 | return $app;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/Providers/BroadcastServiceProvider.php:
--------------------------------------------------------------------------------
1 | team->name }}**.
6 | Because you are already registered to the platform, you just
7 | need to accept or reject the invitation in your
8 | [team management console]({{ $url }}).
9 |
10 | @component('mail::button', ['url' => $url])
11 | Go to Dashboard
12 | @endcomponent
13 |
14 | Thanks,
15 | {{ config('app.name') }}
16 | @endcomponent
17 |
--------------------------------------------------------------------------------
/app/Http/Controllers/User/MeController.php:
--------------------------------------------------------------------------------
1 | check()){
16 | $user = auth()->user();
17 | return new UserResource($user);
18 | }
19 | return response()->json(null, 401);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Models/Comment.php:
--------------------------------------------------------------------------------
1 | morphTo();
19 | }
20 |
21 | public function user()
22 | {
23 | return $this->belongsTo(User::class);
24 | }
25 |
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/resources/views/emails/invitations/invite-new-user.blade.php:
--------------------------------------------------------------------------------
1 | @component('mail::message')
2 | # Hi,
3 |
4 | You have been invited to join the team
5 | **{{ $invitation->team->name }}**.
6 | Because you are not yet signed up to the platform, please
7 | [Register for free]({{ $url }}), then you can accept or reject the
8 | invitation in your team management console.
9 |
10 | @component('mail::button', ['url' => $url])
11 | Register for free
12 | @endcomponent
13 |
14 | Thanks,
15 | {{ config('app.name') }}
16 | @endcomponent
17 |
--------------------------------------------------------------------------------
/app/Http/Middleware/TrustProxies.php:
--------------------------------------------------------------------------------
1 | id === (int) $id;
16 | });
17 |
--------------------------------------------------------------------------------
/app/Http/Middleware/Authenticate.php:
--------------------------------------------------------------------------------
1 | expectsJson()) {
18 | return route('login');
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Http/Middleware/VerifyCsrfToken.php:
--------------------------------------------------------------------------------
1 | '« Previous',
17 | 'next' => 'Next »',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/routes/console.php:
--------------------------------------------------------------------------------
1 | comment(Inspiring::quote());
18 | })->describe('Display an inspiring quote');
19 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/Repositories/Eloquent/InvitationRepository.php:
--------------------------------------------------------------------------------
1 | members()->attach($user_id);
18 | }
19 |
20 | public function removeUserFromTeam($team, $user_id)
21 | {
22 | $team->members()->detach($user_id);
23 | }
24 |
25 |
26 | }
--------------------------------------------------------------------------------
/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 | # Handle Front Controller...
18 | RewriteCond %{REQUEST_FILENAME} !-d
19 | RewriteCond %{REQUEST_FILENAME} !-f
20 | RewriteRule ^ index.php [L]
21 |
22 |
--------------------------------------------------------------------------------
/app/Models/Invitation.php:
--------------------------------------------------------------------------------
1 | belongsTo(Team::class);
20 | }
21 |
22 | public function recipient()
23 | {
24 | return $this->hasOne(User::class, 'email', 'recipient_email');
25 | }
26 |
27 | public function sender()
28 | {
29 | return $this->hasOne(User::class, 'id', 'sender_id');
30 | }
31 |
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/resources/lang/en/auth.php:
--------------------------------------------------------------------------------
1 | 'These credentials do not match our records.',
17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/app/Repositories/Eloquent/ChatRepository.php:
--------------------------------------------------------------------------------
1 | model->find($chatId);
17 | $chat->participants()->sync($data);
18 | }
19 |
20 | public function getUserChats()
21 | {
22 | return auth()->user()->chats()
23 | ->with(['messages', 'participants'])
24 | ->get();
25 |
26 | }
27 | }
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ForgotPasswordController.php:
--------------------------------------------------------------------------------
1 | json(['status' => trans($response)], 200);
17 | }
18 |
19 | protected function sendResetlinkFailedResponse(Request $request, $response)
20 | {
21 | return response()->json(['email' => trans($response)], 422);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/Http/Middleware/RedirectIfAuthenticated.php:
--------------------------------------------------------------------------------
1 | check()) {
22 | return redirect(RouteServiceProvider::HOME);
23 | }
24 |
25 | return $next($request);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ResetPasswordController.php:
--------------------------------------------------------------------------------
1 | json(['status' => trans($response)], 200);
18 | }
19 |
20 | protected function sendResetFailedResponse(Request $request, $response)
21 | {
22 | return response()->json(['email' => trans($response)], 200);
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | newQuery();
8 | // if ($request->q) {
9 | // $query->orWhere(function($q) use ($request){
10 | // $q->where('title', 'LIKE', '%'.$request->q.'%')
11 | // ->orWhere('description', 'LIKE', '%'.$request->q.'%');
12 | // });
13 | // }
14 | // $query->has('comments');
15 | // $q->has('team');
16 | // $q->withCount('likes')
17 | // ->orderByDesc('likes_count');
18 |
19 |
20 | // get by tag
21 | //$q->withAllTags('adobe-photoshop');
22 | // dd($query->get());
23 |
24 |
25 |
26 |
27 | return view('welcome');
28 | });
29 |
--------------------------------------------------------------------------------
/app/Notifications/VerifyEmail.php:
--------------------------------------------------------------------------------
1 | addMinutes(60),
22 | ['user' => $notifiable->id]
23 | );
24 |
25 | return str_replace(url('/api'), $appUrl, $url);
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Http/Resources/MessageResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'body' => $this->body,
20 | 'deleted' => $this->trashed(),
21 | 'dates' => [
22 | 'created_at_human' => $this->created_at->diffForHumans(),
23 | 'created_at' => $this->created_at,
24 | ],
25 | 'sender' => new UserResource($this->sender)
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Http/Resources/TeamResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'name' => $this->name,
20 | 'total_members' => $this->members->count(),
21 | 'slug' => $this->slug,
22 | 'designs' => DesignResource::collection($this->designs),
23 | 'owner' => new UserResource($this->owner),
24 | 'member' => UserResource::collection($this->members)
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/resources/lang/en/passwords.php:
--------------------------------------------------------------------------------
1 | 'Your password has been reset!',
17 | 'sent' => 'We have e-mailed 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 e-mail address.",
21 |
22 | ];
23 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/Providers/EventServiceProvider.php:
--------------------------------------------------------------------------------
1 | [
19 | SendEmailVerificationNotification::class,
20 | ],
21 | ];
22 |
23 | /**
24 | * Register any events for your application.
25 | *
26 | * @return void
27 | */
28 | public function boot()
29 | {
30 | parent::boot();
31 |
32 | //
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/Http/Resources/ChatResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'dates' => [
20 | 'created_at_human' => $this->created_at->diffForHumans(),
21 | 'created_at' => $this->created_at,
22 | ],
23 | 'is_unread' => $this->isUnreadForUser(auth()->id()),
24 | 'latest_message' => new MessageResource($this->latest_message),
25 | 'participants' => UserResource::collection($this->participants)
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Notifications/ResetPassword.php:
--------------------------------------------------------------------------------
1 | token).
17 | '?email='.urlencode($notifiable->email);
18 | return (new MailMessage)
19 | ->line('you are receiving this email because we received a password reset request for your account')
20 | ->action('Reset Password', $url)
21 | ->line('If you did not request a password reset, no further action is required.');
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/database/migrations/2020_01_23_003838_add_fields_to_designs.php:
--------------------------------------------------------------------------------
1 | boolean('upload_successful')->default(false);
18 | $table->string('disk')->default('public');
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | *
25 | * @return void
26 | */
27 | public function down()
28 | {
29 | Schema::table('designs', function (Blueprint $table) {
30 | $table->dropColumn(['disk', 'upload_successful']);
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/database/migrations/2020_02_01_011027_add_team_id_to_designs.php:
--------------------------------------------------------------------------------
1 | bigInteger('team_id')
18 | ->after('user_id')
19 | ->unsigned()
20 | ->nullable();
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | *
27 | * @return void
28 | */
29 | public function down()
30 | {
31 | Schema::table('designs', function (Blueprint $table) {
32 | $table->dropColumn(['team_id']);
33 | });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/Rules/MatchOldPassword.php:
--------------------------------------------------------------------------------
1 | user()->password);
30 | }
31 |
32 | /**
33 | * Get the validation error message.
34 | *
35 | * @return string
36 | */
37 | public function message()
38 | {
39 | return 'You have provided a wrong current password';
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/Rules/CheckSamePassword.php:
--------------------------------------------------------------------------------
1 | user()->password);
30 | }
31 |
32 | /**
33 | * Get the validation error message.
34 | *
35 | * @return string
36 | */
37 | public function message()
38 | {
39 | return 'Your new password must be different from your current password';
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/Http/Resources/CommentResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'body' => $this->body,
20 | 'created_at_dates' => [
21 | 'created_at_human' => $this->created_at->diffForHumans(),
22 | 'created_at' => $this->created_at
23 | ],
24 | 'updated_at_dates' => [
25 | 'updated_at_human' => $this->updated_at->diffForHumans(),
26 | 'updated_at' => $this->updated_at
27 | ],
28 | 'user' => new UserResource($this->user)
29 | ];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/database/migrations/2019_08_19_000000_create_failed_jobs_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->text('connection');
19 | $table->text('queue');
20 | $table->longText('payload');
21 | $table->longText('exception');
22 | $table->timestamp('failed_at')->useCurrent();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | *
29 | * @return void
30 | */
31 | public function down()
32 | {
33 | Schema::dropIfExists('failed_jobs');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/Models/Message.php:
--------------------------------------------------------------------------------
1 | trashed()){
21 | if(!auth()->check()) return null;
22 |
23 | return auth()->id() == $this->sender->id ?
24 | 'You deleted this message' :
25 | "{$this->sender->name} deleted this message";
26 | }
27 | return $value;
28 | }
29 |
30 | public function chat()
31 | {
32 | return $this->belongsTo(Chat::class);
33 | }
34 |
35 | public function sender()
36 | {
37 | return $this->belongsTo(User::class, 'user_id');
38 | }
39 |
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 | define(User::class, function (Faker $faker) {
20 | return [
21 | 'name' => $faker->name,
22 | 'email' => $faker->unique()->safeEmail,
23 | 'email_verified_at' => now(),
24 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
25 | 'remember_token' => Str::random(10),
26 | ];
27 | });
28 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=DesignHouse
2 | APP_ENV=local
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_URL=
6 | CLIENT_URL=
7 |
8 | LOG_CHANNEL=stack
9 |
10 | # MySql v5.7
11 | DB_CONNECTION=mysql
12 | DB_HOST=127.0.0.1
13 | DB_PORT=3306
14 | DB_DATABASE=
15 | DB_USERNAME=
16 | DB_PASSWORD=
17 |
18 | BROADCAST_DRIVER=log
19 | CACHE_DRIVER=file
20 | QUEUE_CONNECTION=sync
21 | SESSION_DRIVER=cookie
22 | SESSION_LIFETIME=120
23 |
24 | REDIS_HOST=127.0.0.1
25 | REDIS_PASSWORD=null
26 | REDIS_PORT=6379
27 |
28 | MAIL_DRIVER=smtp
29 | MAIL_HOST=smtp.mailtrap.io
30 | MAIL_PORT=2525
31 | MAIL_USERNAME=
32 | MAIL_PASSWORD=
33 | MAIL_ENCRYPTION=null
34 |
35 | AWS_ACCESS_KEY_ID=
36 | AWS_SECRET_ACCESS_KEY=
37 | AWS_DEFAULT_REGION=us-east-1
38 | AWS_BUCKET=
39 |
40 | PUSHER_APP_ID=
41 | PUSHER_APP_KEY=
42 | PUSHER_APP_SECRET=
43 | PUSHER_APP_CLUSTER=mt1
44 |
45 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
46 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
47 |
48 | JWT_SECRET=3JVhu6aTifZERvah1wNtH9aIpgqGBESbKmlcu6fg95LWE6BlSKnTeSGmffwoTHTX
49 |
--------------------------------------------------------------------------------
/database/migrations/2020_01_31_224355_create_messages_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->bigInteger('user_id')->unsigned();
19 | $table->bigInteger('chat_id')->unsigned();
20 | $table->text('body');
21 | $table->datetime('last_read')->nullable();
22 | $table->softDeletes();
23 | $table->timestamps();
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('messages');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | command('inspire')
28 | // ->hourly();
29 | }
30 |
31 | /**
32 | * Register the commands for the application.
33 | *
34 | * @return void
35 | */
36 | protected function commands()
37 | {
38 | $this->load(__DIR__.'/Commands');
39 |
40 | require base_path('routes/console.php');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/database/migrations/2020_01_23_021358_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/2020_01_26_031336_create_comments_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->bigInteger('user_id')->unsigned()->index();
19 | $table->text('body');
20 | $table->morphs('commentable');
21 | $table->timestamps();
22 |
23 | $table->foreign('user_id')->references('id')
24 | ->on('users')->onDelete('cascade');
25 | });
26 | }
27 |
28 | /**
29 | * Reverse the migrations.
30 | *
31 | * @return void
32 | */
33 | public function down()
34 | {
35 | Schema::dropIfExists('comments');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/database/migrations/2020_01_27_232327_create_likes_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->bigInteger('user_id')
19 | ->unsigned()->index();
20 | $table->morphs('likeable');
21 | $table->timestamps();
22 |
23 | $table->foreign('user_id')
24 | ->references('id')
25 | ->on('users')
26 | ->onDelete('cascade');
27 | });
28 | }
29 |
30 | /**
31 | * Reverse the migrations.
32 | *
33 | * @return void
34 | */
35 | public function down()
36 | {
37 | Schema::dropIfExists('likes');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6 | "watch": "npm run development -- --watch",
7 | "watch-poll": "npm run watch -- --watch-poll",
8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
9 | "prod": "npm run production",
10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11 | },
12 | "devDependencies": {
13 | "axios": "^0.19",
14 | "cross-env": "^5.1",
15 | "laravel-mix": "^4.0.7",
16 | "lodash": "^4.17.13",
17 | "resolve-url-loader": "^2.3.1",
18 | "sass": "^1.15.2",
19 | "sass-loader": "^7.1.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/Models/Chat.php:
--------------------------------------------------------------------------------
1 | belongsToMany(User::class, 'participants');
14 | }
15 |
16 | public function messages()
17 | {
18 | return $this->hasMany(Message::class);
19 | }
20 |
21 | // helper
22 | public function getLatestMessageAttribute()
23 | {
24 | return $this->messages()->latest()->first();
25 | }
26 |
27 | public function isUnreadForUser($userId)
28 | {
29 | return (bool)$this->messages()
30 | ->whereNull('last_read')
31 | ->where('user_id', '<>', $userId)
32 | ->count();
33 | }
34 |
35 | public function markAsReadForUser($userId)
36 | {
37 | $this->messages()
38 | ->whereNull('last_read')
39 | ->where('user_id', '<>', $userId)
40 | ->update([
41 | 'last_read' => Carbon::now()
42 | ]);
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/app/Http/Controllers/User/UserController.php:
--------------------------------------------------------------------------------
1 | users = $users;
20 | }
21 |
22 | public function index()
23 | {
24 | $users = $this->users->withCriteria([
25 | new EagerLoad(['designs'])
26 | ])->all();
27 |
28 | return UserResource::collection($users);
29 | }
30 |
31 | public function search(Request $request)
32 | {
33 | $designers = $this->users->search($request);
34 | return UserResource::collection($designers);
35 | }
36 |
37 | public function findByUsername($username)
38 | {
39 | $user = $this->users->findWhereFirst('username', $username);
40 | return new UserResource($user);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/database/migrations/2020_01_22_213725_create_designs_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->bigInteger('user_id')->unsigned();
19 | $table->string('image');
20 | $table->string('title')->nullable();
21 | $table->string('description')->nullable();
22 | $table->string('slug')->nullable();
23 | $table->boolean('is_live')->default(false);
24 | $table->timestamps();
25 |
26 | $table->foreign('user_id')->references('id')->on('users');
27 | });
28 | }
29 |
30 | /**
31 | * Reverse the migrations.
32 | *
33 | * @return void
34 | */
35 | public function down()
36 | {
37 | Schema::dropIfExists('designs');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ConfirmPasswordController.php:
--------------------------------------------------------------------------------
1 | middleware('auth');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/Http/Middleware/ProfileJsonResponse.php:
--------------------------------------------------------------------------------
1 | bound('debugbar') || ! app('debugbar')->isEnabled() ){
25 | return $response;
26 | }
27 |
28 | // profile the json response
29 | if($response instanceof JsonResponse && $request->has('_debug')){
30 |
31 | // $response->setData(array_merge($response->getData(true), [
32 | // '_debugbar' => app('debugbar')->getData()
33 | // ]));
34 | $response->setData(array_merge([
35 | '_debugbar' => Arr::only(app('debugbar')->getData(), 'queries')
36 | ], $response->getData(true)));
37 |
38 | }
39 |
40 | return $response;
41 |
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/Providers/AuthServiceProvider.php:
--------------------------------------------------------------------------------
1 | 'App\Policies\ModelPolicy',
27 | Design::class => DesignPolicy::class,
28 | Comment::class => CommentPolicy::class,
29 | Team::class => TeamPolicy::class,
30 | Invitation::class => InvitationPolicy::class,
31 | Message::class => MessagePolicy::class
32 | ];
33 |
34 | /**
35 | * Register any authentication / authorization services.
36 | *
37 | * @return void
38 | */
39 | public function boot()
40 | {
41 | $this->registerPolicies();
42 |
43 | //
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/Http/Resources/UserResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'username' => $this->username,
20 | $this->mergeWhen(auth()->check() && auth()->id() == $this->id, [
21 | 'email' => $this->email,
22 | ]),
23 | 'name' => $this->name,
24 | 'photo_url' => $this->photo_url,
25 | 'designs' => DesignResource::collection(
26 | $this->whenLoaded('designs')
27 | ),
28 | 'create_dates' => [
29 | 'created_at_human' => $this->created_at->diffForHumans(),
30 | 'created_at' => $this->created_at
31 | ],
32 | 'formatted_address' => $this->formatted_address,
33 | 'tagline' => $this->tagline,
34 | 'about' => $this->about,
35 | 'location' => $this->location,
36 | 'available_to_hire' => $this->available_to_hire,
37 | ];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/database/migrations/2020_01_29_234339_create_invitations_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->bigInteger('team_id')->unsigned();
19 | $table->bigInteger('sender_id')->unsigned();
20 | $table->string('recipient_email')->index();
21 | $table->string('token');
22 | $table->timestamps();
23 |
24 | $table->foreign('team_id')
25 | ->references('id')
26 | ->on('teams')
27 | ->onDelete('cascade');
28 |
29 | $table->foreign('sender_id')
30 | ->references('id')
31 | ->on('users')
32 | ->onDelete('cascade');
33 | });
34 | }
35 |
36 | /**
37 | * Reverse the migrations.
38 | *
39 | * @return void
40 | */
41 | public function down()
42 | {
43 | Schema::dropIfExists('invitations');
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->string('username')->unique();
19 | $table->string('name');
20 | $table->string('email')->unique();
21 | $table->timestamp('email_verified_at')->nullable();
22 | $table->string('tagline')->nullable();
23 | $table->text('about')->nullable();
24 | $table->point('location')->nullable();
25 | $table->string('formatted_address')->nullable();
26 | $table->boolean('available_to_hire')->default(false);
27 | $table->string('password');
28 | $table->rememberToken();
29 | $table->timestamps();
30 | });
31 | }
32 |
33 | /**
34 | * Reverse the migrations.
35 | *
36 | * @return void
37 | */
38 | public function down()
39 | {
40 | Schema::dropIfExists('users');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/database/migrations/2020_01_31_224345_create_chats_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->timestamps();
19 | });
20 |
21 | Schema::create('participants', function (Blueprint $table) {
22 | $table->bigInteger('chat_id')->unsigned();
23 | $table->bigInteger('user_id')->unsigned();
24 |
25 | $table->foreign('user_id')
26 | ->references('id')
27 | ->on('users')
28 | ->onDelete('cascade');
29 |
30 | $table->foreign('chat_id')
31 | ->references('id')
32 | ->on('chats')
33 | ->onDelete('cascade');
34 | });
35 | }
36 |
37 | /**
38 | * Reverse the migrations.
39 | *
40 | * @return void
41 | */
42 | public function down()
43 | {
44 | Schema::dropIfExists('participants');
45 | Schema::dropIfExists('chats');
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/Providers/RepositoryServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->bind(IDesign::class, DesignRepository::class);
45 | $this->app->bind(IUser::class, UserRepository::class);
46 | $this->app->bind(IComment::class, CommentRepository::class);
47 | $this->app->bind(ITeam::class, TeamRepository::class);
48 | $this->app->bind(IInvitation::class, InvitationRepository::class);
49 | $this->app->bind(IChat::class, ChatRepository::class);
50 | $this->app->bind(IMessage::class, MessageRepository::class);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/Models/Traits/Likeable.php:
--------------------------------------------------------------------------------
1 | removeLikes();
12 | });
13 | }
14 |
15 | // delete likes when model is being deleted
16 | public function removeLikes()
17 | {
18 | if($this->likes()->count()){
19 | $this->likes()->delete();
20 | }
21 | }
22 |
23 |
24 | public function likes()
25 | {
26 | return $this->morphMany(Like::class, 'likeable');
27 | }
28 |
29 | public function like()
30 | {
31 | if(! auth()->check()) return;
32 |
33 | // check if the current user has already liked the model
34 | if($this->isLikedByUser(auth()->id())){
35 | return;
36 | };
37 |
38 | $this->likes()->create(['user_id' => auth()->id()]);
39 | }
40 |
41 | public function unlike()
42 | {
43 | if(! auth()->check()) return;
44 |
45 | if(! $this->isLikedByUser(auth()->id())){
46 | return;
47 | }
48 |
49 | $this->likes()
50 | ->where('user_id', auth()
51 | ->id())->delete();
52 | }
53 |
54 | public function isLikedByUser($user_id)
55 | {
56 | return (bool)$this->likes()
57 | ->where('user_id', $user_id)
58 | ->count();
59 | }
60 |
61 |
62 |
63 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests/Unit
16 |
17 |
18 |
19 | ./tests/Feature
20 |
21 |
22 |
23 |
24 | ./app
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/Models/Team.php:
--------------------------------------------------------------------------------
1 | user()->teams()->attach($team->id);
23 | $team->members()->attach(auth()->id());
24 | });
25 |
26 | static::deleting(function($team){
27 | $team->members()->sync([]);
28 | });
29 |
30 | }
31 |
32 | public function owner()
33 | {
34 | return $this->belongsTo(User::class, 'owner_id');
35 | }
36 |
37 | public function members()
38 | {
39 | return $this->belongsToMany(User::class)
40 | ->withTimestamps();
41 | }
42 |
43 | public function designs()
44 | {
45 | return $this->hasMany(Design::class);
46 | }
47 |
48 | public function hasUser(User $user)
49 | {
50 | return $this->members()
51 | ->where('user_id', $user->id)
52 | ->first() ? true : false;
53 | }
54 |
55 | public function invitations()
56 | {
57 | return $this->hasMany(Invitation::class);
58 | }
59 |
60 | public function hasPendingInvite($email)
61 | {
62 | return (bool)$this->invitations()
63 | ->where('recipient_email', $email)
64 | ->count();
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/database/migrations/2020_01_29_214651_create_teams_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->string('name');
19 | $table->string('slug')->unique()->index();
20 | $table->bigInteger('owner_id')->unsigned();
21 | $table->timestamps();
22 |
23 | $table->foreign('owner_id')
24 | ->references('id')
25 | ->on('users');
26 |
27 | });
28 |
29 | Schema::create('team_user', function (Blueprint $table) {
30 | $table->bigInteger('team_id')->unsigned();
31 | $table->bigInteger('user_id')->unsigned();
32 | $table->timestamps();
33 |
34 | $table->foreign('team_id')
35 | ->references('id')
36 | ->on('teams')
37 | ->onDelete('cascade');
38 |
39 | $table->foreign('user_id')
40 | ->references('id')
41 | ->on('users')
42 | ->onDelete('cascade');
43 | });
44 | }
45 |
46 | /**
47 | * Reverse the migrations.
48 | *
49 | * @return void
50 | */
51 | public function down()
52 | {
53 | Schema::dropIfExists('team_users');
54 | Schema::dropIfExists('teams');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/Models/Design.php:
--------------------------------------------------------------------------------
1 | 'boolean',
29 | 'upload_successful' => 'boolean',
30 | 'close_to_comments' => 'boolean'
31 | ];
32 |
33 | public function user()
34 | {
35 | return $this->belongsTo(User::class);
36 | }
37 |
38 | public function team()
39 | {
40 | return $this->belongsTo(Team::class);
41 | }
42 |
43 | public function comments()
44 | {
45 | return $this->morphMany(Comment::class, 'commentable')
46 | ->orderBy('created_at', 'asc');
47 | }
48 |
49 | public function getImagesAttribute()
50 | {
51 |
52 | return [
53 | 'thumbnail' => $this->getImagePath('thumbnail'),
54 | 'large' => $this->getImagePath('large'),
55 | 'original' => $this->getImagePath('original'),
56 | ];
57 | }
58 |
59 | protected function getImagePath($size)
60 | {
61 | return Storage::disk($this->disk)
62 | ->url("uploads/designs/{$size}/".$this->image);
63 | }
64 |
65 |
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Designs/CommentController.php:
--------------------------------------------------------------------------------
1 | comments = $comments;
20 | $this->designs = $designs;
21 | }
22 |
23 | public function store(Request $request, $designId)
24 | {
25 | $this->validate($request, [
26 | 'body' => ['required']
27 | ]);
28 |
29 | $comment = $this->designs->addComment($designId, [
30 | 'body' => $request->body,
31 | 'user_id' => auth()->id()
32 | ]);
33 |
34 | return new CommentResource($comment);
35 | }
36 |
37 | public function update(Request $request, $id)
38 | {
39 | $comment = $this->comments->find($id);
40 | $this->authorize('update', $comment);
41 |
42 | $this->validate($request, [
43 | 'body' => ['required']
44 | ]);
45 | $comment = $this->comments->update($id, [
46 | 'body' => $request->body
47 | ]);
48 | return new CommentResource($comment);
49 | }
50 |
51 | public function destroy($id)
52 | {
53 | $comment = $this->comments->find($id);
54 | $this->authorize('delete', $comment);
55 | $this->comments->delete($id);
56 | return response()->json(['message' => 'Item deleted'], 200);
57 | }
58 |
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/config/cors.php:
--------------------------------------------------------------------------------
1 | ['api/*'],
25 |
26 | /*
27 | * Matches the request method. `[*]` allows all methods.
28 | */
29 | 'allowed_methods' => ['*'],
30 |
31 | /*
32 | * Matches the request origin. `[*]` allows all origins.
33 | */
34 | 'allowed_origins' => ['*'],
35 |
36 | /*
37 | * Matches the request origin with, similar to `Request::is()`
38 | */
39 | 'allowed_origins_patterns' => [],
40 |
41 | /*
42 | * Sets the Access-Control-Allow-Headers response header. `[*]` allows all headers.
43 | */
44 | 'allowed_headers' => ['*'],
45 |
46 | /*
47 | * Sets the Access-Control-Expose-Headers response header.
48 | */
49 | 'exposed_headers' => false,
50 |
51 | /*
52 | * Sets the Access-Control-Max-Age response header.
53 | */
54 | 'max_age' => false,
55 |
56 | /*
57 | * Sets the Access-Control-Allow-Credentials header.
58 | */
59 | 'supports_credentials' => false,
60 | ];
61 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/Http/Resources/DesignResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'title' => $this->title,
20 | 'slug' => $this->slug,
21 | 'images' => $this->images,
22 | 'is_live' => $this->is_live,
23 | 'likes_count' => $this->likes()->count(),
24 | 'description' => $this->description,
25 | 'tag_list' => [
26 | 'tags' => $this->tagArray,
27 | 'normalized' => $this->tagArrayNormalized,
28 | ],
29 | 'created_at_dates' => [
30 | 'created_at_human' => $this->created_at->diffForHumans(),
31 | 'created_at' => $this->created_at
32 | ],
33 | 'updated_at_dates' => [
34 | 'updated_at_human' => $this->updated_at->diffForHumans(),
35 | 'updated_at' => $this->updated_at
36 | ],
37 | 'team' => $this->team ? [
38 | 'id' => $this->team->id,
39 | 'name' => $this->team->name,
40 | 'slug' => $this->team->slug
41 | ] : null,
42 | 'comments_count' => $this->comments()->count(),
43 | 'comments' => CommentResource::collection(
44 | $this->whenLoaded('comments')),
45 | 'user' => new UserResource($this->whenLoaded('user'))
46 |
47 | ];
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/Mail/SendInvitationToJoinTeam.php:
--------------------------------------------------------------------------------
1 | invitation = $invitation;
26 | $this->user_exists = $user_exists;
27 | }
28 |
29 | /**
30 | * Build the message.
31 | *
32 | * @return $this
33 | */
34 | public function build()
35 | {
36 | if($this->user_exists){
37 | $url = config('app.client_url').'/settings/teams';
38 | return $this->markdown('emails.invitations.invite-existing-user')
39 | ->subject('Invitation to join team '. $this->invitation->team->name)
40 | ->with([
41 | 'invitation' => $this->invitation,
42 | 'url' => $url
43 | ]);
44 | } else {
45 | $url = config('app.client_url').'/register?invitation='.$this->invitation->recipient_email;
46 | return $this->markdown('emails.invitations.invite-new-user')
47 | ->subject('Invitation to join team '. $this->invitation->team->name)
48 | ->with([
49 | 'invitation' => $this->invitation,
50 | 'url' => $url
51 | ]);
52 | }
53 |
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/Repositories/Eloquent/UserRepository.php:
--------------------------------------------------------------------------------
1 | model
19 | ->where('email', $email)
20 | ->first();
21 | }
22 |
23 | public function search(Request $request)
24 | {
25 | $query = (new $this->model)->newQuery();
26 |
27 | // only designers who have designs
28 | if($request->has_designs){
29 | $query->has('designs');
30 | }
31 |
32 | // check for available_to_hire
33 | if($request->available_to_hire){
34 | $query->where('available_to_hire', true);
35 | }
36 |
37 | // Geographic Search
38 | $lat = $request->latitude;
39 | $lng = $request->longitude;
40 | $dist = $request->distance;
41 | $unit = $request->unit;
42 |
43 | if($lat && $lng){
44 | $point = new Point($lat, $lng);
45 | $unit == 'km' ? $dist *= 1000 : $dist *=1609.34;
46 | $query->distanceSphereExcludingSelf('location', $point, $dist);
47 | }
48 |
49 | // order the results
50 | if($request->orderBy=='closest'){
51 | $query->orderByDistanceSphere('location', $point, 'asc');
52 | } else if($request->orderBy=='latest'){
53 | $query->latest();
54 | } else {
55 | $query->oldest();
56 | }
57 |
58 | return $query->get();
59 |
60 |
61 | }
62 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | 'redis' => [
45 | 'driver' => 'redis',
46 | 'connection' => 'default',
47 | ],
48 |
49 | 'log' => [
50 | 'driver' => 'log',
51 | ],
52 |
53 | 'null' => [
54 | 'driver' => 'null',
55 | ],
56 |
57 | ],
58 |
59 | ];
60 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Designs/UploadController.php:
--------------------------------------------------------------------------------
1 | designs = $designs;
17 | }
18 |
19 | public function upload(Request $request)
20 | {
21 | // validate the request
22 | $this->validate($request, [
23 | 'image' => ['required', 'mimes:jpeg,gif,bmp,png', 'max:2048']
24 | ]);
25 |
26 | // get the image
27 | $image = $request->file('image');
28 | $image_path = $image->getPathName();
29 |
30 |
31 | // get the original file name and replace any spaces with _
32 | // Business Cards.png = timestamp()_business_cards.png
33 | $filename = time()."_". preg_replace('/\s+/', '_', strtolower($image->getClientOriginalName()));
34 |
35 | // move the image to the temporary location (tmp)
36 | $tmp = $image->storeAs('uploads/original', $filename, 'tmp');
37 |
38 | // create the database record for the design
39 | // $design = auth()->user()->designs()->create([
40 | // 'image' => $filename,
41 | // 'disk' => config('site.upload_disk')
42 | // ]);
43 |
44 | $design = $this->designs->create([
45 | 'user_id' => auth()->id(),
46 | 'image' => $filename,
47 | 'disk' => config('site.upload_disk')
48 | ]);
49 |
50 | // dispatch a job to handle the image manipulation
51 | $this->dispatch(new UploadImage($design));
52 |
53 | return response()->json($design, 200);
54 |
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/Providers/RouteServiceProvider.php:
--------------------------------------------------------------------------------
1 | mapApiRoutes();
46 |
47 | $this->mapWebRoutes();
48 |
49 | //
50 | }
51 |
52 | /**
53 | * Define the "web" routes for the application.
54 | *
55 | * These routes all receive session state, CSRF protection, etc.
56 | *
57 | * @return void
58 | */
59 | protected function mapWebRoutes()
60 | {
61 | Route::middleware('web')
62 | ->namespace($this->namespace)
63 | ->group(base_path('routes/web.php'));
64 | }
65 |
66 | /**
67 | * Define the "api" routes for the application.
68 | *
69 | * These routes are typically stateless.
70 | *
71 | * @return void
72 | */
73 | protected function mapApiRoutes()
74 | {
75 | Route::prefix('api')
76 | ->middleware('api')
77 | ->namespace($this->namespace)
78 | ->group(base_path('routes/api.php'));
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | define('LARAVEL_START', microtime(true));
11 |
12 | /*
13 | |--------------------------------------------------------------------------
14 | | Register The Auto Loader
15 | |--------------------------------------------------------------------------
16 | |
17 | | Composer provides a convenient, automatically generated class loader for
18 | | our application. We just need to utilize it! We'll simply require it
19 | | into the script here so that we don't have to worry about manual
20 | | loading any of our classes later on. It feels great to relax.
21 | |
22 | */
23 |
24 | require __DIR__.'/../vendor/autoload.php';
25 |
26 | /*
27 | |--------------------------------------------------------------------------
28 | | Turn On The Lights
29 | |--------------------------------------------------------------------------
30 | |
31 | | We need to illuminate PHP development, so let us turn on the lights.
32 | | This bootstraps the framework and gets it ready for use, then it
33 | | will load up this application so that we can run it and send
34 | | the responses back to the browser and delight our users.
35 | |
36 | */
37 |
38 | $app = require_once __DIR__.'/../bootstrap/app.php';
39 |
40 | /*
41 | |--------------------------------------------------------------------------
42 | | Run The Application
43 | |--------------------------------------------------------------------------
44 | |
45 | | Once we have the application, we can handle the incoming request
46 | | through the kernel, and send the associated response back to
47 | | the client's browser allowing them to enjoy the creative
48 | | and wonderful application we have prepared for them.
49 | |
50 | */
51 |
52 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
53 |
54 | $response = $kernel->handle(
55 | $request = Illuminate\Http\Request::capture()
56 | );
57 |
58 | $response->send();
59 |
60 | $kernel->terminate($request, $response);
61 |
--------------------------------------------------------------------------------
/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.2",
12 | "cviebrock/eloquent-taggable": "^6.0",
13 | "fideloper/proxy": "^4.0",
14 | "fruitcake/laravel-cors": "^1.0",
15 | "grimzy/laravel-mysql-spatial": "^2.2",
16 | "intervention/image": "^2.5",
17 | "laravel/framework": "^6.2",
18 | "laravel/tinker": "^2.0",
19 | "league/flysystem-aws-s3-v3": "^1.0",
20 | "tymon/jwt-auth": "^1.0.0-rc.5"
21 | },
22 | "require-dev": {
23 | "barryvdh/laravel-debugbar": "^3.2",
24 | "facade/ignition": "^1.4",
25 | "fzaninotto/faker": "^1.4",
26 | "mockery/mockery": "^1.0",
27 | "nunomaduro/collision": "^3.0",
28 | "phpunit/phpunit": "^8.0"
29 | },
30 | "config": {
31 | "optimize-autoloader": true,
32 | "preferred-install": "dist",
33 | "sort-packages": true
34 | },
35 | "extra": {
36 | "laravel": {
37 | "dont-discover": []
38 | }
39 | },
40 | "autoload": {
41 | "psr-4": {
42 | "App\\": "app/"
43 | },
44 | "classmap": [
45 | "database/seeds",
46 | "database/factories"
47 | ]
48 | },
49 | "autoload-dev": {
50 | "psr-4": {
51 | "Tests\\": "tests/"
52 | }
53 | },
54 | "minimum-stability": "dev",
55 | "prefer-stable": true,
56 | "scripts": {
57 | "post-autoload-dump": [
58 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
59 | "@php artisan package:discover --ansi"
60 | ],
61 | "post-root-package-install": [
62 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
63 | ],
64 | "post-create-project-cmd": [
65 | "@php artisan key:generate --ansi"
66 | ]
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/Http/Controllers/User/SettingsController.php:
--------------------------------------------------------------------------------
1 | users = $users;
19 | }
20 |
21 | public function updateProfile(Request $request)
22 | {
23 | $user = auth()->user();
24 |
25 | $this->validate($request, [
26 | 'tagline' => ['required'],
27 | 'name' => ['required'],
28 | 'about' => ['required', 'string', 'min:20'],
29 | 'formatted_address' => ['required'],
30 | 'location.latitude' => ['required', 'numeric', 'min:-90', 'max:90'],
31 | 'location.longitude' => ['required', 'numeric', 'min:-180', 'max:180']
32 | ]);
33 |
34 | $location = new Point($request->location['latitude'], $request->location['longitude']);
35 |
36 |
37 | $user = $this->users->update(auth()->id(), [
38 | 'name' => $request->name,
39 | 'formatted_address' => $request->formatted_address,
40 | 'location' => $location,
41 | 'available_to_hire' => $request->available_to_hire,
42 | 'about' => $request->about,
43 | 'tagline' => $request->tagline,
44 | ]);
45 |
46 | return new UserResource($user);
47 |
48 | }
49 |
50 | public function updatePassword(Request $request)
51 | {
52 |
53 | $this->validate($request, [
54 | 'current_password' => ['required', new MatchOldPassword],
55 | 'password' => ['required', 'confirmed', 'min:6', new CheckSamePassword],
56 | ]);
57 |
58 | $this->users->update(auth()->id(), [
59 | 'password' => bcrypt($request->password)
60 | ]);
61 |
62 | return response()->json(['message' => 'Password updated'], 200);
63 |
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/app/Repositories/Eloquent/BaseRepository.php:
--------------------------------------------------------------------------------
1 | model = $this->getModelClass();
19 | }
20 |
21 | public function all()
22 | {
23 | return $this->model->get();
24 | }
25 |
26 | public function find($id)
27 | {
28 | $result = $this->model->findOrFail($id);
29 | return $result;
30 | }
31 |
32 | public function findWhere($column, $value)
33 | {
34 | return $this->model->where($column, $value)->get();
35 | }
36 |
37 |
38 | public function findWhereFirst($column, $value)
39 | {
40 | return $this->model->where($column, $value)->firstOrFail();
41 | }
42 |
43 | public function paginate($perPage = 10)
44 | {
45 | return $this->model->paginate($perPage);
46 | }
47 |
48 | public function create(array $data)
49 | {
50 | $result = $this->model->create($data);
51 | return $result;
52 | }
53 |
54 | public function update($id, array $data)
55 | {
56 | $record = $this->find($id);
57 | $record->update($data);
58 | return $record;
59 | }
60 |
61 | public function delete($id)
62 | {
63 | $record = $this->find($id);
64 | return $record->delete();
65 | }
66 |
67 |
68 | public function withCriteria(...$criteria)
69 | {
70 | $criteria = Arr::flatten($criteria);
71 |
72 | foreach($criteria as $criterion){
73 | $this->model = $criterion->apply($this->model);
74 | }
75 |
76 | return $this;
77 | }
78 |
79 |
80 |
81 | protected function getModelClass()
82 | {
83 | if( !method_exists($this, 'model'))
84 | {
85 | throw new ModelNotDefined();
86 | }
87 |
88 | return app()->make($this->model());
89 |
90 | }
91 |
92 |
93 |
94 |
95 |
96 | }
--------------------------------------------------------------------------------
/app/Exceptions/Handler.php:
--------------------------------------------------------------------------------
1 | expectsJson()){
54 | return response()->json(["errors" => [
55 | "message" => "You are not authorized to access this resource"
56 | ]], 403);
57 | }
58 | }
59 |
60 | if($exception instanceof ModelNotFoundException && $request->expectsJson()){
61 | return response()->json(["errors" => [
62 | "message" => "The resource was not found in the database"
63 | ]], 404);
64 | }
65 |
66 | if($exception instanceof ModelNotDefined && $request->expectsJson()){
67 | return response()->json(["errors" => [
68 | "message" => "No model defined"
69 | ]], 500);
70 | }
71 |
72 |
73 |
74 |
75 | return parent::render($request, $exception);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/Policies/TeamPolicy.php:
--------------------------------------------------------------------------------
1 | isOwnerOfTeam($team);
57 | }
58 |
59 | /**
60 | * Determine whether the user can delete the team.
61 | *
62 | * @param \App\Models\User $user
63 | * @param \App\Models\Team $team
64 | * @return mixed
65 | */
66 | public function delete(User $user, Team $team)
67 | {
68 | return $user->isOwnerOfTeam($team);
69 | }
70 |
71 | /**
72 | * Determine whether the user can restore the team.
73 | *
74 | * @param \App\Models\User $user
75 | * @param \App\Models\Team $team
76 | * @return mixed
77 | */
78 | public function restore(User $user, Team $team)
79 | {
80 | //
81 | }
82 |
83 | /**
84 | * Determine whether the user can permanently delete the team.
85 | *
86 | * @param \App\Models\User $user
87 | * @param \App\Models\Team $team
88 | * @return mixed
89 | */
90 | public function forceDelete(User $user, Team $team)
91 | {
92 | //
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/Policies/MessagePolicy.php:
--------------------------------------------------------------------------------
1 | id == $message->user_id;
69 | }
70 |
71 | /**
72 | * Determine whether the user can restore the message.
73 | *
74 | * @param \App\Models\User $user
75 | * @param \App\Models\Message $message
76 | * @return mixed
77 | */
78 | public function restore(User $user, Message $message)
79 | {
80 | //
81 | }
82 |
83 | /**
84 | * Determine whether the user can permanently delete the message.
85 | *
86 | * @param \App\Models\User $user
87 | * @param \App\Models\Message $message
88 | * @return mixed
89 | */
90 | public function forceDelete(User $user, Message $message)
91 | {
92 | //
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/Policies/DesignPolicy.php:
--------------------------------------------------------------------------------
1 | user_id === $user->id;
57 | }
58 |
59 | /**
60 | * Determine whether the user can delete the design.
61 | *
62 | * @param \App\Models\User $user
63 | * @param \App\Models\Design $design
64 | * @return mixed
65 | */
66 | public function delete(User $user, Design $design)
67 | {
68 | return $design->user_id === $user->id;
69 | }
70 |
71 | /**
72 | * Determine whether the user can restore the design.
73 | *
74 | * @param \App\Models\User $user
75 | * @param \App\Models\Design $design
76 | * @return mixed
77 | */
78 | public function restore(User $user, Design $design)
79 | {
80 | //
81 | }
82 |
83 | /**
84 | * Determine whether the user can permanently delete the design.
85 | *
86 | * @param \App\Models\User $user
87 | * @param \App\Models\Design $design
88 | * @return mixed
89 | */
90 | public function forceDelete(User $user, Design $design)
91 | {
92 | //
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/LoginController.php:
--------------------------------------------------------------------------------
1 | guard()->attempt($this->credentials($request));
22 |
23 | if( ! $token){
24 | return false;
25 | }
26 |
27 | // Get the authenticated user
28 | $user = $this->guard()->user();
29 |
30 | if($user instanceof MustVerifyEmail && ! $user->hasVerifiedEmail()){
31 | return false;
32 | }
33 |
34 | // set the user's token
35 | $this->guard()->setToken($token);
36 |
37 | return true;
38 | }
39 |
40 | protected function sendLoginResponse(Request $request)
41 | {
42 | $this->clearLoginAttempts($request);
43 |
44 | // get the tokem from the authentication guard (JWT)
45 | $token = (string)$this->guard()->getToken();
46 |
47 | // extract the expiry date of the token
48 | $expiration = $this->guard()->getPayload()->get('exp');
49 |
50 | return response()->json([
51 | 'token' => $token,
52 | 'token_type' => 'bearer',
53 | 'expires_in' => $expiration
54 | ]);
55 | }
56 |
57 |
58 | protected function sendFailedLoginResponse()
59 | {
60 | $user = $this->guard()->user();
61 |
62 | if($user instanceof MustVerifyEmail && ! $user->hasVerifiedEmail()){
63 | return response()->json(["errors" => [
64 | "message" => "You need to verify your email account"
65 | ]], 422);
66 | }
67 |
68 | throw ValidationException::withMessages([
69 | $this->username() => "Invalid credentials"
70 | ]);
71 | }
72 |
73 | public function logout()
74 | {
75 | $this->guard()->logout();
76 | return response()->json(['message' => 'Logged out successfully!']);
77 | }
78 |
79 |
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/RegisterController.php:
--------------------------------------------------------------------------------
1 | users = $users;
22 | }
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Register Controller
26 | |--------------------------------------------------------------------------
27 | |
28 | | This controller handles the registration of new users as well as their
29 | | validation and creation. By default this controller uses a trait to
30 | | provide this functionality without requiring any additional code.
31 | |
32 | */
33 |
34 | use RegistersUsers;
35 |
36 | protected function registered(Request $request, User $user)
37 | {
38 | return response()->json($user, 200);
39 | }
40 |
41 | /**
42 | * Get a validator for an incoming registration request.
43 | *
44 | * @param array $data
45 | * @return \Illuminate\Contracts\Validation\Validator
46 | */
47 | protected function validator(array $data)
48 | {
49 | return Validator::make($data, [
50 | 'username' => ['required', 'string', 'max:15', 'alpha_dash', 'unique:users,username'],
51 | 'name' => ['required', 'string', 'max:255'],
52 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
53 | 'password' => ['required', 'string', 'min:8', 'confirmed'],
54 | ]);
55 | }
56 |
57 | /**
58 | * Create a new user instance after a valid registration.
59 | *
60 | * @param array $data
61 | * @return \App\User
62 | */
63 | protected function create(array $data)
64 | {
65 |
66 | return $this->users->create([
67 | 'username' => $data['username'],
68 | 'name' => $data['name'],
69 | 'email' => $data['email'],
70 | 'password' => Hash::make($data['password']),
71 | ]);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/Policies/CommentPolicy.php:
--------------------------------------------------------------------------------
1 | user_id == $user->id;
57 | }
58 |
59 | /**
60 | * Determine whether the user can delete the comment.
61 | *
62 | * @param \App\Models\User $user
63 | * @param \App\Models\Comment $comment
64 | * @return mixed
65 | */
66 | public function delete(User $user, Comment $comment)
67 | {
68 | return $comment->user_id == $user->id;
69 | }
70 |
71 | /**
72 | * Determine whether the user can restore the comment.
73 | *
74 | * @param \App\Models\User $user
75 | * @param \App\Models\Comment $comment
76 | * @return mixed
77 | */
78 | public function restore(User $user, Comment $comment)
79 | {
80 | //
81 | }
82 |
83 | /**
84 | * Determine whether the user can permanently delete the comment.
85 | *
86 | * @param \App\Models\User $user
87 | * @param \App\Models\Comment $comment
88 | * @return mixed
89 | */
90 | public function forceDelete(User $user, Comment $comment)
91 | {
92 | //
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/VerificationController.php:
--------------------------------------------------------------------------------
1 | middleware('throttle:6,1')->only('verify', 'resend');
28 | $this->users = $users;
29 | }
30 |
31 | public function verify(Request $request, User $user)
32 | {
33 | // check if the url is a valid signed url
34 | if(! URL::hasValidSignature($request)){
35 | return response()->json(["errors" => [
36 | "message" => "Invalid verification link or signature"
37 | ]], 422);
38 | }
39 |
40 | // check if the user has already verified account
41 | if($user->hasVerifiedEmail()){
42 | return response()->json(["errors" => [
43 | "message" => "Email address already verified"
44 | ]], 422);
45 | }
46 |
47 | $user->markEmailAsVerified();
48 | event(new Verified($user));
49 |
50 | return response()->json(['message' => 'Email successfully verified'], 200);
51 |
52 | }
53 |
54 | public function resend(Request $request)
55 | {
56 | $this->validate($request, [
57 | 'email' => ['email', 'required']
58 | ]);
59 |
60 | $user = $this->users->findWhereFirst('email', $request->email);
61 | // $user = User::where('email', $request->email)->first();
62 |
63 | if(! $user){
64 | return response()->json(["errors" => [
65 | "email" => "No user could be found with this email address"
66 | ]], 422);
67 | }
68 |
69 | if($user->hasVerifiedEmail()){
70 | return response()->json(["errors" => [
71 | "message" => "Email address already verified"
72 | ]], 422);
73 | }
74 |
75 | $user->sendEmailVerificationNotification();
76 |
77 | return response()->json(['status' => "verification link resent"]);
78 |
79 | }
80 |
81 |
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/app/Repositories/Eloquent/DesignRepository.php:
--------------------------------------------------------------------------------
1 | find($id);
20 | $design->retag($data);
21 | }
22 |
23 | public function addComment($designId, array $data)
24 | {
25 | // get the design for which we want to create a comment
26 | $design = $this->find($designId);
27 |
28 | // create the comment for the design
29 | $comment = $design->comments()->create($data);
30 |
31 | return $comment;
32 | }
33 |
34 | public function like($id)
35 | {
36 | $design = $this->model->findOrFail($id);
37 | if($design->isLikedByUser(auth()->id())){
38 | $design->unlike();
39 | } else {
40 | $design->like();
41 | }
42 |
43 | return $design->likes()->count();
44 | }
45 |
46 | public function isLikedByUser($id)
47 | {
48 | $design = $this->model->findOrFail($id);
49 | return $design->isLikedByUser(auth()->id());
50 | }
51 |
52 | public function search(Request $request)
53 | {
54 | $query = (new $this->model)->newQuery();
55 | $query->where('is_live', true);
56 |
57 | // return only designs with comments
58 | if($request->has_comments){
59 | $query->has('comments');
60 | }
61 |
62 | // return only designs assigned to teams
63 | if($request->has_team){
64 | $query->has('team');
65 | }
66 |
67 | // search title and description for provided string
68 | if($request->q){
69 | $query->where(function($q) use ($request){
70 | $q->where('title', 'like', '%'.$request->q.'%')
71 | ->orWhere('description', 'like', '%'.$request->q.'%');
72 | });
73 | }
74 |
75 | // order the query by likes or latest first
76 | if($request->orderBy=='likes'){
77 | $query->withCount('likes') // likes_count
78 | ->orderByDesc('likes_count');
79 | } else {
80 | $query->latest();
81 | }
82 |
83 | return $query->with('user')->get();
84 | }
85 |
86 |
87 | }
--------------------------------------------------------------------------------
/config/filesystems.php:
--------------------------------------------------------------------------------
1 | env('FILESYSTEM_DRIVER', 'local'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Default Cloud Filesystem Disk
21 | |--------------------------------------------------------------------------
22 | |
23 | | Many applications store files both locally and in the cloud. For this
24 | | reason, you may specify a default "cloud" driver here. This driver
25 | | will be bound as the Cloud disk implementation in the container.
26 | |
27 | */
28 |
29 | 'cloud' => env('FILESYSTEM_CLOUD', 's3'),
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Filesystem Disks
34 | |--------------------------------------------------------------------------
35 | |
36 | | Here you may configure as many filesystem "disks" as you wish, and you
37 | | may even configure multiple disks of the same driver. Defaults have
38 | | been setup for each driver as an example of the required options.
39 | |
40 | | Supported Drivers: "local", "ftp", "sftp", "s3"
41 | |
42 | */
43 |
44 | 'disks' => [
45 |
46 | 'local' => [
47 | 'driver' => 'local',
48 | 'root' => storage_path('app'),
49 | ],
50 |
51 | 'public' => [
52 | 'driver' => 'local',
53 | 'root' => storage_path('app/public'),
54 | 'url' => env('APP_URL').'/storage',
55 | 'visibility' => 'public',
56 | ],
57 |
58 | 'tmp' => [
59 | 'driver' => 'local',
60 | 'root' => storage_path(),
61 | 'url' => env('APP_URL').'/storage',
62 | 'visibility' => 'public'
63 | ],
64 |
65 | 's3' => [
66 | 'driver' => 's3',
67 | 'key' => env('AWS_ACCESS_KEY_ID'),
68 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
69 | 'region' => env('AWS_DEFAULT_REGION'),
70 | 'bucket' => env('AWS_BUCKET'),
71 | 'url' => env('AWS_URL'),
72 | ],
73 |
74 | ],
75 |
76 | ];
77 |
--------------------------------------------------------------------------------
/database/migrations/2020_01_25_114136_create_taggable_table.php:
--------------------------------------------------------------------------------
1 | hasTable($taggableTagsTable)) {
21 | Schema::connection($connection)->create($taggableTagsTable, static function(Blueprint $table) {
22 | $table->bigIncrements('tag_id');
23 | $table->string('name');
24 | $table->string('normalized')->unique();
25 | $table->timestamps();
26 |
27 | $table->index('normalized');
28 | });
29 | }
30 |
31 | if (!Schema::connection($connection)->hasTable($taggableTaggablesTable)) {
32 | Schema::connection($connection)->create($taggableTaggablesTable, static function(Blueprint $table) {
33 | $table->unsignedBigInteger('tag_id');
34 | $table->unsignedBigInteger('taggable_id');
35 | $table->string('taggable_type');
36 | $table->timestamps();
37 |
38 | $table->unique(['tag_id', 'taggable_id', 'taggable_type']);
39 |
40 | $table->index(['tag_id', 'taggable_id'], 'i_taggable_fwd');
41 | $table->index(['taggable_id', 'tag_id'], 'i_taggable_rev');
42 | $table->index('taggable_type', 'i_taggable_type');
43 | });
44 | }
45 | }
46 |
47 | /**
48 | * Reverse the migrations.
49 | */
50 | public function down(): void
51 | {
52 | $connection = config('taggable.connection');
53 | $taggableTagsTable = config('taggable.tables.taggable_tags', 'taggable_tags');
54 | $taggableTaggablesTable = config('taggable.tables.taggable_taggables', 'taggable_taggables');
55 |
56 | if (Schema::connection($connection)->hasTable($taggableTagsTable)) {
57 | Schema::connection($connection)->drop($taggableTagsTable);
58 | }
59 |
60 | if (Schema::connection($connection)->hasTable($taggableTaggablesTable)) {
61 | Schema::connection($connection)->drop($taggableTaggablesTable);
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/config/taggable.php:
--------------------------------------------------------------------------------
1 | ',;',
10 |
11 | /**
12 | * Character used to delimit tag lists returned in the
13 | * tagList, tagListNormalized, etc. attributes.
14 | */
15 | 'glue' => ',',
16 |
17 | /**
18 | * Method used to "normalize" tag names. Can either be a global function name,
19 | * a closure function, or a callable, e.g. ['Classname', 'method'].
20 | */
21 | //'normalizer' => 'mb_strtolower',
22 | 'normalizer' => ['Illuminate\Support\Str', 'slug'],
23 |
24 |
25 |
26 | /**
27 | * The database connection to use for the Tag model and associated tables.
28 | * By default, we use the default database connection, but this can be defined
29 | * so that all the tag-related tables are stored in a different connection.
30 | */
31 | 'connection' => null,
32 |
33 | /**
34 | * How to handle passing empty values to the scope queries. When set to false,
35 | * the scope queries will return no models. When set to true, passing an empty
36 | * value to the scope queries will throw an exception instead.
37 | */
38 | 'throwEmptyExceptions' => false,
39 |
40 | /**
41 | * If you want to be able to find all the models that share a tag, you will need
42 | * to define the inverse relations here. The array keys are the relation names
43 | * you would use to access them (e.g. "posts") and the values are the qualified
44 | * class names of the models that are taggable (e.g. "\App\Post). e.g. with
45 | * the following configuration:
46 | *
47 | * 'taggedModels' => [
48 | * 'posts' => \App\Post::class
49 | * ]
50 | *
51 | * You will be able to do:
52 | *
53 | * $posts = Tag::findByName('Apple')->posts;
54 | *
55 | * to get a collection of all the Posts that are tagged "Apple".
56 | */
57 |
58 | 'taggedModels' => [],
59 |
60 | /**
61 | * The model used to store the tags in the database. You can
62 | * create your own class that extends the package's Tag model,
63 | * then update the configuration below.
64 | */
65 | 'model' => \Cviebrock\EloquentTaggable\Models\Tag::class,
66 |
67 |
68 | /**
69 | * The tables used to store the tags in the database. You can
70 | * publish the package's migrations and use custom names.
71 | */
72 | 'tables' => [
73 | 'taggable_tags' => 'taggable_tags',
74 | 'taggable_taggables' => 'taggable_taggables',
75 | ]
76 | ];
77 |
--------------------------------------------------------------------------------
/app/Jobs/UploadImage.php:
--------------------------------------------------------------------------------
1 | design = $design;
28 | }
29 |
30 | /**
31 | * Execute the job.
32 | *
33 | * @return void
34 | */
35 | public function handle()
36 | {
37 | $disk = $this->design->disk;
38 | $filename = $this->design->image;
39 | $original_file = storage_path() . '/uploads/original/'. $filename;
40 |
41 | try{
42 | // create the Large Image and save to tmp disk
43 | Image::make($original_file)
44 | ->fit(800, 600, function($constraint){
45 | $constraint->aspectRatio();
46 | })
47 | ->save($large = storage_path('uploads/large/'. $filename));
48 |
49 | // Create the thumbnail image
50 | Image::make($original_file)
51 | ->fit(250, 200, function($constraint){
52 | $constraint->aspectRatio();
53 | })
54 | ->save($thumbnail = storage_path('uploads/thumbnail/'. $filename));
55 |
56 | // store images to permanent disk
57 | // original image
58 | if(Storage::disk($disk)
59 | ->put('uploads/designs/original/'.$filename, fopen($original_file, 'r+'))){
60 | File::delete($original_file);
61 | }
62 |
63 | // large images
64 | if(Storage::disk($disk)
65 | ->put('uploads/designs/large/'.$filename, fopen($large, 'r+'))){
66 | File::delete($large);
67 | }
68 |
69 | // thumbnail images
70 | if(Storage::disk($disk)
71 | ->put('uploads/designs/thumbnail/'.$filename, fopen($thumbnail, 'r+'))){
72 | File::delete($thumbnail);
73 | }
74 |
75 | // Update the database record with success flag
76 | $this->design->update([
77 | 'upload_successful' => true
78 | ]);
79 |
80 | } catch(\Exception $e){
81 | \Log::error($e->getMessage());
82 | }
83 |
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Chats/ChatController.php:
--------------------------------------------------------------------------------
1 | chats = $chats;
21 | $this->messages = $messages;
22 | }
23 |
24 | // Send message to user
25 | public function sendMessage(Request $request)
26 | {
27 | // validate the request
28 | $this->validate($request, [
29 | 'recipient' => ['required'],
30 | 'body' => ['required']
31 | ]);
32 |
33 | $recipient = $request->recipient;
34 | $user = auth()->user();
35 | $body = $request->body;
36 |
37 | // check if there is an existing chat
38 | // between the auth user and the recipient
39 | $chat = $user->getChatWithUser($recipient);
40 |
41 | if(! $chat){
42 | $chat = $this->chats->create([]);
43 | $this->chats->createParticipants($chat->id, [$user->id, $recipient]);
44 | }
45 |
46 | // add the message to the chat
47 | $message = $this->messages->create([
48 | 'user_id' => $user->id,
49 | 'chat_id' => $chat->id,
50 | 'body' => $body,
51 | 'last_read' => null
52 | ]);
53 |
54 | return new MessageResource($message);
55 |
56 | }
57 |
58 | // Get chats for user
59 | public function getUserChats()
60 | {
61 | $chats = $this->chats->getUserChats();
62 | return ChatResource::collection($chats);
63 | }
64 |
65 | // get messages for chat
66 | public function getChatMessages($id)
67 | {
68 | $messages = $this->messages->withCriteria([
69 | new WithTrashed()
70 | ])->findWhere('chat_id', $id);
71 |
72 | return MessageResource::collection($messages);
73 | }
74 |
75 | // mark chat as read
76 | public function markAsRead($id)
77 | {
78 | $chat = $this->chats->find($id);
79 | $chat->markAsReadForUser(auth()->id());
80 | return response()->json(['message' => 'successful'], 200);
81 | }
82 |
83 | // destroy message
84 | public function destroyMessage($id)
85 | {
86 | $message = $this->messages->find($id);
87 | $this->authorize('delete', $message);
88 | $message->delete();
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/app/Policies/InvitationPolicy.php:
--------------------------------------------------------------------------------
1 | id == $invitation->sender_id;
69 | }
70 |
71 | public function resend(User $user, Invitation $invitation)
72 | {
73 | return $user->id == $invitation->sender_id;
74 | }
75 |
76 |
77 | public function respond(User $user, Invitation $invitation)
78 | {
79 | return $user->email == $invitation->recipient_email;
80 | }
81 |
82 |
83 |
84 | /**
85 | * Determine whether the user can restore the invitation.
86 | *
87 | * @param \App\Models\User $user
88 | * @param \App\Models\Invitation $invitation
89 | * @return mixed
90 | */
91 | public function restore(User $user, Invitation $invitation)
92 | {
93 | //
94 | }
95 |
96 | /**
97 | * Determine whether the user can permanently delete the invitation.
98 | *
99 | * @param \App\Models\User $user
100 | * @param \App\Models\Invitation $invitation
101 | * @return mixed
102 | */
103 | public function forceDelete(User $user, Invitation $invitation)
104 | {
105 | //
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/config/queue.php:
--------------------------------------------------------------------------------
1 | env('QUEUE_CONNECTION', 'sync'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Queue Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure the connection information for each server that
24 | | is used by your application. A default configuration has been added
25 | | for each back-end shipped with Laravel. You are free to add more.
26 | |
27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'sync' => [
34 | 'driver' => 'sync',
35 | ],
36 |
37 | 'database' => [
38 | 'driver' => 'database',
39 | 'table' => 'jobs',
40 | 'queue' => 'default',
41 | 'retry_after' => 90,
42 | ],
43 |
44 | 'beanstalkd' => [
45 | 'driver' => 'beanstalkd',
46 | 'host' => 'localhost',
47 | 'queue' => 'default',
48 | 'retry_after' => 90,
49 | 'block_for' => 0,
50 | ],
51 |
52 | 'sqs' => [
53 | 'driver' => 'sqs',
54 | 'key' => env('AWS_ACCESS_KEY_ID'),
55 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
56 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
57 | 'queue' => env('SQS_QUEUE', 'your-queue-name'),
58 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
59 | ],
60 |
61 | 'redis' => [
62 | 'driver' => 'redis',
63 | 'connection' => 'default',
64 | 'queue' => env('REDIS_QUEUE', 'default'),
65 | 'retry_after' => 90,
66 | 'block_for' => null,
67 | ],
68 |
69 | ],
70 |
71 | /*
72 | |--------------------------------------------------------------------------
73 | | Failed Queue Jobs
74 | |--------------------------------------------------------------------------
75 | |
76 | | These options configure the behavior of failed queue job logging so you
77 | | can control which database and table are used to store the jobs that
78 | | have failed. You may change them to any database / table you wish.
79 | |
80 | */
81 |
82 | 'failed' => [
83 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database'),
84 | 'database' => env('DB_CONNECTION', 'mysql'),
85 | 'table' => 'failed_jobs',
86 | ],
87 |
88 | ];
89 |
--------------------------------------------------------------------------------
/config/logging.php:
--------------------------------------------------------------------------------
1 | env('LOG_CHANNEL', 'stack'),
21 |
22 | /*
23 | |--------------------------------------------------------------------------
24 | | Log Channels
25 | |--------------------------------------------------------------------------
26 | |
27 | | Here you may configure the log channels for your application. Out of
28 | | the box, Laravel uses the Monolog PHP logging library. This gives
29 | | you a variety of powerful log handlers / formatters to utilize.
30 | |
31 | | Available Drivers: "single", "daily", "slack", "syslog",
32 | | "errorlog", "monolog",
33 | | "custom", "stack"
34 | |
35 | */
36 |
37 | 'channels' => [
38 | 'stack' => [
39 | 'driver' => 'stack',
40 | 'channels' => ['single'],
41 | 'ignore_exceptions' => false,
42 | ],
43 |
44 | 'single' => [
45 | 'driver' => 'single',
46 | 'path' => storage_path('logs/laravel.log'),
47 | 'level' => 'debug',
48 | ],
49 |
50 | 'daily' => [
51 | 'driver' => 'daily',
52 | 'path' => storage_path('logs/laravel.log'),
53 | 'level' => 'debug',
54 | 'days' => 14,
55 | ],
56 |
57 | 'slack' => [
58 | 'driver' => 'slack',
59 | 'url' => env('LOG_SLACK_WEBHOOK_URL'),
60 | 'username' => 'Laravel Log',
61 | 'emoji' => ':boom:',
62 | 'level' => 'critical',
63 | ],
64 |
65 | 'papertrail' => [
66 | 'driver' => 'monolog',
67 | 'level' => 'debug',
68 | 'handler' => SyslogUdpHandler::class,
69 | 'handler_with' => [
70 | 'host' => env('PAPERTRAIL_URL'),
71 | 'port' => env('PAPERTRAIL_PORT'),
72 | ],
73 | ],
74 |
75 | 'stderr' => [
76 | 'driver' => 'monolog',
77 | 'handler' => StreamHandler::class,
78 | 'formatter' => env('LOG_STDERR_FORMATTER'),
79 | 'with' => [
80 | 'stream' => 'php://stderr',
81 | ],
82 | ],
83 |
84 | 'syslog' => [
85 | 'driver' => 'syslog',
86 | 'level' => 'debug',
87 | ],
88 |
89 | 'errorlog' => [
90 | 'driver' => 'errorlog',
91 | 'level' => 'debug',
92 | ],
93 |
94 | 'null' => [
95 | 'driver' => 'monolog',
96 | 'handler' => NullHandler::class,
97 | ],
98 | ],
99 |
100 | ];
101 |
--------------------------------------------------------------------------------
/app/Http/Kernel.php:
--------------------------------------------------------------------------------
1 | [
33 | \App\Http\Middleware\EncryptCookies::class,
34 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
35 | \Illuminate\Session\Middleware\StartSession::class,
36 | // \Illuminate\Session\Middleware\AuthenticateSession::class,
37 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
38 | \App\Http\Middleware\VerifyCsrfToken::class,
39 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
40 | ],
41 |
42 | 'api' => [
43 | 'throttle:60,1',
44 | 'bindings',
45 | ],
46 | ];
47 |
48 | /**
49 | * The application's route middleware.
50 | *
51 | * These middleware may be assigned to groups or used individually.
52 | *
53 | * @var array
54 | */
55 | protected $routeMiddleware = [
56 | 'auth' => \App\Http\Middleware\Authenticate::class,
57 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
58 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::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 | /**
69 | * The priority-sorted list of middleware.
70 | *
71 | * This forces non-global middleware to always be in the given order.
72 | *
73 | * @var array
74 | */
75 | protected $middlewarePriority = [
76 | \Illuminate\Session\Middleware\StartSession::class,
77 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
78 | \App\Http\Middleware\Authenticate::class,
79 | \Illuminate\Routing\Middleware\ThrottleRequests::class,
80 | \Illuminate\Session\Middleware\AuthenticateSession::class,
81 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
82 | \Illuminate\Auth\Middleware\Authorize::class,
83 | ];
84 | }
85 |
--------------------------------------------------------------------------------
/resources/views/welcome.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Laravel
8 |
9 |
10 |
11 |
12 |
13 |
65 |
66 |
67 |
68 | @if (Route::has('login'))
69 |
70 | @auth
71 |
Home
72 | @else
73 |
Login
74 |
75 | @if (Route::has('register'))
76 |
Register
77 | @endif
78 | @endauth
79 |
80 | @endif
81 |
82 |
83 |
84 | Laravel
85 |
86 |
87 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/config/cache.php:
--------------------------------------------------------------------------------
1 | env('CACHE_DRIVER', 'file'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Cache Stores
26 | |--------------------------------------------------------------------------
27 | |
28 | | Here you may define all of the cache "stores" for your application as
29 | | well as their drivers. You may even define multiple stores for the
30 | | same cache driver to group types of items stored in your caches.
31 | |
32 | */
33 |
34 | 'stores' => [
35 |
36 | 'apc' => [
37 | 'driver' => 'apc',
38 | ],
39 |
40 | 'array' => [
41 | 'driver' => 'array',
42 | ],
43 |
44 | 'database' => [
45 | 'driver' => 'database',
46 | 'table' => 'cache',
47 | 'connection' => null,
48 | ],
49 |
50 | 'file' => [
51 | 'driver' => 'file',
52 | 'path' => storage_path('framework/cache/data'),
53 | ],
54 |
55 | 'memcached' => [
56 | 'driver' => 'memcached',
57 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
58 | 'sasl' => [
59 | env('MEMCACHED_USERNAME'),
60 | env('MEMCACHED_PASSWORD'),
61 | ],
62 | 'options' => [
63 | // Memcached::OPT_CONNECT_TIMEOUT => 2000,
64 | ],
65 | 'servers' => [
66 | [
67 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
68 | 'port' => env('MEMCACHED_PORT', 11211),
69 | 'weight' => 100,
70 | ],
71 | ],
72 | ],
73 |
74 | 'redis' => [
75 | 'driver' => 'redis',
76 | 'connection' => 'cache',
77 | ],
78 |
79 | 'dynamodb' => [
80 | 'driver' => 'dynamodb',
81 | 'key' => env('AWS_ACCESS_KEY_ID'),
82 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
83 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
84 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
85 | 'endpoint' => env('DYNAMODB_ENDPOINT'),
86 | ],
87 |
88 | ],
89 |
90 | /*
91 | |--------------------------------------------------------------------------
92 | | Cache Key Prefix
93 | |--------------------------------------------------------------------------
94 | |
95 | | When utilizing a RAM based store such as APC or Memcached, there might
96 | | be other applications utilizing the same cache. So, we'll specify a
97 | | value to get prefixed to all our keys so we can avoid collisions.
98 | |
99 | */
100 |
101 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
102 |
103 | ];
104 |
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | ['auth:api']], function(){
27 | Route::post('logout', 'Auth\LoginController@logout');
28 | Route::put('settings/profile', 'User\SettingsController@updateProfile');
29 | Route::put('settings/password', 'User\SettingsController@updatePassword');
30 |
31 |
32 | // Upload Designs
33 | Route::post('designs', 'Designs\UploadController@upload');
34 | Route::put('designs/{id}', 'Designs\DesignController@update');
35 | Route::get('designs/{id}/byUser', 'Designs\DesignController@userOwnsDesign');
36 |
37 | Route::delete('designs/{id}', 'Designs\DesignController@destroy');
38 |
39 | // Likes and Unlikes
40 | Route::post('designs/{id}/like', 'Designs\DesignController@like');
41 | Route::get('designs/{id}/liked', 'Designs\DesignController@checkIfUserHasLiked');
42 |
43 | // Comments
44 | Route::post('designs/{id}/comments', 'Designs\CommentController@store');
45 | Route::put('comments/{id}', 'Designs\CommentController@update');
46 | Route::delete('comments/{id}', 'Designs\CommentController@destroy');
47 |
48 | // Teams
49 | Route::post('teams', 'Teams\TeamsController@store');
50 | Route::get('teams/{id}', 'Teams\TeamsController@findById');
51 | Route::get('teams', 'Teams\TeamsController@index');
52 | Route::get('users/teams', 'Teams\TeamsController@fetchUserTeams');
53 | Route::put('teams/{id}', 'Teams\TeamsController@update');
54 | Route::delete('teams/{id}', 'Teams\TeamsController@destroy');
55 | Route::delete('teams/{team_id}/users/{user_id}', 'Teams\TeamsController@removeFromTeam');
56 |
57 | // Invitations
58 | Route::post('invitations/{teamId}', 'Teams\InvitationsController@invite');
59 | Route::post('invitations/{id}/resend', 'Teams\InvitationsController@resend');
60 | Route::post('invitations/{id}/respond', 'Teams\InvitationsController@respond');
61 | Route::delete('invitations/{id}', 'Teams\InvitationsController@destroy');
62 |
63 | // Chats
64 | Route::post('chats', 'Chats\ChatController@sendMessage');
65 | Route::get('chats', 'Chats\ChatController@getUserChats');
66 | Route::get('chats/{id}/messages', 'Chats\ChatController@getChatMessages');
67 | Route::put('chats/{id}/markAsRead', 'Chats\ChatController@markAsRead');
68 | Route::delete('messages/{id}', 'Chats\ChatController@destroyMessage');
69 |
70 | });
71 |
72 | // Routes for guests only
73 | Route::group(['middleware' => ['guest:api']], function(){
74 | Route::post('register', 'Auth\RegisterController@register');
75 | Route::post('verification/verify/{user}', 'Auth\VerificationController@verify')->name('verification.verify');
76 | Route::post('verification/resend', 'Auth\VerificationController@resend');
77 | Route::post('login', 'Auth\LoginController@login');
78 | Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
79 | Route::post('password/reset', 'Auth\ResetPasswordController@reset');
80 |
81 |
82 |
83 |
84 | });
85 |
86 |
--------------------------------------------------------------------------------
/app/Models/User.php:
--------------------------------------------------------------------------------
1 | email)).'.jpg?s=200&d=mm';
54 | }
55 |
56 |
57 | /**
58 | * The attributes that should be cast to native types.
59 | *
60 | * @var array
61 | */
62 | protected $casts = [
63 | 'email_verified_at' => 'datetime',
64 | 'available_to_hire' => 'boolean'
65 | ];
66 |
67 | public function sendEmailVerificationNotification()
68 | {
69 | $this->notify(new VerifyEmail);
70 | }
71 |
72 | public function sendPasswordResetNotification($token)
73 | {
74 | $this->notify(new ResetPassword($token));
75 | }
76 |
77 |
78 | public function designs()
79 | {
80 | return $this->hasMany(Design::class);
81 | }
82 |
83 | public function comments()
84 | {
85 | return $this->hasMany(Comment::class);
86 | }
87 |
88 | // teams that the user belongs to
89 | public function teams()
90 | {
91 | return $this->belongsToMany(Team::class)
92 | ->withTimestamps();
93 | }
94 |
95 | public function ownedTeams()
96 | {
97 | return $this->teams()
98 | ->where('owner_id', $this->id);
99 | }
100 |
101 | public function isOwnerOfTeam($team)
102 | {
103 | return (bool)$this->teams()
104 | ->where('id', $team->id)
105 | ->where('owner_id', $this->id)
106 | ->count();
107 | }
108 |
109 |
110 | // Relationships for invitations
111 | public function invitations()
112 | {
113 | return $this->hasMany(Invitation::class, 'recipient_email', 'email');
114 | }
115 |
116 | // relationships for chat messaging
117 | public function chats()
118 | {
119 | return $this->belongsToMany(Chat::class, 'participants');
120 | }
121 |
122 | public function messages()
123 | {
124 | return $this->hasMany(Message::class);
125 | }
126 |
127 | public function getChatWithUser($user_id)
128 | {
129 | $chat = $this->chats()
130 | ->whereHas('participants', function($query) use ($user_id){
131 | $query->where('user_id', $user_id);
132 | })
133 | ->first();
134 | return $chat;
135 | }
136 |
137 |
138 |
139 |
140 |
141 | /**
142 | * Get the identifier that will be stored in the subject claim of the JWT.
143 | *
144 | * @return mixed
145 | */
146 | public function getJWTIdentifier()
147 | {
148 | return $this->getKey();
149 | }
150 |
151 | /**
152 | * Return a key value array, containing any custom claims to be added to the JWT.
153 | *
154 | * @return array
155 | */
156 | public function getJWTCustomClaims()
157 | {
158 | return [];
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/config/auth.php:
--------------------------------------------------------------------------------
1 | [
17 | 'guard' => 'api',
18 | 'passwords' => 'users',
19 | ],
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Authentication Guards
24 | |--------------------------------------------------------------------------
25 | |
26 | | Next, you may define every authentication guard for your application.
27 | | Of course, a great default configuration has been defined for you
28 | | here which uses session storage and the Eloquent user provider.
29 | |
30 | | All authentication drivers have a user provider. This defines how the
31 | | users are actually retrieved out of your database or other storage
32 | | mechanisms used by this application to persist your user's data.
33 | |
34 | | Supported: "session", "token"
35 | |
36 | */
37 |
38 | 'guards' => [
39 | 'web' => [
40 | 'driver' => 'session',
41 | 'provider' => 'users',
42 | ],
43 |
44 | 'api' => [
45 | 'driver' => 'jwt',
46 | 'provider' => 'users',
47 | 'hash' => false,
48 | ],
49 | ],
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | User Providers
54 | |--------------------------------------------------------------------------
55 | |
56 | | All authentication drivers have a user provider. This defines how the
57 | | users are actually retrieved out of your database or other storage
58 | | mechanisms used by this application to persist your user's data.
59 | |
60 | | If you have multiple user tables or models you may configure multiple
61 | | sources which represent each model / table. These sources may then
62 | | be assigned to any extra authentication guards you have defined.
63 | |
64 | | Supported: "database", "eloquent"
65 | |
66 | */
67 |
68 | 'providers' => [
69 | 'users' => [
70 | 'driver' => 'eloquent',
71 | 'model' => App\Models\User::class,
72 | ],
73 |
74 | // 'users' => [
75 | // 'driver' => 'database',
76 | // 'table' => 'users',
77 | // ],
78 | ],
79 |
80 | /*
81 | |--------------------------------------------------------------------------
82 | | Resetting Passwords
83 | |--------------------------------------------------------------------------
84 | |
85 | | You may specify multiple password reset configurations if you have more
86 | | than one user table or model in the application and you want to have
87 | | separate password reset settings based on the specific user types.
88 | |
89 | | The expire time is the number of minutes that the reset token should be
90 | | considered valid. This security feature keeps tokens short-lived so
91 | | they have less time to be guessed. You may change this as needed.
92 | |
93 | */
94 |
95 | 'passwords' => [
96 | 'users' => [
97 | 'provider' => 'users',
98 | 'table' => 'password_resets',
99 | 'expire' => 60,
100 | 'throttle' => 60,
101 | ],
102 | ],
103 |
104 | /*
105 | |--------------------------------------------------------------------------
106 | | Password Confirmation Timeout
107 | |--------------------------------------------------------------------------
108 | |
109 | | Here you may define the amount of seconds before a password confirmation
110 | | times out and the user is prompted to re-enter their password via the
111 | | confirmation screen. By default, the timeout lasts for three hours.
112 | |
113 | */
114 |
115 | 'password_timeout' => 10800,
116 |
117 | ];
118 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Teams/TeamsController.php:
--------------------------------------------------------------------------------
1 | teams = $teams;
24 | $this->users = $users;
25 | $this->invitations = $invitations;
26 | }
27 |
28 | /**
29 | * Get list of all teams (eg for Search)
30 | */
31 | public function index(Request $request)
32 | {
33 |
34 | }
35 |
36 | /**
37 | * Save team to database
38 | */
39 | public function store(Request $request)
40 | {
41 | $this->validate($request, [
42 | 'name' => ['required', 'string', 'max:80', 'unique:teams,name']
43 | ]);
44 |
45 | // create team in database
46 | $team = $this->teams->create([
47 | 'owner_id' => auth()->id(),
48 | 'name' => $request->name,
49 | 'slug' => Str::slug($request->name)
50 | ]);
51 |
52 | // current user is inserted as
53 | // team member using boot method in Team model
54 |
55 | return new TeamResource($team);
56 |
57 |
58 | }
59 |
60 | /**
61 | * Update team information
62 | */
63 | public function update(Request $request, $id)
64 | {
65 | $team = $this->teams->find($id);
66 | $this->authorize('update', $team);
67 |
68 | $this->validate($request, [
69 | 'name' => ['required', 'string', 'max:80', 'unique:teams,name,'.$id]
70 | ]);
71 |
72 | $team = $this->teams->update($id, [
73 | 'name' => $request->name,
74 | 'slug' => Str::slug($request->name)
75 | ]);
76 |
77 | return new TeamResource($team);
78 | }
79 |
80 | /**
81 | * Find a team by its ID
82 | */
83 | public function findById($id)
84 | {
85 | $team = $this->teams->find($id);
86 | return new TeamResource($team);
87 | }
88 |
89 | /**
90 | * Get the teams that the current user belongs to
91 | */
92 | public function fetchUserTeams()
93 | {
94 | $teams = $this->teams->fetchUserTeams();
95 | return TeamResource::collection($teams);
96 | }
97 |
98 | /**
99 | * Get team by slug for Public view
100 | */
101 | public function findBySlug($slug)
102 | {
103 | $team = $this->teams->findWhereFirst('slug', $slug);
104 | return new TeamResource($team);
105 | }
106 |
107 | /**
108 | * Destroy (delete) a team
109 | */
110 | public function destroy($id)
111 | {
112 | $team = $this->teams->find($id);
113 | $this->authorize('delete', $team);
114 |
115 | $team->delete();
116 |
117 | return response()->json(['message' => 'Deleted'], 200);
118 | }
119 |
120 | public function removeFromTeam($teamId, $userId)
121 | {
122 | // get the team
123 | $team = $this->teams->find($teamId);
124 | $user = $this->users->find($userId);
125 |
126 | // check that the user is not the owner
127 | if($user->isOwnerOfTeam($team)){
128 | return response()->json([
129 | 'message' => 'You are the team owner'
130 | ], 401);
131 | }
132 |
133 | // check that the person sending the request
134 | // is either the owner of the team or the person
135 | // who wants to leave the team
136 | if(!auth()->user()->isOwnerOfTeam($team) &&
137 | auth()->id() !== $user->id
138 | ){
139 | return response()->json([
140 | 'message' => 'You cannot do this'
141 | ], 401);
142 | }
143 |
144 | $this->invitations->removeUserFromTeam($team, $userId);
145 |
146 | return response()->json(['message' => 'Success'], 200);
147 |
148 |
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | ## About Laravel
11 |
12 | Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
13 |
14 | - [Simple, fast routing engine](https://laravel.com/docs/routing).
15 | - [Powerful dependency injection container](https://laravel.com/docs/container).
16 | - Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
17 | - Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
18 | - Database agnostic [schema migrations](https://laravel.com/docs/migrations).
19 | - [Robust background job processing](https://laravel.com/docs/queues).
20 | - [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
21 |
22 | Laravel is accessible, powerful, and provides tools required for large, robust applications.
23 |
24 | ## Learning Laravel
25 |
26 | Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
27 |
28 | If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains over 1500 video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
29 |
30 | ## Laravel Sponsors
31 |
32 | We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the Laravel [Patreon page](https://patreon.com/taylorotwell).
33 |
34 | - **[Vehikl](https://vehikl.com/)**
35 | - **[Tighten Co.](https://tighten.co)**
36 | - **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
37 | - **[64 Robots](https://64robots.com)**
38 | - **[Cubet Techno Labs](https://cubettech.com)**
39 | - **[Cyber-Duck](https://cyber-duck.co.uk)**
40 | - **[British Software Development](https://www.britishsoftware.co)**
41 | - **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)**
42 | - **[DevSquad](https://devsquad.com)**
43 | - [UserInsights](https://userinsights.com)
44 | - [Fragrantica](https://www.fragrantica.com)
45 | - [SOFTonSOFA](https://softonsofa.com/)
46 | - [User10](https://user10.com)
47 | - [Soumettre.fr](https://soumettre.fr/)
48 | - [CodeBrisk](https://codebrisk.com)
49 | - [1Forge](https://1forge.com)
50 | - [TECPRESSO](https://tecpresso.co.jp/)
51 | - [Runtime Converter](http://runtimeconverter.com/)
52 | - [WebL'Agence](https://weblagence.com/)
53 | - [Invoice Ninja](https://www.invoiceninja.com)
54 | - [iMi digital](https://www.imi-digital.de/)
55 | - [Earthlink](https://www.earthlink.ro/)
56 | - [Steadfast Collective](https://steadfastcollective.com/)
57 | - [We Are The Robots Inc.](https://watr.mx/)
58 | - [Understand.io](https://www.understand.io/)
59 | - [Abdel Elrafa](https://abdelelrafa.com)
60 | - [Hyper Host](https://hyper.host)
61 | - [Appoly](https://www.appoly.co.uk)
62 | - [OP.GG](https://op.gg)
63 |
64 | ## Contributing
65 |
66 | Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
67 |
68 | ## Code of Conduct
69 |
70 | In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
71 |
72 | ## Security Vulnerabilities
73 |
74 | If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
75 |
76 | ## License
77 |
78 | The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
79 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Designs/DesignController.php:
--------------------------------------------------------------------------------
1 | designs = $designs;
26 | }
27 |
28 | public function index()
29 | {
30 | $designs = $this->designs->withCriteria([
31 | new LatestFirst(),
32 | new IsLive(),
33 | new ForUser(2),
34 | new EagerLoad(['user', 'comments'])
35 | ])->all();
36 | return DesignResource::collection($designs);
37 | }
38 |
39 | public function findDesign($id)
40 | {
41 | $design = $this->designs->find($id);
42 | return new DesignResource($design);
43 | }
44 |
45 | public function update(Request $request, $id)
46 | {
47 |
48 | $design = $this->designs->find($id);
49 |
50 | $this->authorize('update', $design);
51 | $this->validate($request, [
52 | 'title' => ['required', 'unique:designs,title,'. $id],
53 | 'description' => ['required', 'string', 'min:20', 'max:140'],
54 | 'tags' => ['required'],
55 | 'team' => ['required_if:assign_to_team,true']
56 | ]);
57 |
58 |
59 | $design = $this->designs->update($id, [
60 | 'team_id' => $request->team,
61 | 'title' => $request->title,
62 | 'description' => $request->description,
63 | 'slug' => Str::slug($request->title),
64 | 'is_live' => ! $design->upload_successful ? false : $request->is_live
65 | ]);
66 |
67 | // apply the tags
68 | $this->designs->applyTags($id, $request->tags);
69 |
70 | return new DesignResource($design);
71 | }
72 |
73 | public function destroy($id)
74 | {
75 | $design = $this->designs->find($id);
76 | $this->authorize('delete', $design);
77 | // delete the files associated to the record
78 | foreach(['thumbnail', 'large', 'original'] as $size){
79 | // check if the file exists in the database
80 | if(Storage::disk($design->disk)->exists("uploads/designs/{$size}/".$design->image)){
81 | Storage::disk($design->disk)->delete("uploads/designs/{$size}/".$design->image);
82 | }
83 | }
84 | $this->designs->delete($id);
85 | return response()->json(['message' => 'Record deleted'], 200);
86 |
87 | }
88 |
89 | public function like($id)
90 | {
91 | $total = $this->designs->like($id);
92 | return response()->json([
93 | 'message' => 'Successful',
94 | 'total' => $total
95 | ], 200);
96 | }
97 |
98 | public function checkIfUserHasLiked($designId)
99 | {
100 | $isLiked = $this->designs->isLikedByUser($designId);
101 | return response()->json(['liked' => $isLiked], 200);
102 | }
103 |
104 | public function search(Request $request)
105 | {
106 | $designs = $this->designs->search($request);
107 | return DesignResource::collection($designs);
108 | }
109 |
110 | public function findBySlug($slug)
111 | {
112 | $design = $this->designs->withCriteria([
113 | new IsLive(),
114 | new EagerLoad(['user', 'comments'])
115 | ])->findWhereFirst('slug', $slug);
116 | return new DesignResource($design);
117 | }
118 |
119 | public function getForTeam($teamId)
120 | {
121 | $designs = $this->designs
122 | ->withCriteria([new IsLive()])
123 | ->findWhere('team_id', $teamId);
124 | return DesignResource::collection($designs);
125 | }
126 |
127 | public function getForUser($userId)
128 | {
129 | $designs = $this->designs
130 | //->withCriteria([new IsLive()])
131 | ->findWhere('user_id', $userId);
132 | return DesignResource::collection($designs);
133 | }
134 |
135 | public function userOwnsDesign($id)
136 | {
137 | $design = $this->designs->withCriteria(
138 | [ new ForUser(auth()->id())]
139 | )->findWhereFirst('id', $id);
140 |
141 | return new DesignResource($design);
142 | }
143 |
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Teams/InvitationsController.php:
--------------------------------------------------------------------------------
1 | invitations = $invitations;
27 | $this->teams = $teams;
28 | $this->users = $users;
29 | }
30 |
31 | public function invite(Request $request, $teamId)
32 | {
33 | // get the team
34 | $team = $this->teams->find($teamId);
35 |
36 | $this->validate($request, [
37 | 'email' => ['required', 'email']
38 | ]);
39 | $user = auth()->user();
40 | // check if the user owns the team
41 | if(! $user->isOwnerOfTeam($team)){
42 | return response()->json([
43 | 'email' => 'You are not the team owner'
44 | ], 401);
45 | }
46 |
47 | // check if the email has a pending invitation
48 | if($team->hasPendingInvite($request->email)){
49 | return response()->json([
50 | 'email' => 'Email already has a pending invite'
51 | ], 422);
52 | }
53 |
54 | // get the recipient by email
55 | $recipient = $this->users->findByEmail($request->email);
56 |
57 | // if the recipient does not exist, send invitation to join the team
58 | if(! $recipient){
59 | $this->createInvitation(false, $team, $request->email);
60 |
61 | return response()->json([
62 | 'message' => 'Invitation sent to user'
63 | ], 200);
64 | }
65 |
66 | // check if the team already has the user
67 | if($team->hasUser($recipient)){
68 | return response()->json([
69 | 'email' => 'This user seems to be a team member already'
70 | ], 422);
71 | }
72 |
73 | // send the invitation to the user
74 | $this->createInvitation(true, $team, $request->email);
75 | return response()->json([
76 | 'message' => 'Invitation sent to user'
77 | ], 200);
78 | }
79 |
80 | public function resend($id)
81 | {
82 | $invitation = $this->invitations->find($id);
83 |
84 | $this->authorize('resend', $invitation);
85 |
86 | $recipient = $this->users
87 | ->findByEmail($invitation->recipient_email);
88 |
89 | Mail::to($invitation->recipient_email)
90 | ->send(new SendInvitationToJoinTeam($invitation, !is_null($recipient)));
91 |
92 | return response()->json(['message' => 'Invitation resent'], 200);
93 | }
94 |
95 | public function respond(Request $request, $id)
96 | {
97 | $this->validate($request, [
98 | 'token' => ['required'],
99 | 'decision' => ['required']
100 | ]);
101 |
102 | $token = $request->token;
103 | $decision = $request->decision; // 'accept' or 'deny'
104 | $invitation = $this->invitations->find($id);
105 |
106 | // check if the invitation belongs to this user
107 | $this->authorize('respond', $invitation);
108 |
109 |
110 | // check to make sure that the tokens match
111 | if($invitation->token !== $token){
112 | return response()->json([
113 | 'message' => 'Invalid Token'
114 | ], 401);
115 | }
116 |
117 | // check if accepted
118 | if($decision !== 'deny'){
119 | $this->invitations->addUserToTeam($invitation->team, auth()->id());
120 | }
121 |
122 | $invitation->delete();
123 |
124 | return response()->json(['message' => 'Successful'], 200);
125 |
126 | }
127 |
128 | public function destroy($id)
129 | {
130 | $invitation = $this->invitations->find($id);
131 | $this->authorize('delete', $invitation);
132 |
133 | $invitation->delete();
134 |
135 | return response()->json(['message' => 'Deleted'], 200);
136 | }
137 |
138 | protected function createInvitation(bool $user_exists, Team $team, string $email)
139 | {
140 |
141 | $invitation = $this->invitations->create([
142 | 'team_id' => $team->id,
143 | 'sender_id' => auth()->id(),
144 | 'recipient_email' => $email,
145 | 'token' => md5(uniqid(microtime()))
146 | ]);
147 |
148 | Mail::to($email)
149 | ->send(new SendInvitationToJoinTeam($invitation, $user_exists));
150 |
151 | }
152 |
153 |
154 |
155 |
156 | }
157 |
--------------------------------------------------------------------------------
/config/mail.php:
--------------------------------------------------------------------------------
1 | env('MAIL_DRIVER', 'smtp'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | SMTP Host Address
24 | |--------------------------------------------------------------------------
25 | |
26 | | Here you may provide the host address of the SMTP server used by your
27 | | applications. A default option is provided that is compatible with
28 | | the Mailgun mail service which will provide reliable deliveries.
29 | |
30 | */
31 |
32 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | SMTP Host Port
37 | |--------------------------------------------------------------------------
38 | |
39 | | This is the SMTP port used by your application to deliver e-mails to
40 | | users of the application. Like the host we have set this value to
41 | | stay compatible with the Mailgun e-mail application by default.
42 | |
43 | */
44 |
45 | 'port' => env('MAIL_PORT', 587),
46 |
47 | /*
48 | |--------------------------------------------------------------------------
49 | | Global "From" Address
50 | |--------------------------------------------------------------------------
51 | |
52 | | You may wish for all e-mails sent by your application to be sent from
53 | | the same address. Here, you may specify a name and address that is
54 | | used globally for all e-mails that are sent by your application.
55 | |
56 | */
57 |
58 | 'from' => [
59 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
60 | 'name' => env('MAIL_FROM_NAME', 'Example'),
61 | ],
62 |
63 | /*
64 | |--------------------------------------------------------------------------
65 | | E-Mail Encryption Protocol
66 | |--------------------------------------------------------------------------
67 | |
68 | | Here you may specify the encryption protocol that should be used when
69 | | the application send e-mail messages. A sensible default using the
70 | | transport layer security protocol should provide great security.
71 | |
72 | */
73 |
74 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
75 |
76 | /*
77 | |--------------------------------------------------------------------------
78 | | SMTP Server Username
79 | |--------------------------------------------------------------------------
80 | |
81 | | If your SMTP server requires a username for authentication, you should
82 | | set it here. This will get used to authenticate with your server on
83 | | connection. You may also set the "password" value below this one.
84 | |
85 | */
86 |
87 | 'username' => env('MAIL_USERNAME'),
88 |
89 | 'password' => env('MAIL_PASSWORD'),
90 |
91 | /*
92 | |--------------------------------------------------------------------------
93 | | Sendmail System Path
94 | |--------------------------------------------------------------------------
95 | |
96 | | When using the "sendmail" driver to send e-mails, we will need to know
97 | | the path to where Sendmail lives on this server. A default path has
98 | | been provided here, which will work well on most of your systems.
99 | |
100 | */
101 |
102 | 'sendmail' => '/usr/sbin/sendmail -bs',
103 |
104 | /*
105 | |--------------------------------------------------------------------------
106 | | Markdown Mail Settings
107 | |--------------------------------------------------------------------------
108 | |
109 | | If you are using Markdown based email rendering, you may configure your
110 | | theme and component paths here, allowing you to customize the design
111 | | of the emails. Or, you may simply stick with the Laravel defaults!
112 | |
113 | */
114 |
115 | 'markdown' => [
116 | 'theme' => 'default',
117 |
118 | 'paths' => [
119 | resource_path('views/vendor/mail'),
120 | ],
121 | ],
122 |
123 | /*
124 | |--------------------------------------------------------------------------
125 | | Log Channel
126 | |--------------------------------------------------------------------------
127 | |
128 | | If you are using the "log" driver, you may specify the logging channel
129 | | if you prefer to keep mail messages separate from other log entries
130 | | for simpler reading. Otherwise, the default channel will be used.
131 | |
132 | */
133 |
134 | 'log_channel' => env('MAIL_LOG_CHANNEL'),
135 |
136 | ];
137 |
--------------------------------------------------------------------------------
/config/database.php:
--------------------------------------------------------------------------------
1 | env('DB_CONNECTION', 'mysql'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Database Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here are each of the database connections setup for your application.
26 | | Of course, examples of configuring each database platform that is
27 | | supported by Laravel is shown below to make development simple.
28 | |
29 | |
30 | | All database work in Laravel is done through the PHP PDO facilities
31 | | so make sure you have the driver for your particular database of
32 | | choice installed on your machine before you begin development.
33 | |
34 | */
35 |
36 | 'connections' => [
37 |
38 | 'sqlite' => [
39 | 'driver' => 'sqlite',
40 | 'url' => env('DATABASE_URL'),
41 | 'database' => env('DB_DATABASE', database_path('database.sqlite')),
42 | 'prefix' => '',
43 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
44 | ],
45 |
46 | 'mysql' => [
47 | 'driver' => 'mysql',
48 | 'url' => env('DATABASE_URL'),
49 | 'host' => env('DB_HOST', '127.0.0.1'),
50 | 'port' => env('DB_PORT', '3306'),
51 | 'database' => env('DB_DATABASE', 'forge'),
52 | 'username' => env('DB_USERNAME', 'forge'),
53 | 'password' => env('DB_PASSWORD', ''),
54 | 'unix_socket' => env('DB_SOCKET', ''),
55 | 'charset' => 'utf8mb4',
56 | 'collation' => 'utf8mb4_unicode_ci',
57 | 'prefix' => '',
58 | 'prefix_indexes' => true,
59 | 'strict' => true,
60 | 'engine' => null,
61 | 'options' => extension_loaded('pdo_mysql') ? array_filter([
62 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
63 | ]) : [],
64 | ],
65 |
66 | 'pgsql' => [
67 | 'driver' => 'pgsql',
68 | 'url' => env('DATABASE_URL'),
69 | 'host' => env('DB_HOST', '127.0.0.1'),
70 | 'port' => env('DB_PORT', '5432'),
71 | 'database' => env('DB_DATABASE', 'forge'),
72 | 'username' => env('DB_USERNAME', 'forge'),
73 | 'password' => env('DB_PASSWORD', ''),
74 | 'charset' => 'utf8',
75 | 'prefix' => '',
76 | 'prefix_indexes' => true,
77 | 'schema' => 'public',
78 | 'sslmode' => 'prefer',
79 | ],
80 |
81 | 'sqlsrv' => [
82 | 'driver' => 'sqlsrv',
83 | 'url' => env('DATABASE_URL'),
84 | 'host' => env('DB_HOST', 'localhost'),
85 | 'port' => env('DB_PORT', '1433'),
86 | 'database' => env('DB_DATABASE', 'forge'),
87 | 'username' => env('DB_USERNAME', 'forge'),
88 | 'password' => env('DB_PASSWORD', ''),
89 | 'charset' => 'utf8',
90 | 'prefix' => '',
91 | 'prefix_indexes' => true,
92 | ],
93 |
94 | ],
95 |
96 | /*
97 | |--------------------------------------------------------------------------
98 | | Migration Repository Table
99 | |--------------------------------------------------------------------------
100 | |
101 | | This table keeps track of all the migrations that have already run for
102 | | your application. Using this information, we can determine which of
103 | | the migrations on disk haven't actually been run in the database.
104 | |
105 | */
106 |
107 | 'migrations' => 'migrations',
108 |
109 | /*
110 | |--------------------------------------------------------------------------
111 | | Redis Databases
112 | |--------------------------------------------------------------------------
113 | |
114 | | Redis is an open source, fast, and advanced key-value store that also
115 | | provides a richer body of commands than a typical key-value system
116 | | such as APC or Memcached. Laravel makes it easy to dig right in.
117 | |
118 | */
119 |
120 | 'redis' => [
121 |
122 | 'client' => env('REDIS_CLIENT', 'phpredis'),
123 |
124 | 'options' => [
125 | 'cluster' => env('REDIS_CLUSTER', 'redis'),
126 | 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
127 | ],
128 |
129 | 'default' => [
130 | 'url' => env('REDIS_URL'),
131 | 'host' => env('REDIS_HOST', '127.0.0.1'),
132 | 'password' => env('REDIS_PASSWORD', null),
133 | 'port' => env('REDIS_PORT', 6379),
134 | 'database' => env('REDIS_DB', 0),
135 | ],
136 |
137 | 'cache' => [
138 | 'url' => env('REDIS_URL'),
139 | 'host' => env('REDIS_HOST', '127.0.0.1'),
140 | 'password' => env('REDIS_PASSWORD', null),
141 | 'port' => env('REDIS_PORT', 6379),
142 | 'database' => env('REDIS_CACHE_DB', 1),
143 | ],
144 |
145 | ],
146 |
147 | ];
148 |
--------------------------------------------------------------------------------
/config/session.php:
--------------------------------------------------------------------------------
1 | env('SESSION_DRIVER', 'cookie'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Session Lifetime
26 | |--------------------------------------------------------------------------
27 | |
28 | | Here you may specify the number of minutes that you wish the session
29 | | to be allowed to remain idle before it expires. If you want them
30 | | to immediately expire on the browser closing, set that option.
31 | |
32 | */
33 |
34 | 'lifetime' => env('SESSION_LIFETIME', 120),
35 |
36 | 'expire_on_close' => false,
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Session Encryption
41 | |--------------------------------------------------------------------------
42 | |
43 | | This option allows you to easily specify that all of your session data
44 | | should be encrypted before it is stored. All encryption will be run
45 | | automatically by Laravel and you can use the Session like normal.
46 | |
47 | */
48 |
49 | 'encrypt' => false,
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | Session File Location
54 | |--------------------------------------------------------------------------
55 | |
56 | | When using the native session driver, we need a location where session
57 | | files may be stored. A default has been set for you but a different
58 | | location may be specified. This is only needed for file sessions.
59 | |
60 | */
61 |
62 | 'files' => storage_path('framework/sessions'),
63 |
64 | /*
65 | |--------------------------------------------------------------------------
66 | | Session Database Connection
67 | |--------------------------------------------------------------------------
68 | |
69 | | When using the "database" or "redis" session drivers, you may specify a
70 | | connection that should be used to manage these sessions. This should
71 | | correspond to a connection in your database configuration options.
72 | |
73 | */
74 |
75 | 'connection' => env('SESSION_CONNECTION', null),
76 |
77 | /*
78 | |--------------------------------------------------------------------------
79 | | Session Database Table
80 | |--------------------------------------------------------------------------
81 | |
82 | | When using the "database" session driver, you may specify the table we
83 | | should use to manage the sessions. Of course, a sensible default is
84 | | provided for you; however, you are free to change this as needed.
85 | |
86 | */
87 |
88 | 'table' => 'sessions',
89 |
90 | /*
91 | |--------------------------------------------------------------------------
92 | | Session Cache Store
93 | |--------------------------------------------------------------------------
94 | |
95 | | When using the "apc", "memcached", or "dynamodb" session drivers you may
96 | | list a cache store that should be used for these sessions. This value
97 | | must match with one of the application's configured cache "stores".
98 | |
99 | */
100 |
101 | 'store' => env('SESSION_STORE', null),
102 |
103 | /*
104 | |--------------------------------------------------------------------------
105 | | Session Sweeping Lottery
106 | |--------------------------------------------------------------------------
107 | |
108 | | Some session drivers must manually sweep their storage location to get
109 | | rid of old sessions from storage. Here are the chances that it will
110 | | happen on a given request. By default, the odds are 2 out of 100.
111 | |
112 | */
113 |
114 | 'lottery' => [2, 100],
115 |
116 | /*
117 | |--------------------------------------------------------------------------
118 | | Session Cookie Name
119 | |--------------------------------------------------------------------------
120 | |
121 | | Here you may change the name of the cookie used to identify a session
122 | | instance by ID. The name specified here will get used every time a
123 | | new session cookie is created by the framework for every driver.
124 | |
125 | */
126 |
127 | 'cookie' => env(
128 | 'SESSION_COOKIE',
129 | Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
130 | ),
131 |
132 | /*
133 | |--------------------------------------------------------------------------
134 | | Session Cookie Path
135 | |--------------------------------------------------------------------------
136 | |
137 | | The session cookie path determines the path for which the cookie will
138 | | be regarded as available. Typically, this will be the root path of
139 | | your application but you are free to change this when necessary.
140 | |
141 | */
142 |
143 | 'path' => '/',
144 |
145 | /*
146 | |--------------------------------------------------------------------------
147 | | Session Cookie Domain
148 | |--------------------------------------------------------------------------
149 | |
150 | | Here you may change the domain of the cookie used to identify a session
151 | | in your application. This will determine which domains the cookie is
152 | | available to in your application. A sensible default has been set.
153 | |
154 | */
155 |
156 | 'domain' => env('SESSION_DOMAIN', null),
157 |
158 | /*
159 | |--------------------------------------------------------------------------
160 | | HTTPS Only Cookies
161 | |--------------------------------------------------------------------------
162 | |
163 | | By setting this option to true, session cookies will only be sent back
164 | | to the server if the browser has a HTTPS connection. This will keep
165 | | the cookie from being sent to you if it can not be done securely.
166 | |
167 | */
168 |
169 | 'secure' => env('SESSION_SECURE_COOKIE', false),
170 |
171 | /*
172 | |--------------------------------------------------------------------------
173 | | HTTP Access Only
174 | |--------------------------------------------------------------------------
175 | |
176 | | Setting this value to true will prevent JavaScript from accessing the
177 | | value of the cookie and the cookie will only be accessible through
178 | | the HTTP protocol. You are free to modify this option if needed.
179 | |
180 | */
181 |
182 | 'http_only' => true,
183 |
184 | /*
185 | |--------------------------------------------------------------------------
186 | | Same-Site Cookies
187 | |--------------------------------------------------------------------------
188 | |
189 | | This option determines how your cookies behave when cross-site requests
190 | | take place, and can be used to mitigate CSRF attacks. By default, we
191 | | do not enable this as other CSRF protection services are in place.
192 | |
193 | | Supported: "lax", "strict", "none"
194 | |
195 | */
196 |
197 | 'same_site' => null,
198 |
199 | ];
200 |
--------------------------------------------------------------------------------