RSM-8 Add team files page and ability to download files

This commit is contained in:
Miguel Nogueira 2020-10-11 02:54:09 +01:00
parent b8a2a64354
commit 06d1e0ad3f
15 changed files with 511 additions and 17 deletions

View File

@ -0,0 +1,67 @@
<?php
namespace App\Console\Commands;
use Faker\Factory;
use Faker\Generator;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class MakeFile extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'files:make {count : How many test files to generate}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generates test files for the TeamFile model. Use in conjunction with it\'s factory.';
/**
* The faker instance used to obtain dummy text.
*
* @var Generator
*/
private $faker;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
$this->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;
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace App\Http\Controllers;
use App\TeamFile;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\FileNotFoundException;
class TeamFileController extends Controller
{
/**
* Display a listing of the resource.
*
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Http\Response
*/
public function index(Request $request)
{
if (is_null(Auth::user()->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)
{
//
}
}

View File

@ -36,4 +36,10 @@ class Team extends TeamworkTeam
{ {
return $this->belongsToMany('App\Vacancy', 'team_has_vacancy'); return $this->belongsToMany('App\Vacancy', 'team_has_vacancy');
} }
public function files()
{
return $this->hasMany('App\TeamFile', 'team_id');
}
} }

31
app/TeamFile.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Mpociot\Teamwork\Traits\UsedByTeams;
class TeamFile extends Model
{
use HasFactory, UsedByTeams;
protected $fillable = [
'uploaded_by',
'team_id',
'name',
'fs_location',
'extension'
];
public function uploader()
{
return $this->belongsTo('App\User', 'uploaded_by', 'id');
}
public function team()
{
return $this->belongsTo('App\Team');
}
}

View File

@ -60,7 +60,8 @@ class User extends Authenticatable implements MustVerifyEmail
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
]; ];
// // RELATIONSHIPS
public function applications() public function applications()
{ {
return $this->hasMany('App\Application', 'applicantUserID', 'id'); return $this->hasMany('App\Application', 'applicantUserID', 'id');
@ -86,6 +87,13 @@ class User extends Authenticatable implements MustVerifyEmail
return $this->hasMany('App\Comment', 'authorID', 'id'); return $this->hasMany('App\Comment', 'authorID', 'id');
} }
public function files()
{
return $this->hasMany('App\TeamFile', 'uploaded_by');
}
// UTILITY LOGIC
public function isBanned() public function isBanned()
{ {
return ! $this->bans()->get()->isEmpty(); return ! $this->bans()->get()->isEmpty();

View File

@ -607,6 +607,8 @@ return [
'location' => 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/css/bootstrap-multiselect.css', 'location' => 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/css/bootstrap-multiselect.css',
], ],
], ],
],
[ [
'name' => 'DropzoneJS', 'name' => 'DropzoneJS',
'active' => true, 'active' => true,
@ -624,5 +626,4 @@ return [
] ]
] ]
], ],
],
]; ];

View File

@ -0,0 +1,38 @@
<?php
namespace Database\Factories;
use App\TeamFile;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Storage;
class TeamFileFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = TeamFile::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
$prefix = Storage::disk('local')->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
];
}
}

View File

@ -0,0 +1,48 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTeamFilesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('team_files', function (Blueprint $table) {
$table->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');
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDetailsToTeamFiles extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('team_files', function (Blueprint $table) {
$table->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');
});
}
}

View File

@ -35,5 +35,8 @@ class DatabaseSeeder extends Seeder
$this->call(PermissionSeeder::class); $this->call(PermissionSeeder::class);
$this->call(UserSeeder::class); $this->call(UserSeeder::class);
$this->call(DefaultOptionsSeeder::class); $this->call(DefaultOptionsSeeder::class);
$this->call(NewPermissions::class);
$this->call(TeamSeeder::class);
$this->call(TeamFileSeeder::class);
} }
} }

View File

@ -0,0 +1,19 @@
<?php
namespace Database\Seeders;
use App\TeamFile;
use Illuminate\Database\Seeder;
class TeamFileSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
TeamFile::factory()->count(50)->create();
}
}

1
public/img/files.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/img/textfile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -0,0 +1,113 @@
@extends('adminlte::page')
@section('title', config('app.name') . ' | Team Files')
@section('content_header')
<h1>{{config('app.name')}} / Teams / Files</h1>
@stop
@section('js')
<x-global-errors></x-global-errors>
@stop
@section('content')
<x-modal id="upload-dropzone" modal-label="upload-dropzone-modal" modal-title="Upload Files" include-close-button="true">
<form class="dropzone" id="teamFile" action="{{route('uploadTeamFile')}}"></form>
<x-slot name="modalFooter">
</x-slot>
</x-modal>
<div class="row">
<div class="col-3 offset-3">
<img src="/img/files.svg" width="230px" height="230px" alt="Team files illustration">
</div>
</div>
<div class="row">
<div class="col">
<div class="card bg-gray-dark">
<div class="card-header bg-indigo">
<div class="card-title"><h4 class="text-bold">Team Files <span class="badge badge-warning"><i class="fas fa-check-circle"></i> {{ (Auth::user()->currentTeam) ? Auth::user()->currentTeam->name : '(No team)' }}</span></h4></div>
</div>
<div class="card-body">
@if(!$files->isEmpty())
<table class="table table-active table-borderless" style="white-space: nowrap">
<thead>
<tr>
<th>#</th>
<th>File name</th>
<th>Caption</th>
<th>Size</th>
<th>Last updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($files as $file)
<tr>
<td>{{$file->id}}</td>
<td>{{ Str::of($file->name)->limit(10, '(..).' . $file->extension) }}</td>
<td>{{ Str::of($file->caption)->limit(10) }}</td>
<td>{{ $file->size }} bytes</td>
<td>{{ $file->updated_at }}</td>
<td>
<button rel="buttonTxtTooltip" data-toggle="tooltip" data-placement="top" title="Download" type="button" class="btn btn-success btn-sm ml-3" onclick="window.location='{{route('downloadTeamFile', ['teamFile' => $file->id])}}'"><i class="fas fa-download"></i></button>
<button rel="buttonTxtTooltip" data-toggle="tooltip" data-placement="top" title="View" type="button" class="btn btn-success btn-sm ml-3"><i class="fas fa-eye"></i></button>
<button rel="buttonTxtTooltip" data-toggle="tooltip" data-placement="top" title="Delete File" type="button" class="btn btn-danger btn-sm ml-3"><i class="fas fa-trash"></i></button>
</td>
</tr>
@endforeach
</tbody>
</table>
@else
<div class="alert alert-warning">
<span class="text-bold"><i class="fas fa-exclamation-triangle"></i> There are currently no team files. Try uploading some to get started.</span>
</div>
@endif
</div>
<div class="card-footer text-center">
<button type="button" class="btn btn-warning" onclick="$('#upload-dropzone').modal('show')"><i class="fas fa-upload"></i> Upload Files</button>
{{ $files->links() }}
</div>
</div>
</div>
</div>
@stop
@section('footer')
@include('breadcrumbs.dashboard.footer')
@stop

View File

@ -31,6 +31,7 @@ use App\Http\Controllers\FormController;
use App\Http\Controllers\HomeController; use App\Http\Controllers\HomeController;
use App\Http\Controllers\ProfileController; use App\Http\Controllers\ProfileController;
use App\Http\Controllers\TeamController; use App\Http\Controllers\TeamController;
use App\Http\Controllers\TeamFileController;
use App\Http\Controllers\UserController; use App\Http\Controllers\UserController;
use App\Http\Controllers\VacancyController; use App\Http\Controllers\VacancyController;
use App\Http\Controllers\VoteController; 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']) Route::get('teams/invites/{action}/{token}', [TeamController::class, 'processInviteAction'])
->name('processInvite'); ->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::group(['prefix' => '/applications'], function () {
Route::get('/my-applications', [ApplicationController::class, 'showUserApps']) Route::get('/my-applications', [ApplicationController::class, 'showUserApps'])
->name('showUserApps') ->name('showUserApps')