Merge MVC rewrite into master (#21)

* Just commit it all

* Require auth

* crap

* Update homepage

* Block AI scrapers

* Update cache update script

* Add dummy file

* Remove unnecessary lastfm config var

* Use withQueryParameters for LastFM API

* Fix embeds

* Update example env

* Smard
This commit is contained in:
Frankie B 2024-06-11 18:02:01 +01:00 committed by GitHub
commit c9299b5410
88 changed files with 1982 additions and 1661 deletions

View file

@ -0,0 +1,15 @@
<?php
namespace App\Http\Controllers;
use App\Models\BookmarkCategory;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
class AdminBookmarksController extends Controller
{
public function show() : View {
$categories = BookmarkCategory::with('sites')->get();
return view('admin.bookmarks', compact('categories'));
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers;
use App\Models\GuestbookEntry;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
use UAParser\Parser;
class AdminGuestbookController extends Controller
{
function getGuestbookUniqueAddr(): int {
$uniqueIpsCount = DB::table('guestbook__entries')->distinct()->count('ip');
return $uniqueIpsCount;
}
function getGuestbookEntriesCount(): int {
$entryCount = DB::table('guestbook__entries')->count();
return $entryCount;
}
public function show() : View {
$guestbook_unique_addr = $this->getGuestbookUniqueAddr();
$guestbook_entry_count = $this->getGuestbookEntriesCount();
$entries = GuestbookEntry::selectEntries();
$parser = Parser::create();
return view('admin.guestbook', [
'guestbook_unique_addr' => $guestbook_unique_addr,
'guestbook_entry_count' => $guestbook_entry_count,
'entries' => $entries,
'parser' => $parser,
]);
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers;
use App\Models\BookmarkCategory;
use App\Models\BookmarkSite;
use App\Models\GuestbookEntry;
use Exception;
use Illuminate\Http\Request;
use Illuminate\View\View;
class AdminImportController extends Controller
{
public function show() : View {
return view('admin.import');
}
public function submit(Request $request)
{
$request->validate([
'data_file' => 'required|mimes:json',
]);
$file = $request->file('data_file');
$jsonContent = file_get_contents($file->getRealPath());
$data = json_decode($jsonContent, true);
$tables = [];
foreach($data as $item) {
if ($item['type'] !== "table") continue;
$tables[$item['name']] = [
'data' => $item['data'],
'count' => count($item['data'])
];
if ($item['name'] === "guestbook__entries") {
GuestbookEntry::importGuestbookEntry($item['data']);
}
$this->import($item['data'], $item['name']);
}
return view('admin.import-success', ['tables' => $tables]);
}
/**
* Imports the given data to the specified table
*
* @param array $data The data to import
* @param string $table_name The name of the table to import to
* @return void
* @throws Exception Invalid table specified, to be replaced with custom exception
*/
public function import(array $data, string $table_name): void {
switch ($table_name) {
case 'guestbook__entries':
GuestbookEntry::importGuestbookEntry($data);
break;
case 'bookmark__categories' :
BookmarkCategory::importBookmarkCategory($data);
break;
case 'bookmark__sites':
BookmarkSite::importBookmark($data);
break;
case 'guestbook__bans':
break;
default:
// TODO: Replace with custom exception
throw new Exception("Invalid table specified ($table_name)");
}
}
}

View file

@ -0,0 +1,15 @@
<?php
namespace App\Http\Controllers;
use App\Models\BookmarkSite;
use App\Models\BookmarkCategory;
use Illuminate\View\View;
class BookmarksController extends Controller
{
public function show() : View {
$categories = BookmarkCategory::with('sites')->get();
return view('bookmarks', compact('categories'));
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\View\View;
class CalculatorsController extends Controller
{
public function show() : View {
return view('calculators');
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\View\View;
class ComputersController extends Controller
{
public function show() : View {
return view('computers');
}
}

View file

@ -2,37 +2,46 @@
namespace App\Http\Controllers;
use App\Models\GuestbookEntry;
use Illuminate\Http\Request;
use DB;
use Illuminate\Http\RedirectResponse;
use Illuminate\Contracts\View\View;
use Illuminate\Validation\ValidationException;
use UAParser\Parser;
class GuestbookController extends Controller {
public function guestbook() {
return view('pages.guestbook');
public function show(): View {
$entries = GuestbookEntry::selectEntries();
$parser = Parser::create();
return view('guestbook')
->with('entries', $entries)
->with('parser', $parser);
}
public function guestbookPost(Request $request) {
/**
* Creates a new guestbook entry
*
* @param Request $request
* @return RedirectResponse
* @throws ValidationException
*/
public function addEntry(Request $request): RedirectResponse {
$this->validate($request, [
'name' => 'required',
'message' => 'required'
]);
$matching_bans = DB::select('SELECT reason FROM guestbook__bans WHERE ip_address = ?', array($request->ip()));
if (!empty($matching_bans)) {
return view('errors.guestbook-ipban')->with('reason', $matching_bans[0]->reason);
}
DB::insert(
'INSERT INTO guestbook__entries (name, timestamp, ip_address, agent, message) values (?, ?, ?, ?, ?)',
[
htmlspecialchars($request->get('name')),
time(),
$request->ip(),
$request->userAgent(),
htmlspecialchars($request->get('message'))
]
);
GuestbookEntry::insertGuestbookEntry($request);
return back()->with('success', 'Entry submitted successfully!');
}
public function banIP(string $addr) {
// TODO: Add banning system
// $matching_bans = DB::select('SELECT reason FROM guestbook__bans WHERE ip_address = ?', array($request->ip()));
// if (!empty($matching_bans)) {
// return view('errors.guestbook-ipban')->with('reason', $matching_bans[0]->reason);
// }
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers;
use Illuminate\View\View;
use DateTime;
class HomeController extends Controller
{
/**
* Returns age based on birthday date and current date (GMT)
* @return int
*/
function returnAge(): int
{
date_default_timezone_set('Europe/London');
$birthday = new DateTime("2005-06-07");
$currentDate = DateTime::createFromFormat("Y-m-d", date("Y-m-d"));
$age = $birthday->diff($currentDate);
return $age->y;
}
/**
* Shows home page
* @return View
*/
public function show() : View {
return view('home', [
'age' => $this->returnAge()
]);
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Http;
use Illuminate\View\View;
class MusicController extends Controller
{
public function getCurrentTrack() {
// If it's already cached just return that
if (Cache::has('current_track')) {
return Cache::get('current_track');
}
$response = Http::withQueryParameters([
'method' => 'user.getrecenttracks',
'user' => Config::get('services.lastfm.user'),
'format' => 'json',
'nowplaying' => 'true',
'api_key' => Config::get('services.lastfm.key')
])->get('https://ws.audioscrobbler.com/2.0/');
$data = $response->json();
error_log($response->body());
$track_data = $data["recenttracks"]["track"][0];
$current_track = [
'title' => $track_data["name"],
'artist' => $track_data["artist"]["#text"],
'url' => $track_data["url"],
];
Cache::put('current_track', $current_track, now()->addSeconds(15));
return $current_track;
}
public function getTopTracks() {
// If it's already cached just return that
if (Cache::has('top_tracks')) {
return Cache::get('top_tracks');
}
$response = Http::withQueryParameters([
'method' => 'user.gettoptracks',
'user' => Config::get('services.lastfm.user'),
'format' => 'json',
'period' => '1month',
'limit' => 10,
'api_key' => Config::get('services.lastfm.key')
])->get('https://ws.audioscrobbler.com/2.0/');
$data = $response->json();
$topTracks = [];
foreach ($data["toptracks"]["track"] as $track) {
$topTracks[] = [
'title' => $track["name"],
'artist' => $track["artist"]["name"],
'url' => $track["url"],
'plays' => $track["playcount"],
];
}
Cache::put('top_tracks', $topTracks, now()->addSeconds(15));
return $topTracks;
}
public function show() : View {
return view('music')
->with('current_track', $this->getCurrentTrack())
->with('top_tracks', $this->getTopTracks());
}
}

View file

@ -16,6 +16,9 @@ class RateLimiter
*/
public function handle(Request $request, Closure $next): Response
{
if (auth()->check()) {
return $next($request);
}
$ipAddress = $request->ip();
$cacheKey = 'rate_limit_'.$ipAddress;

View file

@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class BookmarkCategory extends Model
{
use HasFactory;
protected $table = "bookmark__categories";
protected $fillable = ['name'];
public function sites() {
return $this->hasMany(BookmarkSite::class, 'category');
}
public static function insertBookmarkCategory(string $name) {
$newBookmarkCategory = new BookmarkCategory;
$newBookmarkCategory->name = $name;
$newBookmarkCategory->save();
}
public static function selectBookmarks(int $id) {
$bookmarks = BookmarkSite::where('category', '=', $id)->firstOrFail();
return $bookmarks;
}
public static function importBookmarkCategory(array $data) {
foreach ($data as $category) {
$newBookmarkCategory = new BookmarkCategory;
$newBookmarkCategory->name = $category['name'];
$newBookmarkCategory->priority = intval($category['priority']);
$newBookmarkCategory->save();
}
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class BookmarkSite extends Model {
use HasFactory;
protected $table = "bookmark__sites";
protected $fillable = ['name', 'description', 'url', 'category'];
public function category() {
return $this->belongsTo(BookmarkCategory::class, 'category');
}
public static function insertBookmark(string $name, string $url, int $category) {
$category = BookmarkCategory::where('id', $category)->firstOrFail();
$newBookmark = new BookmarkSite;
$newBookmark->name = $name;
$newBookmark->url = $url;
$newBookmark->category = $category->id;
$newBookmark->save();
}
public static function importBookmark(array $data) {
foreach ($data as $site) {
$newBookmark = new BookmarkSite;
$newBookmark->name = $site['name'];
$newBookmark->description = $site['description'];
$newBookmark->url = $site['url'];
$newBookmark->category = $site['category_id'];
$newBookmark->save();
}
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace App\Models;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class GuestbookEntry extends Model
{
use HasFactory;
protected $table = "guestbook__entries";
protected $fillable = ['name', 'message'];
/**
* Creates a new guestbook entry.
*
* @param Request $request The HTTP POST request
* @return void
*/
public static function insertGuestbookEntry(Request $request) {
$newEntry = new GuestbookEntry;
$newEntry->name = htmlspecialchars($request->get('name'));
$newEntry->message = htmlspecialchars($request->get('message'));
$newEntry->ip = $request->ip();
$newEntry->agent = $request->userAgent();
$newEntry->admin = auth()->check();
$newEntry->save();
}
public static function selectEntries() {
$entries = GuestbookEntry::orderBy('created_at', 'desc')->get();
return $entries;
}
public static function importGuestbookEntry(array $data) {
foreach ($data as $entry) {
$dt = new \DateTime('@' . $entry['timestamp']);
$newEntry = new GuestbookEntry;
$newEntry->name = $entry['name'];
$newEntry->ip = $entry['ip_address'];
$newEntry->agent = $entry['agent'];
$newEntry->admin = $entry['site_owner'];
$newEntry->message = $entry['message'];
$newEntry->created_at = $dt;
$newEntry->updated_at = $dt;
$newEntry->save();
}
}
}

View file

@ -1,45 +0,0 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class CurrentTrack extends Component
{
public $track;
/**
* Create a new component instance.
*/
public function __construct($track)
{
$this->track = $track;
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.current-track');
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Layout extends Component
{
/**
* Create a new component instance.
*/
public function __construct()
{
//
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.layout');
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Navbar extends Component
{
public $title;
/**
* Create a new component instance.
*/
public function __construct($title)
{
$this->title = $title;
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.navbar');
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class TopTracks extends Component
{
public $tracks;
/**
* Create a new component instance.
*/
public function __construct($tracks)
{
$this->tracks = $tracks;
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.top-tracks');
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Track extends Component
{
public $track;
public $count;
/**
* Create a new component instance.
*/
public function __construct($track, $count)
{
$this->track = $track;
$this->count = $count;
}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.track');
}
}