Started working on support for uploading photos

This commit is contained in:
Andy Heathershaw 2016-09-02 21:27:50 +01:00
parent 67bfecd2b3
commit 9360d8bbbe
25 changed files with 518 additions and 22 deletions

View File

@ -16,7 +16,7 @@ class Album extends Model
* @var array
*/
protected $fillable = [
'name', 'description'
'name', 'description', 'url_alias'
];
/**
@ -31,7 +31,19 @@ class Album extends Model
{
$this->name = $request->get('name');
$this->description = $request->get('description');
$this->generateAlias();
return $this;
}
public function generateAlias()
{
$this->url_alias = ucfirst(preg_replace('/[^a-z0-9\-]/', '-', strtolower($this->name)));
}
public function getUploadDisk()
{
// TODO allow albums to specify a storage location
return 'local';
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace App\Console\Commands;
use App\Album;
use App\Photo;
use App\Upload;
use App\UploadPhoto;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class ProcessUploadCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'twilight:process-uploads';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Processes uploads made through the web application.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$uploadsToProcess = Upload::where([
['is_completed', false],
['is_processing', false]
])
->orderBy('created_at', 'desc')
->get();
foreach ($uploadsToProcess as $upload)
{
$this->output->writeln(sprintf('Processing upload #%d', $upload->id));
$this->handleUpload($upload);
}
}
private function handleUpload(Upload $upload)
{
$photos = $upload->uploadPhotos;
foreach ($photos as $photo)
{
$this->handlePhoto($photo);
}
}
private function handlePhoto(UploadPhoto $uploadPhoto)
{
$photo = $uploadPhoto->photo;
$this->output->writeln(sprintf('Analysing photo #%d: %s', $photo->id, $photo->name));
$album = $photo->album;
$photoFile = Storage::path(sprintf('albums/%s/%s', $album->url_alias, $photo->file_name), $album->getUploadDisk());
dump($photoFile);
dump(@exif_read_data($photoFile));
}
}

View File

@ -2,6 +2,8 @@
namespace App\Console;
use App\Console\Commands\ProcessUploadCommand;
use App\Upload;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -13,7 +15,7 @@ class Kernel extends ConsoleKernel
* @var array
*/
protected $commands = [
//
ProcessUploadCommand::class
];
/**
@ -24,8 +26,11 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')
// ->hourly();
$schedule->command('twilight:process-uploads')
->everyMinute()
->when(function () {
return (Upload::where('is_completed', 0)->count() > 0);
});
}
/**

View File

@ -73,7 +73,7 @@ class ThemeHelper
{
$themeName = ThemeHelper::DEFAULT_THEME;
$currentTheme = Configuration::all()->where('key', 'theme')->first();
$currentTheme = Configuration::where('key', 'theme')->first();
if (!is_null($currentTheme))
{
$themeName = $currentTheme->value;

View File

@ -43,7 +43,7 @@ class AlbumController extends Controller
{
$this->authorize('admin-access');
$album = $this->loadAlbum($id);
$album = AlbumController::loadAlbum($id);
return Theme::render('admin.delete_album', ['album' => $album]);
}
@ -74,7 +74,7 @@ class AlbumController extends Controller
{
$this->authorize('admin-access');
$album = $this->loadAlbum($id);
$album = AlbumController::loadAlbum($id);
return Theme::render('admin.show_album', ['album' => $album]);
}
@ -89,7 +89,7 @@ class AlbumController extends Controller
{
$this->authorize('admin-access');
$album = $this->loadAlbum($id);
$album = AlbumController::loadAlbum($id);
return Theme::render('admin.edit_album', ['album' => $album]);
}
@ -105,7 +105,7 @@ class AlbumController extends Controller
{
$this->authorize('admin-access');
$album = $this->loadAlbum($id);
$album = AlbumController::loadAlbum($id);
$album->fromRequest($request)->save();
return Theme::render('admin.show_album', ['album' => $album]);
@ -127,9 +127,13 @@ class AlbumController extends Controller
return redirect(route('albums.index'));
}
private function loadAlbum($id)
/**
* @param $id
* @return Album
*/
public static function loadAlbum($id)
{
$album = Album::all()->where('id', intval($id))->first();
$album = Album::where('id', intval($id))->first();
if (is_null($album))
{
App::abort(404);

View File

@ -0,0 +1,126 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Album;
use App\Photo;
use App\Upload;
use App\UploadPhoto;
use Illuminate\Http\File;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
class PhotoController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* 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)
{
$this->authorize('admin-access');
/** @var UploadedFile $photoFile */
$photoFile = UploadedFile::createFromBase($request->files->get('photo'));
// Load the linked album
$album = AlbumController::loadAlbum($request->get('album_id'));
$storageLocation = sprintf('albums/%s', $album->url_alias);
/** @var \SplFileInfo $savedFile */
$savedFilePath = $photoFile->store($storageLocation, $album->getUploadDisk());
$photo = new Photo();
$photo->album_id = $album->id;
$photo->name = $photoFile->getClientOriginalName();
$photo->file_name = basename($savedFilePath);
$photo->mime_type = $photoFile->getClientMimeType();
$photo->file_size = $photoFile->getSize();
$photo->save();
$upload = new Upload();
$upload->is_completed = false;
$upload->is_processing = false;
$upload->number_photos = 1;
$upload->save();
$uploadPhoto = new UploadPhoto();
$uploadPhoto->upload_id = $upload->id;
$uploadPhoto->photo_id = $photo->id;
$uploadPhoto->save();
exit();
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}

33
app/Photo.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class Photo extends Model
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'album_id', 'name', 'description', 'file_name', 'mime_type', 'file_size'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
];
public function album()
{
return $this->belongsTo(Album::class);
}
}

View File

@ -4,6 +4,7 @@ namespace App\Providers;
use App\Facade\Theme;
use App\Helpers\ThemeHelper;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
@ -21,7 +22,14 @@ class AppServiceProvider extends ServiceProvider
return new ThemeHelper();
});
$this->addThemeInfoToView();
try
{
$this->addThemeInfoToView();
}
catch (QueryException $ex)
{
// When running migrations, the configuration table may not exist - so ignore and continue
}
}
/**

33
app/Upload.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class Upload extends Model
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'is_completed', 'is_processing', 'number_photos', 'number_successful', 'number_failed'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
];
public function uploadPhotos()
{
return $this->hasMany(UploadPhoto::class);
}
}

33
app/UploadPhoto.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class UploadPhoto extends Model
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'upload_id', 'photo_id'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
];
public function photo()
{
return $this->belongsTo(Photo::class);
}
}

View File

@ -56,8 +56,8 @@ return [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'database' => env('DB_DATABASE', 'blue_twilight'),
'username' => env('DB_USERNAME', 'blue_twilight'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
@ -70,8 +70,8 @@ return [
'driver' => 'pgsql',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'database' => env('DB_DATABASE', 'blue_twilight'),
'username' => env('DB_USERNAME', 'blue_twilight'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',

View File

@ -30,6 +30,6 @@ class CreateUsersTable extends Migration
*/
public function down()
{
Schema::drop('users');
Schema::dropIfExists('users');
}
}

View File

@ -27,6 +27,6 @@ class CreatePasswordResetsTable extends Migration
*/
public function down()
{
Schema::drop('password_resets');
Schema::dropIfExists('password_resets');
}
}

View File

@ -17,6 +17,7 @@ class CreateAlbumsTable extends Migration
$table->increments('id');
$table->string('name');
$table->text('description');
$table->string('url_alias');
$table->timestamps();
});
}
@ -28,6 +29,6 @@ class CreateAlbumsTable extends Migration
*/
public function down()
{
Schema::drop('albums');
Schema::dropIfExists('albums');
}
}

View File

@ -28,6 +28,6 @@ class CreateConfigTable extends Migration
*/
public function down()
{
Schema::drop('configuration');
Schema::dropIfExists('configuration');
}
}

View File

@ -0,0 +1,41 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreatePhotosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('photos', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedInteger('album_id');
$table->string('name');
$table->string('description')->default('');
$table->string('file_name');
$table->string('mime_type');
$table->unsignedBigInteger('file_size');
$table->timestamps();
$table->foreign('album_id')
->references('id')->on('albums')
->onDelete('no action');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('photos');
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUploadsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('uploads', function (Blueprint $table) {
$table->increments('id');
$table->boolean('is_completed');
$table->boolean('is_processing');
$table->integer('number_photos')->default(0);
$table->integer('number_successful')->default(0);
$table->integer('number_failed')->default(0);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('uploads');
}
}

View File

@ -0,0 +1,40 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUploadPhotosTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('upload_photos', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedInteger('upload_id');
$table->unsignedBigInteger('photo_id');
$table->timestamps();
$table->foreign('upload_id')
->references('id')->on('uploads')
->onDelete('cascade');
$table->foreign('photo_id')
->references('id')->on('photos')
->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('upload_photos');
}
}

View File

@ -6,4 +6,10 @@
.form-actions {
text-align: right;
}
.tab-content {
border: solid 1px #ddd;
border-top: 0;
padding: 10px;
}

View File

@ -6,5 +6,6 @@ return [
'description_label' => 'Description:',
'edit_action' => 'Edit',
'name_label' => 'Name:',
'upload_action' => 'Upload',
'save_action' => 'Save Changes'
];

View File

@ -32,8 +32,8 @@
</div>
<div class="form-actions">
{!! Form::submit(trans('forms.create_action'), ['class' => 'btn btn-success']) !!}
<a href="{{ route('admin') }}" class="btn btn-default">@lang('forms.cancel_action')</a>
{!! Form::submit(trans('forms.create_action'), ['class' => 'btn btn-success']) !!}
</div>
{!! Form::close() !!}
</div>

View File

@ -32,8 +32,8 @@
</div>
<div class="form-actions">
{!! Form::submit(trans('forms.save_action'), ['class' => 'btn btn-success']) !!}
<a href="{{ route('albums.show', ['id' => $album->id]) }}" class="btn btn-default">@lang('forms.cancel_action')</a>
{!! Form::submit(trans('forms.save_action'), ['class' => 'btn btn-success']) !!}
</div>
{!! Form::close() !!}
</div>

View File

@ -8,6 +8,42 @@
<h1>{{ $album->name }}</h1>
<p>{{ $album->description }}</p>
<hr/>
<div>
{{-- Nav tabs --}}
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#upload-tab" aria-controls="upload-tab" role="tab" data-toggle="tab"><i class="fa fa-fw fa-upload"></i> Upload</a></li>
<li role="presentation"><a href="#settings-tab" aria-controls="settings-tab" role="tab" data-toggle="tab"><i class="fa fa-fw fa-cog"></i> Settings</a></li>
</ul>
{{-- Tab panes --}}
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="upload-tab">
<h4>Upload a single image</h4>
{!! Form::open(['route' => 'photos.store', 'method' => 'POST', 'files' => true]) !!}
{!! Form::hidden('album_id', $album->id) !!}
<div class="form-group">
{!! Form::file('photo', ['class' => 'control-label']) !!}
</div>
<div>
{!! Form::submit(trans('forms.upload_action'), ['class' => 'btn btn-success']) !!}
</div>
{!! Form::close() !!}
</div>
<div role="tabpanel" class="tab-pane" id="settings-tab">
<h4>Settings</h4>
</div>
</div>
</div>
<div class="btn-toolbar" style="margin-top: 30px;">
<a href="{{ route('albums.edit', ['id' => $album->id]) }}" class="btn btn-default">@lang('forms.edit_action')</a>
<a href="{{ route('albums.delete', ['id' => $album->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
</div>
</div>
</div>
</div>

View File

@ -22,4 +22,5 @@ Route::group(['prefix' => 'admin'], function () {
Route::get('albums/{id}/delete', 'Admin\AlbumController@delete')->name('albums.delete');
Route::resource('albums', 'Admin\AlbumController');
Route::resource('photos', 'Admin\PhotoController');
});