indev maintenance page, indev /asset impl

This commit is contained in:
Graphictoria 2022-04-16 23:36:42 -04:00
parent 73547da723
commit 5f96ecd96f
17 changed files with 304 additions and 48 deletions

View File

@ -0,0 +1,16 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
class ContentController extends Controller
{
public function fetchAsset(Request $request) {
// Temporary
// TODO: Fetch assets from DB, if it doesn't exist then redirect to roblox's assetdelivery.
return redirect('https://assetdelivery.roblox.com/v1/asset/?id=' . $request->input('id'));
}
}

View File

@ -10,6 +10,10 @@ use App\Models\WebsiteConfiguration;
class MaintenanceController extends Controller
{
public function showPage() {
return view('maintenance');
}
/**
* Handles the maintenance bypass request.
*
@ -20,50 +24,49 @@ class MaintenanceController extends Controller
$password = $request->input('password');
$buttons = $request->input('buttons');
if($password && $buttons)
{
$mtconf = json_decode(WebsiteConfiguration::whereName('MaintenancePassword')->first()->value);
if($password == $mtconf->password)
{
$btns = array_slice($buttons, -count($mtconf->combination));
$data = json_decode(file_get_contents(storage_path('framework/down')), true);
if(isset($data['secret']) && $btns === $mtconf->combination)
{
$trustedHosts = explode(',', env('TRUSTED_HOSTS'));
$origin = parse_url($request->headers->get('origin'), PHP_URL_HOST);
$passCheck = false;
foreach($trustedHosts as &$host)
{
if(str_ends_with($origin, $host))
$passCheck = true;
}
$expiresAt = Carbon::now()->addHours(24);
$bypassCookie = new Cookie('gt_constraint', base64_encode(json_encode([
'expires_at' => $expiresAt->getTimestamp(),
'mac' => hash_hmac('SHA256', $expiresAt->getTimestamp(), $data['secret']),
])), $expiresAt);
if($passCheck)
$bypassCookie = $bypassCookie->withDomain('.' . $origin);
return response('')
->withCookie($bypassCookie);
}
}
return response('')
->setStatusCode(403);
}
else
{
if(!$password || !$buttons) {
return response('{"errors":[{"code":400,"message":"BadRequest"}]}')
->setStatusCode(400)
->header('Cache-Control', 'private')
->header('Content-Type', 'application/json; charset=utf-8');
}
/* */
$mtconf = json_decode(WebsiteConfiguration::whereName('MaintenancePassword')->first()->value);
if(file_exists(storage_path('framework/down')) && $password == $mtconf->password)
{
$btns = array_slice($buttons, -count($mtconf->combination));
$data = json_decode(file_get_contents(storage_path('framework/down')), true);
if(isset($data['secret']) && $btns === $mtconf->combination)
{
$trustedHosts = explode(',', env('TRUSTED_HOSTS'));
$origin = parse_url($request->headers->get('origin'), PHP_URL_HOST);
$passCheck = false;
foreach($trustedHosts as &$host)
{
if(str_ends_with($origin, $host))
$passCheck = true;
}
$expiresAt = Carbon::now()->addHours(24);
$bypassCookie = new Cookie('gt_constraint', base64_encode(json_encode([
'expires_at' => $expiresAt->getTimestamp(),
'mac' => hash_hmac('SHA256', $expiresAt->getTimestamp(), $data['secret']),
])), $expiresAt);
if($passCheck)
$bypassCookie = $bypassCookie->withDomain('.' . $origin);
return response('')
->withCookie($bypassCookie);
}
}
return response('')
->setStatusCode(403);
}
}

View File

@ -37,6 +37,7 @@ class Kernel extends HttpKernel
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
],
'admin' => [

View File

@ -21,7 +21,7 @@ class PreventRequestsDuringMaintenance
*
* @var array
*/
protected $except = ['banners/data', 'maintenance/bypass'];
protected $except = ['maintenance', 'maintenance/bypass'];
/**
* Create a new middleware instance.

View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class AssetGenre extends Model
{
use HasFactory;
}

View File

@ -102,6 +102,26 @@ return [
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mysql-asset' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_ASSET_DATABASE', 'gtoriadev_primary'),
'username' => env('DB_USERNAME', 'gtoriadev_primary'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',

View File

@ -6,6 +6,13 @@ use Illuminate\Support\Facades\Schema;
class CreateAssetTypesTable extends Migration
{
/**
* The database connection that should be used by the migration.
*
* @var string
*/
protected $connection = 'mysql-asset';
/**
* Run the migrations.
*
@ -15,6 +22,8 @@ class CreateAssetTypesTable extends Migration
{
Schema::create('asset_types', function (Blueprint $table) {
$table->id();
$table->longText('name');
$table->boolean('binaryFormat')->default(false);
$table->timestamps();
});
}

View File

@ -6,6 +6,13 @@ use Illuminate\Support\Facades\Schema;
class CreateAssetsTable extends Migration
{
/**
* The database connection that should be used by the migration.
*
* @var string
*/
protected $connection = 'mysql-asset';
/**
* Run the migrations.
*
@ -13,8 +20,19 @@ class CreateAssetsTable extends Migration
*/
public function up()
{
// TODO: relational genres?
// god i fucking hate foreign keys
Schema::create('assets', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('creatorId');
$table->longText('title');
$table->longText('description');
$table->json('settings');
$table->boolean('commentsEnabled')->default(true);
$table->boolean('purchasable')->default(false);
$table->unsignedBigInteger('assetVersionId');
$table->unsignedBigInteger('assetTypeId');
$table->unsignedBigInteger('assetGenreId');
$table->timestamps();
});
}

View File

@ -6,6 +6,13 @@ use Illuminate\Support\Facades\Schema;
class CreateAssetVersionsTable extends Migration
{
/**
* The database connection that should be used by the migration.
*
* @var string
*/
protected $connection = 'mysql-asset';
/**
* Run the migrations.
*
@ -15,6 +22,8 @@ class CreateAssetVersionsTable extends Migration
{
Schema::create('asset_versions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('assetId');
$table->longText('cdnHash');
$table->timestamps();
});
}

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAssetGenresTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('asset_genres', function (Blueprint $table) {
$table->id();
$table->longText('name');
$table->longText('iconUrl');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('asset_genres');
}
}

View File

@ -1,4 +1,5 @@
{
"/js/app.js": "/js/app.js",
"/js/pages/maintenance.js": "/js/pages/maintenance.js",
"/css/graphictoria.css": "/css/graphictoria.css"
}

95
web/resources/js/pages/Maintenance.js vendored Normal file
View File

@ -0,0 +1,95 @@
// © XlXi 2021
// Graphictoria 5
import axios from 'axios';
import $ from 'jquery';
import * as ReactDOM from 'react-dom';
import React, { useRef, Suspense } from 'react';
import { buildGenericApiUrl } from '../util/HTTP.js';
import { Canvas, useFrame } from '@react-three/fiber';
import { Instances, Instance, PerspectiveCamera, useGLTF } from '@react-three/drei';
const randomVector = (r) => [r / 2 - Math.random() * r, r / 2 - Math.random() * r, r / 2 - Math.random() * r];
const randomEuler = () => [Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI];
const randomData = Array.from({ length: 2000 }, (r = 200) => ({ random: Math.random(), position: randomVector(r), rotation: randomEuler() }));
function Scene() {
const { nodes, materials } = useGLTF('/models/graphictoriapart.glb');
return (
<>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<Instances range={2000} material={materials.Material} geometry={nodes.Cube.geometry} >
{
randomData.map((props, i) => (
<Box key={i} {...props} />
))
}
</Instances>
<Camera makeDefault />
</>
);
}
function Box({ random, ...props }){
const ref = useRef()
useFrame((state) => {
const t = state.clock.getElapsedTime() + random * 10000
ref.current.rotation.set(Math.cos(t / 4) / 2, Math.sin(t / 4) / 2, Math.cos(t / 1.5) / 2)
ref.current.position.y = Math.sin(t / 1.5) / 2
});
return (
<group {...props}>
<Instance ref={ref} />
</group>
)
}
function Camera({ ...props }){
const ref = useRef()
useFrame((state) => {
const t = state.clock.getElapsedTime() / 30
ref.current.position.x = 10 * Math.cos(t);
ref.current.position.y = 4 * Math.sin(t);
ref.current.position.z = 10 * Math.sin(t);
ref.current.lookAt(0, 0, 0);
});
return (
<PerspectiveCamera ref={ref} {...props} />
)
}
let ButtonHistory = []
function attemptBypass() {
axios.post(buildGenericApiUrl('apis', 'v1/maintenance/bypass'), {
'password': $('#gt_mt_buttons > input').val(),
'buttons': ButtonHistory.slice(-40)
})
.then((response) => {
window.location.reload();
});
}
$(document).ready(function() {
ReactDOM.render(
(<Canvas>
<Suspense fallback={null}>
<Scene />
</Suspense>
</Canvas>),
document.getElementsByClassName('gtoria-maintenance-background')[0]
);
$('#gt_mt_buttons').on('click', 'button', function() {
let ButtonId = parseInt(this.getAttribute('name').substr(8)); //gt_mtbtnX
ButtonHistory.push(ButtonId);
attemptBypass();
});
});

16
web/resources/js/util/HTTP.js vendored Normal file
View File

@ -0,0 +1,16 @@
// © XlXi 2021
// Graphictoria 5
const urlObject = new URL(document.location.href);
export function getCurrentDomain() {
return urlObject.hostname.replace(/^[^.]+\./g, '');
};
export function getProtocol() {
return urlObject.protocol;
};
export function buildGenericApiUrl(subdomain, path) {
return `${getProtocol()}//${subdomain}.${getCurrentDomain()}/${path}`;
};

View File

@ -0,0 +1,25 @@
@php
$noFooter = true;
$buttons = str_split('Graphictoria')
@endphp
@extends('layouts.app')
@section('page-specific')
<script src="{{ asset('js/pages/maintenance.js') }}"></script>
@endsection
@section('content')
<div class="gtoria-maintenance-background"></div>
<div class="text-center m-auto container gtoria-maintenance-form">
<h1>Graphictoria is currently under maintenance.</h1>
<h4>Our cyborg team of highly trained code-monkes are working to make Graphictoria better. We'll be back soon!</h4>
<div class="input-group mt-5 d-none d-md-flex" id="gt_mt_buttons">
<input type="password" class="form-control" placeholder="Password" autoComplete="off" />
@foreach($buttons as $index => $button)
<button class="btn btn-secondary" type="button" name="gt_mtbtn{{ $index }}">{{ $button }}</button>
@endforeach
</div>
</div>
@endsection

View File

@ -2,12 +2,6 @@
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AuthController;
use App\Http\Controllers\BannerController;
use App\Http\Controllers\GamesController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\Auth\RegisterController;
use App\Http\Controllers\Controller; // remove this and clean up the code lol
use App\Models\User;
/*

View File

@ -17,4 +17,8 @@ use Illuminate\Support\Facades\Route;
Route::view('/', 'home');
// misc
Route::view('/javascript', 'javascript');
Route::any('/maintenance', 'MaintenanceController@showPage');
Route::view('/javascript', 'javascript');
// client
Route::get('/asset', 'ContentController@fetchAsset');

1
web/webpack.mix.js vendored
View File

@ -14,6 +14,7 @@ require('laravel-mix-banner');
*/
mix.js('resources/js/app.js', 'public/js')
.js('resources/js/pages/maintenance.js', 'public/js/pages')
.react()
.sass('resources/sass/graphictoria.scss', 'public/css')
.banner({