diff --git a/web/app/Http/Controllers/Api/ThumbnailController.php b/web/app/Http/Controllers/Api/ThumbnailController.php index 44bf766..2177861 100644 --- a/web/app/Http/Controllers/Api/ThumbnailController.php +++ b/web/app/Http/Controllers/Api/ThumbnailController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api; +use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; @@ -14,50 +15,93 @@ use App\Models\RenderTracker; class ThumbnailController extends Controller { - public function renderAsset(Request $request) + private function assetValidationRules() { - $validator = Validator::make($request->all(), [ + return [ 'id' => [ 'required', Rule::exists('App\Models\Asset', 'id')->where(function($query) { - return $query->where('moderated', false); + return $query->where('moderated', false) + ->where('approved', true); }) ], 'type' => 'regex:/(3D|2D)/i' - ]); + ]; + } + + private function userValidationRules() + { + // TODO: Fail validation if user is moderated. + return [ + 'id' => [ + 'required', + Rule::exists('App\Models\User', 'id') + ], + 'position' => ['sometimes', 'regex:/(Full|Bust)/i'], + 'type' => 'regex:/(3D|2D)/i' + ]; + } + + private function handleRender(Request $request, string $renderType) + { + $validator = Validator::make($request->all(), $this->{strtolower($renderType) . 'ValidationRules'}()); if($validator->fails()) return ValidationHelper::generateValidatorError($validator); $valid = $validator->valid(); - $asset = Asset::where('id', $valid['id'])->first(); + $model = ('App\\Models\\' . $renderType)::where('id', $valid['id'])->first(); + + if($renderType == 'User') { + if($valid['position'] == null) + $valid['position'] = 'Full'; + + $valid['position'] = strtolower($valid['position']); + } elseif($renderType == 'Asset') { + // TODO: XlXi: Turn this into a switch case and fill in the rest of the unrenderables. + // Things like HTML assets should just have a generic "default" image. + if($model->assetTypeId == 1) + $model = Asset::where('id', $model->parentAsset)->first(); + } $valid['type'] = strtolower($valid['type']); - if($asset->thumbnail2DHash && $valid['type'] == '2d') - return response(['status' => 'success', 'data' => route('content', $asset->thumbnail2DHash)]); + if($model->thumbnail2DHash && $valid['type'] == '2d') + return response(['status' => 'success', 'data' => route('content', $model->thumbnail2DHash)]); - if($asset->thumbnail3DHash && $valid['type'] == '3d') - return response(['status' => 'success', 'data' => route('content', $asset->thumbnail3DHash)]); + if($model->thumbnail3DHash && $valid['type'] == '3d') + return response(['status' => 'success', 'data' => route('content', $model->thumbnail3DHash)]); - $tracker = RenderTracker::where('type', sprintf('asset%s', $valid['type'])) - ->where('target', $valid['id']); + $trackerType = sprintf('%s%s', strtolower($renderType), $valid['type']); + $tracker = RenderTracker::where('type', $trackerType) + ->where('target', $valid['id']) + ->where('created_at', '>', Carbon::now()->subMinute()); if(!$tracker->exists()) { - $tracker = new RenderTracker; - $tracker->type = sprintf('asset%s', $valid['type']); - $tracker->target = $valid['id']; - $tracker->save(); + $tracker = RenderTracker::create([ + 'type' => $trackerType, + 'target' => $valid['id'] + ]); - ArbiterRender::dispatch($tracker, $valid['type'] == '3d', $asset->typeString(), $asset->id); + ArbiterRender::dispatch( + $tracker, + $valid['type'] == '3d', + ($renderType == 'User' ? $valid['position'] : $model->typeString()), + $model->id + ); } return response(['status' => 'loading']); } + public function renderAsset(Request $request) + { + return $this->handleRender($request, 'Asset'); + } + public function renderUser() { - // + return handleRender($request, 'User'); } public function tryAsset() diff --git a/web/app/Http/Controllers/Web/ClientController.php b/web/app/Http/Controllers/Web/ClientController.php new file mode 100644 index 0000000..52bc352 --- /dev/null +++ b/web/app/Http/Controllers/Web/ClientController.php @@ -0,0 +1,98 @@ + [ + 'required', + Rule::exists('App\Models\Asset', 'id')->where(function($query) { + return $query->where('moderated', false) + ->where('approved', true); + }) + ], + 'version' => [ + 'sometimes', + 'numeric' + ] + ]; + } + + function assetVersionValidator() + { + return [ + 'assetversionid' => [ + 'required', + Rule::exists('App\Models\AssetVersion', 'id') + ] + ]; + } + + function asset(Request $request) + { + // TODO: XlXi: userAssetId (owned asset) + $reqData = array_change_key_case($request->all()); + + $validatorRuleSet = 'assetRegularValidator'; + if(array_key_exists('assetversionid', $reqData)) + $validatorRuleSet = 'assetVersionValidator'; + elseif(array_key_exists('userassetid', $reqData)) + return response('todo'); + + $validator = Validator::make($reqData, $this->{$validatorRuleSet}()); + + if($validator->fails()) + return ValidationHelper::generateValidatorError($validator); + + $valid = $validator->valid(); + $asset = null; + + if(array_key_exists('assetversionid', $reqData)) { + $assetVersion = AssetVersion::where('id', $valid['assetversionid'])->first(); + $asset = $assetVersion->asset; + + $valid['version'] = $assetVersion->localVersion; + } else { + $asset = Asset::where('id', $valid['id'])->first(); + + if(!array_key_exists('version', $valid)) + $valid['version'] = 0; + } + + if($asset == null) { + $validator->errors()->add('version', 'Unknown asset version.'); + return ValidationHelper::generateValidatorError($validator); + } + + if( + !($asset->onSale || (Auth::check() && Auth::user()->id == $asset->creatorId)) // not on sale and not the creator + && + !GridHelper::hasAllAccess() // not grid + ) { + $validator->errors()->add('id', 'You do not have access to this asset.'); + return ValidationHelper::generateValidatorError($validator); + } + + $contentHash = $asset->getContent($valid['version']); + if(!$contentHash) { + $validator->errors()->add('version', 'Unknown asset version.'); + return ValidationHelper::generateValidatorError($validator); + } + + return redirect(route('content', $contentHash)); + } +} diff --git a/web/app/Jobs/ArbiterRender.php b/web/app/Jobs/ArbiterRender.php index afc62b2..23ecd8f 100644 --- a/web/app/Jobs/ArbiterRender.php +++ b/web/app/Jobs/ArbiterRender.php @@ -81,12 +81,13 @@ class ArbiterRender implements ShouldQueue */ public function handle() { + // TODO: XlXi: User avatar/closeup render support. $arguments = [ url(sprintf('/asset?id=%d', $this->assetId)), // TODO: XlXi: Move url() to route once the route actually exists. ($this->is3D ? 'OBJ' : 'PNG'), 840, // Width 840, // Height - url('/') + url('/') . '/' ]; switch($this->type) { case 'Head': @@ -118,7 +119,10 @@ class ArbiterRender implements ShouldQueue )); if(is_soap_fault($result)) + { + $this->tracker->delete(); $this->fail(sprintf('SOAP Fault: (faultcode: %s, faultstring: %s)', $result->faultcode, $result->faultstring)); + } $result = $result->OpenJobExResult->LuaValue[0]->value; diff --git a/web/app/Models/RenderTracker.php b/web/app/Models/RenderTracker.php index 5a019b5..5170629 100644 --- a/web/app/Models/RenderTracker.php +++ b/web/app/Models/RenderTracker.php @@ -9,6 +9,16 @@ class RenderTracker extends Model { use HasFactory; + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'type', + 'target' + ]; + public function targetObj() { if($this->type == 'user2d' || $this->type == 'user3d') diff --git a/web/app/Models/asset.php b/web/app/Models/asset.php index 00f9df6..855a7e5 100644 --- a/web/app/Models/asset.php +++ b/web/app/Models/asset.php @@ -2,9 +2,10 @@ namespace App\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -use Carbon\Carbon; +use Illuminate\Support\Facades\Http; class Asset extends Model { @@ -135,6 +136,11 @@ class Asset extends Model return $this->belongsTo(User::class, 'creatorId'); } + public function parentAsset() + { + return $this->belongsTo(User::class, 'parentAssetId'); + } + public function typeString() { return $this->assetTypes[$this->assetTypeId]; @@ -147,7 +153,18 @@ class Asset extends Model public function getThumbnail() { - return 'https://gtoria.local/images/testing/hat.png'; + $renderId = $this->id; + + // TODO: XlXi: Turn this into a switch case and fill in the rest of the unrenderables. + // Things like HTML assets should just have a generic "default" image. + if($this->assetTypeId == 1) // Image + $renderId = $this->parentAsset->id; + + $thumbnail = Http::get(route('thumbnails.v1.asset', ['id' => $renderId, 'type' => '2d'])); + if($thumbnail->json('status') == 'loading') + return 'https://gtoria.local/images/busy/asset.png'; + + return $thumbnail->json('data'); } public function set2DHash($hash) @@ -189,13 +206,13 @@ class Asset extends Model // Version 0 is internally considered the latest. public function getContent($version = 0) { - if($version === 0) + if($version == 0) return $this->latestVersion->contentURL; $assetVersion = AssetVersion::where('parentAsset', $this->id) ->where('localVersion', $version) - ->get(); + ->first(); - return ($assetVersion !== null ? $assetVersion->contentURL : null); + return ($assetVersion ? $assetVersion->contentURL : null); } } diff --git a/web/database/migrations/2014_10_12_000000_create_users_table.php b/web/database/migrations/2014_10_12_000000_create_users_table.php index 7005d3f..40fc756 100644 --- a/web/database/migrations/2014_10_12_000000_create_users_table.php +++ b/web/database/migrations/2014_10_12_000000_create_users_table.php @@ -25,6 +25,9 @@ return new class extends Migration $table->unsignedBigInteger('tokens')->default(0); $table->dateTime('next_reward')->useCurrent(); + $table->string('thumbnail2DHash')->nullable(); + $table->string('thumbnail3DHash')->nullable(); + $table->timestamps(); }); } diff --git a/web/database/migrations/2022_06_05_140351_create_assets_table.php b/web/database/migrations/2022_06_05_140351_create_assets_table.php index e801963..11f5f2d 100644 --- a/web/database/migrations/2022_06_05_140351_create_assets_table.php +++ b/web/database/migrations/2022_06_05_140351_create_assets_table.php @@ -19,6 +19,7 @@ return new class extends Migration $table->unsignedBigInteger('creatorId'); $table->string('name'); $table->string('description')->nullable(); + $table->unsignedBigInteger('parentAssetId')->nullable()->comment('Used by things like images that were created because of something else.'); $table->boolean('approved')->default(false); $table->boolean('moderated')->default(false); diff --git a/web/database/migrations/2022_06_12_210756_create_asset_versions_table.php b/web/database/migrations/2022_06_12_210756_create_asset_versions_table.php index 9405707..142c373 100644 --- a/web/database/migrations/2022_06_12_210756_create_asset_versions_table.php +++ b/web/database/migrations/2022_06_12_210756_create_asset_versions_table.php @@ -19,8 +19,6 @@ return new class extends Migration $table->unsignedBigInteger('parentAsset'); $table->unsignedBigInteger('localVersion'); - // Calculating the subdomain on runtime is too expensive. - // So full URLs are used instead of just the hashes. $table->string('contentURL'); $table->timestamps(); diff --git a/web/resources/views/web/shop/asset.blade.php b/web/resources/views/web/shop/asset.blade.php index b007a06..2dbd36e 100644 --- a/web/resources/views/web/shop/asset.blade.php +++ b/web/resources/views/web/shop/asset.blade.php @@ -58,12 +58,12 @@
+