Asset related changes.
- Created configuration for Roblox cookie to be used with rate-limited Roblox apis. - Autouploader APIs. - Fillable Assets and AssetVersions. - Indev auto uploader. - Roblox asset tracker. This will prevent reuploads of already auto-uploaded assets. - Update assets to use longText instead of string on descriptions. - Modify view to be able to display newlines. - Fix truncate on shop card titles. - Create default thumbnails for deleted, pending, and unrenderable(unavailable) asset thumbnails. - Make /asset redirect to a local asset if one exists for a Roblox asset ID when the the requested asset isnt already locally on the website. - Added Roblox's default thumbnails to grid storage folder. - Temporary clientsettings hack.
|
|
@ -7,6 +7,8 @@ MIX_APP_URL=http://virtubrick.net
|
||||||
|
|
||||||
IS_TEST_ENVIRONMENT=true
|
IS_TEST_ENVIRONMENT=true
|
||||||
|
|
||||||
|
ROBLOX_COOKIE=
|
||||||
|
|
||||||
GAMESERVER_IP=127.0.0.1
|
GAMESERVER_IP=127.0.0.1
|
||||||
THUMBNAILER_IP=127.0.0.1
|
THUMBNAILER_IP=127.0.0.1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
XlXi 2022
|
||||||
|
Asset Helper
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Helpers;
|
||||||
|
|
||||||
|
use GuzzleHttp\Cookie\CookieJar;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
use App\Helpers\CdnHelper;
|
||||||
|
use App\Models\Asset;
|
||||||
|
use App\Models\AssetVersion;
|
||||||
|
use App\Models\RobloxAsset;
|
||||||
|
|
||||||
|
class AssetHelper
|
||||||
|
{
|
||||||
|
private static function cookiedRequest()
|
||||||
|
{
|
||||||
|
$cookieJar = CookieJar::fromArray([
|
||||||
|
'.ROBLOXSECURITY' => env('app.robloxcookie')
|
||||||
|
], '.roblox.com');
|
||||||
|
|
||||||
|
return Http::withOptions(['cookies' => $cookieJar, 'headers' => ['User-Agent' => 'Roblox/WinInet']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function uploadRobloxAsset($id, $uploadToHolder = false)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
$uploadedAsset = RobloxAsset::where('robloxAssetId', $id)->first();
|
||||||
|
if($uploadedAsset)
|
||||||
|
return $uploadedAsset->asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
$marketplaceResult = self::cookiedRequest()->get('https://api.roblox.com/marketplace/productinfo?assetId=' . $id);
|
||||||
|
$assetResult = self::cookiedRequest()->get('https://assetdelivery.roblox.com/v2/asset?id=' . $id);
|
||||||
|
|
||||||
|
if(!$marketplaceResult->ok() || !$assetResult->ok())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$assetContent = Http::get($assetResult['locations'][0]['location']);
|
||||||
|
$hash = CdnHelper::SaveContent($assetContent->body(), $assetContent->header('Content-Type'));
|
||||||
|
$asset = Asset::create([
|
||||||
|
'creatorId' => ($uploadToHolder ? 1 : Auth::user()->id),
|
||||||
|
'name' => $marketplaceResult['Name'],
|
||||||
|
'description' => $marketplaceResult['Description'],
|
||||||
|
'approved' => true,
|
||||||
|
'priceInTokens' => $marketplaceResult['PriceInRobux'] ?: 0,
|
||||||
|
'onSale' => $marketplaceResult['IsForSale'],
|
||||||
|
'assetTypeId' => $marketplaceResult['AssetTypeId'],
|
||||||
|
'assetVersionId' => 0
|
||||||
|
]);
|
||||||
|
$assetVersion = AssetVersion::create([
|
||||||
|
'parentAsset' => $asset->id,
|
||||||
|
'localVersion' => 1,
|
||||||
|
'contentURL' => $hash
|
||||||
|
]);
|
||||||
|
$asset->assetVersionId = $assetVersion->id;
|
||||||
|
$asset->save();
|
||||||
|
|
||||||
|
RobloxAsset::create([
|
||||||
|
'robloxAssetId' => $id,
|
||||||
|
'localAssetId' => $asset->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function uploadCustomRobloxAsset($id, $uploadToHolder = false, $b64Content)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
$uploadedAsset = RobloxAsset::where('robloxAssetId', $id)->first();
|
||||||
|
if($uploadedAsset)
|
||||||
|
return $uploadedAsset->asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
$marketplaceResult = self::cookiedRequest()->get('https://api.roblox.com/marketplace/productinfo?assetId=' . $id);
|
||||||
|
|
||||||
|
if(!$marketplaceResult->ok())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$hash = CdnHelper::SaveContentB64($b64Content, 'application/octet-stream');
|
||||||
|
$asset = Asset::create([
|
||||||
|
'creatorId' => ($uploadToHolder ? 1 : Auth::user()->id),
|
||||||
|
'name' => $marketplaceResult['Name'],
|
||||||
|
'description' => $marketplaceResult['Description'],
|
||||||
|
'approved' => true,
|
||||||
|
'priceInTokens' => $marketplaceResult['PriceInRobux'] ?: 0,
|
||||||
|
'onSale' => $marketplaceResult['IsForSale'],
|
||||||
|
'assetTypeId' => $marketplaceResult['AssetTypeId'],
|
||||||
|
'assetVersionId' => 0
|
||||||
|
]);
|
||||||
|
$assetVersion = AssetVersion::create([
|
||||||
|
'parentAsset' => $asset->id,
|
||||||
|
'localVersion' => 1,
|
||||||
|
'contentURL' => $hash
|
||||||
|
]);
|
||||||
|
$asset->assetVersionId = $assetVersion->id;
|
||||||
|
$asset->save();
|
||||||
|
|
||||||
|
RobloxAsset::create([
|
||||||
|
'robloxAssetId' => $id,
|
||||||
|
'localAssetId' => $asset->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $asset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -113,6 +113,29 @@ class GridHelper
|
||||||
return $job;
|
return $job;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function getThumbDisk()
|
||||||
|
{
|
||||||
|
return Storage::build([
|
||||||
|
'driver' => 'local',
|
||||||
|
'root' => storage_path('app/grid/thumbnails'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getDefaultThumbnail($fileName)
|
||||||
|
{
|
||||||
|
$disk = $self::getThumbDisk();
|
||||||
|
|
||||||
|
if(!$disk->exists($fileName))
|
||||||
|
throw new Exception('Unable to locate template file.');
|
||||||
|
|
||||||
|
return $disk->get($fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getUnknownThumbnail()
|
||||||
|
{
|
||||||
|
return $self::getDefaultThumbnail('UnknownThumbnail.png');
|
||||||
|
}
|
||||||
|
|
||||||
public static function getArbiter($name)
|
public static function getArbiter($name)
|
||||||
{
|
{
|
||||||
$query = DynamicWebConfiguration::where('name', sprintf('%sArbiterIP', $name))->first();
|
$query = DynamicWebConfiguration::where('name', sprintf('%sArbiterIP', $name))->first();
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ namespace App\Http\Controllers\Api;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
use App\Helpers\AssetHelper;
|
||||||
|
use App\Helpers\GridHelper;
|
||||||
use App\Helpers\ValidationHelper;
|
use App\Helpers\ValidationHelper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Jobs\AppDeployment;
|
use App\Jobs\AppDeployment;
|
||||||
|
|
@ -135,4 +137,47 @@ class AdminController extends Controller
|
||||||
|
|
||||||
AppDeployment::dispatch($deployment);
|
AppDeployment::dispatch($deployment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RCC Only
|
||||||
|
function uploadRobloxAsset(Request $request)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'contentId' => ['required', 'int']
|
||||||
|
]);
|
||||||
|
|
||||||
|
if($validator->fails())
|
||||||
|
return ValidationHelper::generateValidatorError($validator);
|
||||||
|
|
||||||
|
if(!GridHelper::hasAllAccess())
|
||||||
|
{
|
||||||
|
$validator->errors()->add('contentId', 'This API can only be called by the web service.');
|
||||||
|
return ValidationHelper::generateValidatorError($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$valid = $validator->valid();
|
||||||
|
$asset = AssetHelper::uploadRobloxAsset($valid['contentId'], true);
|
||||||
|
|
||||||
|
return route('client.asset', ['id' => $asset->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadAsset(Request $request)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'contentId' => ['required', 'int']
|
||||||
|
]);
|
||||||
|
|
||||||
|
if($validator->fails())
|
||||||
|
return ValidationHelper::generateValidatorError($validator);
|
||||||
|
|
||||||
|
if(!GridHelper::hasAllAccess())
|
||||||
|
{
|
||||||
|
$validator->errors()->add('contentId', 'This API can only be called by the web service.');
|
||||||
|
return ValidationHelper::generateValidatorError($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$valid = $validator->valid();
|
||||||
|
$asset = AssetHelper::uploadCustomRobloxAsset($valid['contentId'], true, base64_encode($request->getContent()));
|
||||||
|
|
||||||
|
return route('client.asset', ['id' => $asset->id]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
use App\Helpers\GridHelper;
|
||||||
use App\Helpers\ValidationHelper;
|
use App\Helpers\ValidationHelper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Jobs\ArbiterRender;
|
use App\Jobs\ArbiterRender;
|
||||||
|
|
@ -21,8 +22,7 @@ class ThumbnailController extends Controller
|
||||||
'id' => [
|
'id' => [
|
||||||
'required',
|
'required',
|
||||||
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
|
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'
|
'type' => 'regex:/(3D|2D)/i'
|
||||||
|
|
@ -60,15 +60,19 @@ class ThumbnailController extends Controller
|
||||||
|
|
||||||
$valid['position'] = strtolower($valid['position']);
|
$valid['position'] = strtolower($valid['position']);
|
||||||
} elseif($renderType == 'Asset') {
|
} elseif($renderType == 'Asset') {
|
||||||
|
if($model->moderated)
|
||||||
|
return response(['status' => 'success', 'data' => '/thumbs/DeletedThumbnail.png']);
|
||||||
|
|
||||||
|
if(!$model->approved)
|
||||||
|
return response(['status' => 'success', 'data' => '/thumbs/PendingThumbnail.png']);
|
||||||
|
|
||||||
|
if(!$model->assetType->renderable)
|
||||||
|
return response(['status' => 'success', 'data' => '/thumbs/UnavailableThumbnail.png']);
|
||||||
|
|
||||||
if(!$model->{$valid['type'] == '3d' ? 'canRender3D' : 'isRenderable'}()) {
|
if(!$model->{$valid['type'] == '3d' ? 'canRender3D' : 'isRenderable'}()) {
|
||||||
$validator->errors()->add('id', 'This asset cannot be rendered.');
|
$validator->errors()->add('id', 'This asset cannot be rendered.');
|
||||||
return ValidationHelper::generateValidatorError($validator);
|
return ValidationHelper::generateValidatorError($validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use App\Helpers\ValidationHelper;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Asset;
|
use App\Models\Asset;
|
||||||
use App\Models\AssetVersion;
|
use App\Models\AssetVersion;
|
||||||
|
use App\Models\RobloxAsset;
|
||||||
|
|
||||||
class ClientController extends Controller
|
class ClientController extends Controller
|
||||||
{
|
{
|
||||||
|
|
@ -56,7 +57,13 @@ class ClientController extends Controller
|
||||||
$validator = Validator::make($reqData, $this->{$validatorRuleSet}());
|
$validator = Validator::make($reqData, $this->{$validatorRuleSet}());
|
||||||
|
|
||||||
if($validator->fails())
|
if($validator->fails())
|
||||||
return ValidationHelper::generateValidatorError($validator);
|
{
|
||||||
|
$rbxAsset = RobloxAsset::where('robloxAssetId', $request->get('id'))->first();
|
||||||
|
if($rbxAsset)
|
||||||
|
return redirect()->route('client.asset', ['id' => $rbxAsset->localAssetId]);
|
||||||
|
|
||||||
|
return redirect('https://assetdelivery.roblox.com/v1/asset?id=' . ($request->get('id') ?: 0));//return ValidationHelper::generateValidatorError($validator);
|
||||||
|
}
|
||||||
|
|
||||||
$valid = $validator->valid();
|
$valid = $validator->valid();
|
||||||
$asset = null;
|
$asset = null;
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ class ArbiterRender implements ShouldQueue
|
||||||
420*4, // Height // XlXi: These get scaled down by 4.
|
420*4, // Height // XlXi: These get scaled down by 4.
|
||||||
url('/') . '/'
|
url('/') . '/'
|
||||||
];
|
];
|
||||||
|
|
||||||
switch($this->type) {
|
switch($this->type) {
|
||||||
case 'Head':
|
case 'Head':
|
||||||
case 'Shirt':
|
case 'Shirt':
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,17 @@ class AssetVersion extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'parentAsset',
|
||||||
|
'localVersion',
|
||||||
|
'contentURL'
|
||||||
|
];
|
||||||
|
|
||||||
public function asset()
|
public function asset()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Asset::class, 'parentAsset');
|
return $this->belongsTo(Asset::class, 'parentAsset');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class RobloxAsset extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'localAssetId',
|
||||||
|
'robloxAssetId'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function asset()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Asset::class, 'localAssetId');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,6 +32,26 @@ class Asset extends Model
|
||||||
'updated_at' => 'datetime',
|
'updated_at' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'creatorId',
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'parentAssetId',
|
||||||
|
'approved',
|
||||||
|
'priceInTokens',
|
||||||
|
'onSale',
|
||||||
|
'assetTypeId',
|
||||||
|
'assetVersionId',
|
||||||
|
'universeId',
|
||||||
|
'maxPlayers',
|
||||||
|
'chatStyleEnum'
|
||||||
|
];
|
||||||
|
|
||||||
/* TODO: XlXi: move to db */
|
/* TODO: XlXi: move to db */
|
||||||
protected $assetGenres = [
|
protected $assetGenres = [
|
||||||
/* 0 */ 'All',
|
/* 0 */ 'All',
|
||||||
|
|
@ -121,11 +141,6 @@ class Asset extends Model
|
||||||
{
|
{
|
||||||
$renderId = $this->id;
|
$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']));
|
$thumbnail = Http::get(route('thumbnails.v1.asset', ['id' => $renderId, 'type' => '2d']));
|
||||||
if($thumbnail->json('status') == 'loading')
|
if($thumbnail->json('status') == 'loading')
|
||||||
return ($this->assetTypeId == 9 ? 'https://virtubrick.local/images/busy/game.png' : 'https://virtubrick.local/images/busy/asset.png');
|
return ($this->assetTypeId == 9 ? 'https://virtubrick.local/images/busy/game.png' : 'https://virtubrick.local/images/busy/asset.png');
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,14 @@ class RouteServiceProvider extends ServiceProvider
|
||||||
->middleware('api')
|
->middleware('api')
|
||||||
->namespace('App\Http\Controllers\Api')
|
->namespace('App\Http\Controllers\Api')
|
||||||
->group(base_path('routes/api.php'));
|
->group(base_path('routes/api.php'));
|
||||||
|
|
||||||
|
//
|
||||||
|
// Domain: clientsettings.api.virtubrick.net
|
||||||
|
//
|
||||||
|
Route::domain('clientsettings.api.' . DomainHelper::TopLevelDomain())
|
||||||
|
->middleware('api')
|
||||||
|
->namespace('App\Http\Controllers\ClientSettings')
|
||||||
|
->group(base_path('routes/clientsettings.php'));
|
||||||
|
|
||||||
//
|
//
|
||||||
// Domain: cdn.virtubrick.net
|
// Domain: cdn.virtubrick.net
|
||||||
|
|
|
||||||
|
|
@ -224,4 +224,16 @@ return [
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'testenv' => (bool) env('IS_TEST_ENVIRONMENT', true),
|
'testenv' => (bool) env('IS_TEST_ENVIRONMENT', true),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Roblox Cookie
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Allows the site to access marketplace service APIs without rate
|
||||||
|
| limiting.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'robloxcookie' => env('ROBLOX_COOKIE', ''),
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ return new class extends Migration
|
||||||
|
|
||||||
$table->unsignedBigInteger('creatorId');
|
$table->unsignedBigInteger('creatorId');
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->string('description')->nullable();
|
$table->longText('description')->nullable();
|
||||||
$table->unsignedBigInteger('parentAssetId')->nullable()->comment('Used by things like images that were created because of something else.');
|
$table->unsignedBigInteger('parentAssetId')->nullable()->comment('Used by things like images that were created because of something else.');
|
||||||
|
|
||||||
$table->boolean('approved')->default(false);
|
$table->boolean('approved')->default(false);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('roblox_assets', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('localAssetId');
|
||||||
|
$table->unsignedBigInteger('robloxAssetId');
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('roblox_assets');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -24,7 +24,6 @@ class AssetTypeSeeder extends Seeder
|
||||||
[
|
[
|
||||||
'name' => 'T-Shirt',
|
'name' => 'T-Shirt',
|
||||||
'renderable' => true,
|
'renderable' => true,
|
||||||
'renderable3d' => true,
|
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'sellable' => true,
|
'sellable' => true,
|
||||||
'wearable' => true
|
'wearable' => true
|
||||||
|
|
@ -108,8 +107,6 @@ class AssetTypeSeeder extends Seeder
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Face',
|
'name' => 'Face',
|
||||||
'renderable' => true,
|
|
||||||
'renderable3d' => true,
|
|
||||||
'copyable' => true,
|
'copyable' => true,
|
||||||
'sellable' => true,
|
'sellable' => true,
|
||||||
'wearable' => true
|
'wearable' => true
|
||||||
|
|
@ -235,8 +232,7 @@ class AssetTypeSeeder extends Seeder
|
||||||
[
|
[
|
||||||
'name' => 'MeshPart',
|
'name' => 'MeshPart',
|
||||||
'renderable' => true,
|
'renderable' => true,
|
||||||
'renderable3d' => true,
|
'renderable3d' => true
|
||||||
'locked' => true
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Hair Accessory',
|
'name' => 'Hair Accessory',
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -228,7 +228,7 @@ class ShopItemCard extends Component {
|
||||||
className='img-fluid'
|
className='img-fluid'
|
||||||
/>
|
/>
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
<p>{ item.Name }</p>
|
<p className="text-truncate">{ item.Name }</p>
|
||||||
{ item.OnSale ?
|
{ item.OnSale ?
|
||||||
<p className="virtubrick-tokens text-truncate">{commaSeparate(item.Price)}</p>
|
<p className="virtubrick-tokens text-truncate">{commaSeparate(item.Price)}</p>
|
||||||
: <p className="text-muted">Offsale</p>
|
: <p className="text-muted">Offsale</p>
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,18 @@
|
||||||
<div class="container-md">
|
<div class="container-md">
|
||||||
<h4>Auto Uploader</h4>
|
<h4>Auto Uploader</h4>
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
<p>todo</p>
|
<label for="vb-rbx-asset" class="form-label">Roblox Asset ID</label>
|
||||||
<p>Under Construction</p>
|
<div class="input-group mb-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Roblox Asset ID Here"
|
||||||
|
aria-label="Roblox Asset ID Here"
|
||||||
|
name="rbx-asset" id="vb-rbx-asset"
|
||||||
|
aria-describedby="vb-rbx-asset"
|
||||||
|
>
|
||||||
|
<button type="submit" class="btn btn-primary" type="button" name="rbx-asset-button" id="vb-rbx-asset-btn">Search</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
@ -126,7 +126,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-9">
|
<div class="col-9">
|
||||||
@if ( $asset->description )
|
@if ( $asset->description )
|
||||||
<p>{{ $asset->description }}</p>
|
<p>{!! nl2br(e($asset->description)) !!}</p>
|
||||||
@else
|
@else
|
||||||
<p class="text-muted">This item has no description.</p>
|
<p class="text-muted">This item has no description.</p>
|
||||||
@endif
|
@endif
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,10 @@ Route::middleware('auth')->group(function () {
|
||||||
Route::get('/deploy', 'AdminController@deploy')->name('deploy');
|
Route::get('/deploy', 'AdminController@deploy')->name('deploy');
|
||||||
Route::post('/deploy/{version}', 'AdminController@deployVersion')->name('deploy');
|
Route::post('/deploy/{version}', 'AdminController@deployVersion')->name('deploy');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// RCC Only
|
||||||
|
Route::get('/upload-rbx-asset', 'AdminController@uploadRobloxAsset')->withoutMiddleware('auth')->name('uploadrbxasset');
|
||||||
|
Route::post('/upload-asset', 'AdminController@uploadAsset')->withoutMiddleware('auth')->name('uploadAsset');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ local Lighting = game:GetService("Lighting")
|
||||||
Lighting.ClockTime = 13
|
Lighting.ClockTime = 13
|
||||||
Lighting.GeographicLatitude = -5
|
Lighting.GeographicLatitude = -5
|
||||||
|
|
||||||
game:Load(assetUrl)
|
local accoutrement = game:GetObjects(assetUrl)[1]
|
||||||
|
accoutrement.Parent = workspace
|
||||||
|
|
||||||
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop = ]] true)
|
return game:GetService("ThumbnailGenerator"):Click(fileExtension, x, y, --[[hideSky = ]] true, --[[crop = ]] true)
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 43 B |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 9.3 KiB |