From 06d1e0ad3fbff093f7f251a48e52ffef8b1d70b9 Mon Sep 17 00:00:00 2001 From: Miguel Nogueira Date: Sun, 11 Oct 2020 02:54:09 +0100 Subject: [PATCH] RSM-8 Add team files page and ability to download files --- app/Console/Commands/MakeFile.php | 67 +++++++++++ app/Http/Controllers/TeamFileController.php | 111 +++++++++++++++++ app/Team.php | 6 + app/TeamFile.php | 31 +++++ app/User.php | 10 +- config/adminlte.php | 33 ++--- database/factories/TeamFileFactory.php | 38 ++++++ ...0_10_10_185952_create_team_files_table.php | 48 ++++++++ ...10_10_235319_add_details_to_team_files.php | 36 ++++++ database/seeders/DatabaseSeeder.php | 3 + database/seeders/TeamFileSeeder.php | 19 +++ public/img/files.svg | 1 + public/img/textfile.png | Bin 0 -> 5649 bytes .../dashboard/teams/team-files.blade.php | 113 ++++++++++++++++++ routes/web.php | 12 ++ 15 files changed, 511 insertions(+), 17 deletions(-) create mode 100644 app/Console/Commands/MakeFile.php create mode 100644 app/Http/Controllers/TeamFileController.php create mode 100644 app/TeamFile.php create mode 100644 database/factories/TeamFileFactory.php create mode 100644 database/migrations/2020_10_10_185952_create_team_files_table.php create mode 100644 database/migrations/2020_10_10_235319_add_details_to_team_files.php create mode 100644 database/seeders/TeamFileSeeder.php create mode 100644 public/img/files.svg create mode 100644 public/img/textfile.png create mode 100644 resources/views/dashboard/teams/team-files.blade.php diff --git a/app/Console/Commands/MakeFile.php b/app/Console/Commands/MakeFile.php new file mode 100644 index 0000000..47e7a77 --- /dev/null +++ b/app/Console/Commands/MakeFile.php @@ -0,0 +1,67 @@ +faker = Factory::create(); + + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + $count = $this->argument('count'); + $this->info('Creating ' . $this->argument('count') . ' files!'); + + for ($max = 1; $max < $count; $max++) + { + Storage::disk('local')->put('factory_files/testfile_' . rand(0, 5000) . '.txt', $this->faker->paragraphs(40, true)); + + } + + $this->info('Finished creating files! They will be randomly picked by the factory.'); + return 0; + } +} diff --git a/app/Http/Controllers/TeamFileController.php b/app/Http/Controllers/TeamFileController.php new file mode 100644 index 0000000..b8a67e8 --- /dev/null +++ b/app/Http/Controllers/TeamFileController.php @@ -0,0 +1,111 @@ +currentTeam)) + { + $request->session()->flash('error', 'Please choose a team before viewing it\'s files.'); + return redirect()->to(route('teams.index')); + } + + return view('dashboard.teams.team-files') + ->with('files', TeamFile::with('team', 'uploader')->paginate(20)); + } + + /** + * Show the form for creating a new resource. + * + * @return \Illuminate\Http\Response + */ + public function create() + { + // + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function store(Request $request) + { + // + } + + + public function download(Request $request, TeamFile $teamFile) + { + try + { + return Storage::download('uploads/' . $teamFile->name); + } + catch (FileNotFoundException $ex) + { + $request->session()->flash('error', 'Sorry, but the requested file could not be found in storage. Sometimes, files may be physically deleted by admins, but not from the app\'s database.'); + return redirect()->back(); + + } + } + + /** + * Display the specified resource. + * + * @param \App\TeamFile $teamFile + * @return \Illuminate\Http\Response + */ + public function show(TeamFile $teamFile) + { + // + } + + /** + * Show the form for editing the specified resource. + * + * @param \App\TeamFile $teamFile + * @return \Illuminate\Http\Response + */ + public function edit(TeamFile $teamFile) + { + // + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param \App\TeamFile $teamFile + * @return \Illuminate\Http\Response + */ + public function update(Request $request, TeamFile $teamFile) + { + // + } + + /** + * Remove the specified resource from storage. + * + * @param \App\TeamFile $teamFile + * @return \Illuminate\Http\Response + */ + public function destroy(TeamFile $teamFile) + { + // + } +} diff --git a/app/Team.php b/app/Team.php index f7fe0fd..27fe33c 100644 --- a/app/Team.php +++ b/app/Team.php @@ -36,4 +36,10 @@ class Team extends TeamworkTeam { return $this->belongsToMany('App\Vacancy', 'team_has_vacancy'); } + + + public function files() + { + return $this->hasMany('App\TeamFile', 'team_id'); + } } diff --git a/app/TeamFile.php b/app/TeamFile.php new file mode 100644 index 0000000..8e54c14 --- /dev/null +++ b/app/TeamFile.php @@ -0,0 +1,31 @@ +belongsTo('App\User', 'uploaded_by', 'id'); + } + + public function team() + { + return $this->belongsTo('App\Team'); + } +} diff --git a/app/User.php b/app/User.php index b17aa9c..7ab469f 100644 --- a/app/User.php +++ b/app/User.php @@ -60,7 +60,8 @@ class User extends Authenticatable implements MustVerifyEmail 'email_verified_at' => 'datetime', ]; -// + // RELATIONSHIPS + public function applications() { return $this->hasMany('App\Application', 'applicantUserID', 'id'); @@ -86,6 +87,13 @@ class User extends Authenticatable implements MustVerifyEmail return $this->hasMany('App\Comment', 'authorID', 'id'); } + public function files() + { + return $this->hasMany('App\TeamFile', 'uploaded_by'); + } + + // UTILITY LOGIC + public function isBanned() { return ! $this->bans()->get()->isEmpty(); diff --git a/config/adminlte.php b/config/adminlte.php index 0d0e7de..197962b 100644 --- a/config/adminlte.php +++ b/config/adminlte.php @@ -607,22 +607,23 @@ return [ 'location' => 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/css/bootstrap-multiselect.css', ], ], - [ - 'name' => 'DropzoneJS', - 'active' => true, - 'files' => [ - [ - 'type' => 'js', - 'asset' => false, - 'location' => '/js/plugins/dropzone.min.js' - ], - [ - 'type' => 'css', - 'asset' => false, - 'location' => '/css/dropzone.min.css' - ] - ] - ] + ], + [ + 'name' => 'DropzoneJS', + 'active' => true, + 'files' => [ + [ + 'type' => 'js', + 'asset' => false, + 'location' => '/js/plugins/dropzone.min.js' + ], + [ + 'type' => 'css', + 'asset' => false, + 'location' => '/css/dropzone.min.css' + ] + ] + ] ], ]; diff --git a/database/factories/TeamFileFactory.php b/database/factories/TeamFileFactory.php new file mode 100644 index 0000000..7782137 --- /dev/null +++ b/database/factories/TeamFileFactory.php @@ -0,0 +1,38 @@ +getAdapter()->getPathPrefix(); + + return [ + 'uploaded_by' => rand(1, 10), // Also assuming that the user seeder has ran before + 'team_id' => rand(1, 3), // Assuming you create 3 teams beforehand + 'name' => $this->faker->file($prefix . 'factory_files', $prefix . 'uploads', false), + 'caption' => $this->faker->sentence(), + 'description' => $this->faker->paragraphs(3, true), + 'fs_location' => $this->faker->file($prefix . 'factory_files', $prefix . 'uploads'), + 'extension' => 'txt', + 'size' => rand(1, 1000) // random fake size between 0 bytes and 1 mb + ]; + } +} diff --git a/database/migrations/2020_10_10_185952_create_team_files_table.php b/database/migrations/2020_10_10_185952_create_team_files_table.php new file mode 100644 index 0000000..b1b0835 --- /dev/null +++ b/database/migrations/2020_10_10_185952_create_team_files_table.php @@ -0,0 +1,48 @@ +id(); + $table->bigInteger('uploaded_by')->unsigned()->index(); + $table->integer('team_id')->unsigned()->index(); + $table->string('name'); + $table->string('fs_location'); // filesystem location + $table->string('extension'); + $table->timestamps(); + + $table->foreign('uploaded_by') + ->references('id') + ->on('users') + ->cascadeOnDelete() + ->cascadeOnUpdate(); + + $table->foreign('team_id') + ->references('id') + ->on('teams') + ->cascadeOnDelete() + ->cascadeOnUpdate(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('team_files'); + } +} diff --git a/database/migrations/2020_10_10_235319_add_details_to_team_files.php b/database/migrations/2020_10_10_235319_add_details_to_team_files.php new file mode 100644 index 0000000..0ea7fd0 --- /dev/null +++ b/database/migrations/2020_10_10_235319_add_details_to_team_files.php @@ -0,0 +1,36 @@ +integer('size')->nullable()->after('extension'); + $table->string('caption')->nullable()->after('name'); + $table->mediumText('description')->nullable()->after('caption'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('team_files', function (Blueprint $table) { + $table->dropColumn('size'); + $table->dropColumn('caption'); + $table->dropColumn('description'); + }); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index b267f30..51ae93b 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -35,5 +35,8 @@ class DatabaseSeeder extends Seeder $this->call(PermissionSeeder::class); $this->call(UserSeeder::class); $this->call(DefaultOptionsSeeder::class); + $this->call(NewPermissions::class); + $this->call(TeamSeeder::class); + $this->call(TeamFileSeeder::class); } } diff --git a/database/seeders/TeamFileSeeder.php b/database/seeders/TeamFileSeeder.php new file mode 100644 index 0000000..91f88c2 --- /dev/null +++ b/database/seeders/TeamFileSeeder.php @@ -0,0 +1,19 @@ +count(50)->create(); + } +} diff --git a/public/img/files.svg b/public/img/files.svg new file mode 100644 index 0000000..3900853 --- /dev/null +++ b/public/img/files.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/textfile.png b/public/img/textfile.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b5011c1cbfa06ae77ce0f10da3425a1b66b3a9 GIT binary patch literal 5649 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'app.name')}} / Teams / Files +@stop + +@section('js') + + +@stop + +@section('content') + + + +
+ + + + +
+ +
+ +
+ Team files illustration +
+ +
+ +
+ +
+ +
+ +
+

Team Files {{ (Auth::user()->currentTeam) ? Auth::user()->currentTeam->name : '(No team)' }}

+ +
+ +
+ + @if(!$files->isEmpty()) + + + + + + + + + + + + + + + + + + + @foreach($files as $file) + + + + + + + + + + + @endforeach + + + +
#File nameCaptionSizeLast updatedActions
{{$file->id}}{{ Str::of($file->name)->limit(10, '(..).' . $file->extension) }}{{ Str::of($file->caption)->limit(10) }}{{ $file->size }} bytes{{ $file->updated_at }} + + + +
+ + @else + +
+ + There are currently no team files. Try uploading some to get started. + +
+ + @endif + +
+ + + +
+ +
+ +
+ +@stop + +@section('footer') + @include('breadcrumbs.dashboard.footer') +@stop diff --git a/routes/web.php b/routes/web.php index 95588fc..ab4452f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -31,6 +31,7 @@ use App\Http\Controllers\FormController; use App\Http\Controllers\HomeController; use App\Http\Controllers\ProfileController; use App\Http\Controllers\TeamController; +use App\Http\Controllers\TeamFileController; use App\Http\Controllers\UserController; use App\Http\Controllers\VacancyController; use App\Http\Controllers\VoteController; @@ -86,6 +87,17 @@ Route::group(['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['lo Route::get('teams/invites/{action}/{token}', [TeamController::class, 'processInviteAction']) ->name('processInvite'); + + Route::get('team/files', [TeamFileController::class, 'index']) + ->name('showTeamFiles'); + + Route::post('team/files/upload', [TeamFileController::class, 'store']) + ->name('uploadTeamFile'); + + Route::get('team/files/{teamFile}/download', [TeamFileController::class, 'download']) + ->name('downloadTeamFile'); + + Route::group(['prefix' => '/applications'], function () { Route::get('/my-applications', [ApplicationController::class, 'showUserApps']) ->name('showUserApps')