mirror of
https://github.com/NyaaStudios/nyaabooru.git
synced 2025-12-10 05:42:58 +00:00
add models, setup livewire, setup mongodb
This commit is contained in:
parent
c0590a3412
commit
be4c848eee
27 changed files with 2508 additions and 0 deletions
39
app/Http/Controllers/AuthController.php
Normal file
39
app/Http/Controllers/AuthController.php
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Laravel\Socialite\Facades\Socialite;
|
||||||
|
|
||||||
|
class AuthController extends Controller
|
||||||
|
{
|
||||||
|
public function handleRedirect()
|
||||||
|
{
|
||||||
|
return Socialite::driver('authentik')->redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleCallback()
|
||||||
|
{
|
||||||
|
$user = Socialite::driver('authentik')->user();
|
||||||
|
|
||||||
|
$authUser = User::updateOrCreate(
|
||||||
|
[ 'email' => $user->getEmail() ],
|
||||||
|
[ 'name' => $user->getName() ]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($authUser)
|
||||||
|
{
|
||||||
|
Auth::login($authUser);
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
abort(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleLogout()
|
||||||
|
{
|
||||||
|
Auth::logout();
|
||||||
|
return redirect('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
13
app/Livewire/App/Home.php
Normal file
13
app/Livewire/App/Home.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\App;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Home extends Component
|
||||||
|
{
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.app.home');
|
||||||
|
}
|
||||||
|
}
|
||||||
13
app/Livewire/App/Navbar.php
Normal file
13
app/Livewire/App/Navbar.php
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\App;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Navbar extends Component
|
||||||
|
{
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.app.navbar');
|
||||||
|
}
|
||||||
|
}
|
||||||
64
app/Livewire/Pages/Upload.php
Normal file
64
app/Livewire/Pages/Upload.php
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Pages;
|
||||||
|
|
||||||
|
use App\Models\Post;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Intervention\Image\Laravel\Facades\Image;
|
||||||
|
use Livewire\Attributes\Title;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithFileUploads;
|
||||||
|
|
||||||
|
class Upload extends Component
|
||||||
|
{
|
||||||
|
use WithFileUploads;
|
||||||
|
|
||||||
|
#[Validate('extensions:jpg,jpeg,bmp,gif,png,webp,apng,mp4,wmv,mkv|mimes:jpg,jpeg,bmp,gif,png,webp,apng,mp4,wmv,mkv|max:81920')]
|
||||||
|
public $file;
|
||||||
|
|
||||||
|
#[Validate('required|in:safe,suggestive,explicit')]
|
||||||
|
public $rating = 'safe';
|
||||||
|
|
||||||
|
#[Title('Upload')]
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.pages.upload');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createPost()
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
$author = Auth::user();
|
||||||
|
|
||||||
|
if ($this->file)
|
||||||
|
{
|
||||||
|
$post = Post::create([
|
||||||
|
'extension' => $this->file->getClientOriginalExtension(),
|
||||||
|
'rating' => $this->rating,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($post)
|
||||||
|
{
|
||||||
|
$author->posts()->save($post);
|
||||||
|
|
||||||
|
// Save the full image
|
||||||
|
$this->file->storeAs("posts/$post->id", 'full');
|
||||||
|
$fullImg = Storage::get("posts/$post->id/full");
|
||||||
|
|
||||||
|
// Create thumbnail preview
|
||||||
|
$thumb = Image::read($fullImg)->scaleDown(width: 512, height: 512);
|
||||||
|
Storage::put("posts/$post->id/thumb", $thumb->encodeByExtension($post->extension, quality: 70));
|
||||||
|
|
||||||
|
// Create smaller preview image
|
||||||
|
$preview = Image::read($fullImg)->scaleDown(width: 1280, height: 720);
|
||||||
|
Storage::put("posts/$post->id/preview", $preview->encodeByExtension($post->extension, quality: 70));
|
||||||
|
|
||||||
|
return $this->redirect('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirect('/upload');
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/Livewire/PostFeature.php
Normal file
33
app/Livewire/PostFeature.php
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Post;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class PostFeature extends Component
|
||||||
|
{
|
||||||
|
public ?Post $post = null;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->post = Post::raw(function($collection)
|
||||||
|
{
|
||||||
|
return $collection->aggregate([
|
||||||
|
['$match' => ['featured' => 'on']],
|
||||||
|
['$sample' => ['size' => 1]]
|
||||||
|
]);
|
||||||
|
})->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
if ($this->post == null)
|
||||||
|
{
|
||||||
|
return <<<'HTML'
|
||||||
|
<div></div>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
return view('livewire.post-feature');
|
||||||
|
}
|
||||||
|
}
|
||||||
81
app/Livewire/Posts/Edit.php
Normal file
81
app/Livewire/Posts/Edit.php
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Posts;
|
||||||
|
|
||||||
|
use App\Models\Post;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Edit extends Component
|
||||||
|
{
|
||||||
|
public Post $post;
|
||||||
|
public Collection $tags;
|
||||||
|
public Collection $selectableTags;
|
||||||
|
|
||||||
|
#[Validate('exists:tags,id')]
|
||||||
|
public string $tag = '';
|
||||||
|
|
||||||
|
#[Validate('required|in:safe,suggestive,explicit')]
|
||||||
|
public string $rating = 'unknown';
|
||||||
|
|
||||||
|
public $featured = false;
|
||||||
|
|
||||||
|
public string $deleteTagId = '';
|
||||||
|
|
||||||
|
public function mount(Post $post)
|
||||||
|
{
|
||||||
|
$this->post = $post;
|
||||||
|
$this->tags = $post->tags;
|
||||||
|
$this->rating = $post->rating;
|
||||||
|
$this->featured = $post->featured;
|
||||||
|
$this->selectableTags = Tag::whereDoesntHave('posts', function ($query) {
|
||||||
|
$query->where('id', $this->post->id);
|
||||||
|
})->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.posts.edit')->title("Edit post {$this->post->id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updated()
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
if ($this->tag)
|
||||||
|
{
|
||||||
|
if ($tag = Tag::find($this->tag))
|
||||||
|
{
|
||||||
|
$this->post->tags()->attach($tag);
|
||||||
|
if ($tag->implies)
|
||||||
|
{
|
||||||
|
foreach ($tag->implies as $implied_id)
|
||||||
|
{
|
||||||
|
if ($impliedTag = Tag::find($implied_id))
|
||||||
|
{
|
||||||
|
$this->post->tags()->attach($impliedTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->post->rating = $this->rating;
|
||||||
|
if ($this->post->rating == 'safe')
|
||||||
|
{
|
||||||
|
$this->post->featured = $this->featured;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->post->featured = null;
|
||||||
|
}
|
||||||
|
$this->post->save();
|
||||||
|
|
||||||
|
$this->tags = $this->post->tags;
|
||||||
|
$this->selectableTags = Tag::whereDoesntHave('posts', function ($query) {
|
||||||
|
$query->where('id', $this->post->id);
|
||||||
|
})->get();
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/Livewire/Posts/Image.php
Normal file
27
app/Livewire/Posts/Image.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Posts;
|
||||||
|
|
||||||
|
use App\Models\Post;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Image extends Component
|
||||||
|
{
|
||||||
|
public Post $post;
|
||||||
|
|
||||||
|
public function placeholder()
|
||||||
|
{
|
||||||
|
return <<<'HTML'
|
||||||
|
<div class="wa-stack" style="display: flex; align-items: center; justify-content: center; max-height: 80vh;">
|
||||||
|
<div class="wa-frame wa-border-radius-l" style="max-inline-size: 100%;">
|
||||||
|
<wa-spinner></wa-spinner>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.posts.image');
|
||||||
|
}
|
||||||
|
}
|
||||||
21
app/Livewire/Posts/Index.php
Normal file
21
app/Livewire/Posts/Index.php
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Posts;
|
||||||
|
|
||||||
|
use App\Models\Post;
|
||||||
|
use Livewire\Attributes\Title;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
class Index extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
|
||||||
|
#[Title('Posts')]
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.posts.index', [
|
||||||
|
'posts' => Post::orderBy('created_at', 'desc')->paginate(25),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/Livewire/Posts/Thumbnail.php
Normal file
26
app/Livewire/Posts/Thumbnail.php
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Posts;
|
||||||
|
|
||||||
|
use App\Models\Post;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Thumbnail extends Component
|
||||||
|
{
|
||||||
|
public Post $post;
|
||||||
|
|
||||||
|
public function placeholder()
|
||||||
|
{
|
||||||
|
return <<<'HTML'
|
||||||
|
<div style="display: flex; align-items: center; justify-content: center; width: 256px; height: 256px;">
|
||||||
|
<wa-spinner style="font-size: 4rem;"></wa-spinner>
|
||||||
|
</div>
|
||||||
|
HTML;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.posts.thumbnail');
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Livewire/Posts/View.php
Normal file
22
app/Livewire/Posts/View.php
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Posts;
|
||||||
|
|
||||||
|
use App\Models\Post;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class View extends Component
|
||||||
|
{
|
||||||
|
public Post $post;
|
||||||
|
|
||||||
|
#[Validate('string|max:240')]
|
||||||
|
public $comment = '';
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.posts.view', [
|
||||||
|
// 'comments' => $this->post->comments
|
||||||
|
])->title("Post {$this->post->id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
109
app/Models/Post.php
Normal file
109
app/Models/Post.php
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use MongoDB\Laravel\Eloquent\Model;
|
||||||
|
use MongoDB\Laravel\Relations\BelongsTo;
|
||||||
|
use MongoDB\Laravel\Relations\BelongsToMany;
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
|
||||||
|
class Post extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [ 'rating', 'extension', 'featured' ];
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tags(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Tag::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function toBase64(string $path): string
|
||||||
|
{
|
||||||
|
$ext = $this->extension;
|
||||||
|
$file = Storage::get($path);
|
||||||
|
$data = base64_encode($file);
|
||||||
|
return "data:image/$ext;base64,$data";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getThumbUrl(): ?string
|
||||||
|
{
|
||||||
|
if (Storage::has("posts/$this->id/thumb"))
|
||||||
|
{
|
||||||
|
return $this->toBase64("posts/$this->id/thumb");
|
||||||
|
}
|
||||||
|
return $this->getPreviewUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPreviewUrl(): ?string
|
||||||
|
{
|
||||||
|
if (Storage::has("posts/$this->id/preview"))
|
||||||
|
{
|
||||||
|
return $this->toBase64("posts/$this->id/preview");
|
||||||
|
}
|
||||||
|
return $this->getFullUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFullUrl(): ?string
|
||||||
|
{
|
||||||
|
if (Storage::has("posts/$this->id/full"))
|
||||||
|
{
|
||||||
|
return $this->toBase64("posts/$this->id/full");
|
||||||
|
}
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMimeType(): ?string
|
||||||
|
{
|
||||||
|
return Storage::mimeType("posts/$this->id/full");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDimensions(): false|array
|
||||||
|
{
|
||||||
|
return getimagesize($this->getFullUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAspectRatio(): string
|
||||||
|
{
|
||||||
|
list($width, $height) = $this->getDimensions();
|
||||||
|
$divisor = gmp_intval(gmp_gcd($width, $height));
|
||||||
|
$w = $width / $divisor;
|
||||||
|
$h = $height / $divisor;
|
||||||
|
return "aspect-ratio: $w/$h;";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function download(): StreamedResponse
|
||||||
|
{
|
||||||
|
return Storage::download("posts/$this->id/full");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNextPost(): ?Post
|
||||||
|
{
|
||||||
|
return Post::where('created_at', '>', $this->created_at)
|
||||||
|
->orderBy('created_at', 'asc')
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPreviousPost(): ?Post
|
||||||
|
{
|
||||||
|
return Post::where('created_at', '<', $this->created_at)
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRatingColor(): string
|
||||||
|
{
|
||||||
|
return match ($this->rating)
|
||||||
|
{
|
||||||
|
'safe' => 'success',
|
||||||
|
'suggestive' => 'warning',
|
||||||
|
'explicit' => 'danger',
|
||||||
|
default => 'default',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/Models/Tag.php
Normal file
16
app/Models/Tag.php
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use MongoDB\Laravel\Eloquent\Model;
|
||||||
|
use MongoDB\Laravel\Relations\BelongsToMany;
|
||||||
|
|
||||||
|
class Tag extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [ 'name', 'slug', 'implies' ];
|
||||||
|
|
||||||
|
public function posts(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Post::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
config/image.php
Normal file
46
config/image.php
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Image Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Intervention Image supports “GD Library” and “Imagick” to process images
|
||||||
|
| internally. Depending on your PHP setup, you can choose one of them.
|
||||||
|
|
|
||||||
|
| Included options:
|
||||||
|
| - \Intervention\Image\Drivers\Gd\Driver::class
|
||||||
|
| - \Intervention\Image\Drivers\Imagick\Driver::class
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'driver' => \Intervention\Image\Drivers\Imagick\Driver::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Configuration Options
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These options control the behavior of Intervention Image.
|
||||||
|
|
|
||||||
|
| - "autoOrientation" controls whether an imported image should be
|
||||||
|
| automatically rotated according to any existing Exif data.
|
||||||
|
|
|
||||||
|
| - "decodeAnimation" decides whether a possibly animated image is
|
||||||
|
| decoded as such or whether the animation is discarded.
|
||||||
|
|
|
||||||
|
| - "blendingColor" Defines the default blending color.
|
||||||
|
|
|
||||||
|
| - "strip" controls if meta data like exif tags should be removed when
|
||||||
|
| encoding images.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'options' => [
|
||||||
|
'autoOrientation' => true,
|
||||||
|
'decodeAnimation' => true,
|
||||||
|
'blendingColor' => 'ffffff',
|
||||||
|
'strip' => false,
|
||||||
|
]
|
||||||
|
];
|
||||||
31
database/migrations/2025_05_21_053453_create_posts_table.php
Normal file
31
database/migrations/2025_05_21_053453_create_posts_table.php
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use MongoDB\Laravel\Schema\Blueprint;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('posts', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->enum('rating', ['unknown', 'safe', 'suggestive', 'explicit'])->default('unknown');
|
||||||
|
$table->string('extension')->nullable();
|
||||||
|
$table->boolean('featured')->default(false);
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('posts');
|
||||||
|
}
|
||||||
|
};
|
||||||
27
database/migrations/2025_05_21_173221_create_tags_table.php
Normal file
27
database/migrations/2025_05_21_173221_create_tags_table.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('tags', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('tags');
|
||||||
|
}
|
||||||
|
};
|
||||||
1675
package-lock.json
generated
Normal file
1675
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
26
resources/views/components/layouts/app.blade.php
Normal file
26
resources/views/components/layouts/app.blade.php
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="wa-dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
<script type="module" src="https://early.webawesome.com/webawesome@3.0.0-alpha.13/dist/webawesome.loader.js" data-fa-kit-code="ba9cf75857"></script>
|
||||||
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
|
@livewireStyles
|
||||||
|
<title>{{ $title ?? 'Untitled' }} ~ {{ config('app.name') }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<wa-page mobile-breakpoint="920">
|
||||||
|
<header slot="header">
|
||||||
|
@livewire('app.navbar')
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{{ $slot }}
|
||||||
|
</main>
|
||||||
|
</wa-page>
|
||||||
|
|
||||||
|
@stack('modals')
|
||||||
|
@livewireScripts
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
resources/views/livewire/app/home.blade.php
Normal file
17
resources/views/livewire/app/home.blade.php
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<div class="wa-stack wa-gap-3xl">
|
||||||
|
<div class="wa-grid">
|
||||||
|
{{-- <h1 class="wa-cluster">--}}
|
||||||
|
{{-- <wa-icon name="paw-simple"></wa-icon>--}}
|
||||||
|
{{-- {{ config('app.name') }}--}}
|
||||||
|
{{-- </h1>--}}
|
||||||
|
<h1>{{ config('app.name') }}</h1>
|
||||||
|
|
||||||
|
@auth
|
||||||
|
<wa-input placeholder="Search for posts, tags, users, etc.">
|
||||||
|
<wa-icon slot="prefix" name="magnifying-glass"></wa-icon>
|
||||||
|
</wa-input>
|
||||||
|
@endauth
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<livewire:post-feature />
|
||||||
|
</div>
|
||||||
42
resources/views/livewire/app/navbar.blade.php
Normal file
42
resources/views/livewire/app/navbar.blade.php
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<div class="wa-split">
|
||||||
|
<div class="wa-cluster wa-align-items-center">
|
||||||
|
<wa-icon-button
|
||||||
|
href="{{ route('home') }}"
|
||||||
|
name="paw-simple"
|
||||||
|
appearance="plain"
|
||||||
|
style="font-size: 1.5rem;"
|
||||||
|
wire:navigate.hover>
|
||||||
|
</wa-icon-button>
|
||||||
|
|
||||||
|
@auth
|
||||||
|
<wa-button appearance="plain" href="{{ route('posts.home') }}" wire:navigate.hover>
|
||||||
|
<wa-icon slot="prefix" name="images"></wa-icon>
|
||||||
|
Posts
|
||||||
|
</wa-button>
|
||||||
|
|
||||||
|
<wa-button appearance="plain" href="{{ route('upload') }}" wire:navigate.hover>
|
||||||
|
<wa-icon slot="prefix" name="arrow-up-from-bracket"></wa-icon>
|
||||||
|
Upload
|
||||||
|
</wa-button>
|
||||||
|
|
||||||
|
<wa-button appearance="plain">
|
||||||
|
<wa-icon slot="prefix" name="tags"></wa-icon>
|
||||||
|
Tags
|
||||||
|
</wa-button>
|
||||||
|
@endauth
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wa-cluster">
|
||||||
|
@guest
|
||||||
|
<wa-button href="{{ route('login') }}" appearance="plain">
|
||||||
|
<wa-icon slot="prefix" name="arrow-right-to-bracket"></wa-icon>
|
||||||
|
Sign in
|
||||||
|
</wa-button>
|
||||||
|
@endguest
|
||||||
|
|
||||||
|
@auth
|
||||||
|
<wa-button appearance="plain">{{ Auth::user()->name }}</wa-button>
|
||||||
|
<wa-icon-button href="{{ route('logout') }}" appearance="plain" name="arrow-left-from-bracket"></wa-icon-button>
|
||||||
|
@endauth
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
23
resources/views/livewire/pages/upload.blade.php
Normal file
23
resources/views/livewire/pages/upload.blade.php
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<div class="wa-stack wa-gap-3xl">
|
||||||
|
<h1>Upload</h1>
|
||||||
|
|
||||||
|
<form wire:submit="createPost" class="wa-stack wa-gap-xl">
|
||||||
|
<wa-card>
|
||||||
|
<input wire:model="file" type="file" label="file" placeholder="Select a file to upload." />
|
||||||
|
@error('file')
|
||||||
|
<span class="wa-caption-m">{{ $message }}</span>
|
||||||
|
@enderror
|
||||||
|
</wa-card>
|
||||||
|
|
||||||
|
<wa-select wire:model="rating" label="Rating" value="safe" hint="Select a content rating that matches the file.">
|
||||||
|
<wa-option value="safe">Safe</wa-option>
|
||||||
|
<wa-option value="suggestive">Suggestive</wa-option>
|
||||||
|
<wa-option value="explicit">Explicit</wa-option>
|
||||||
|
</wa-select>
|
||||||
|
|
||||||
|
<wa-button type="submit" variant="brand" wire:loading.attr="disabled">
|
||||||
|
<wa-icon slot="prefix" name="arrow-up-from-bracket"></wa-icon>
|
||||||
|
Upload
|
||||||
|
</wa-button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
6
resources/views/livewire/post-feature.blade.php
Normal file
6
resources/views/livewire/post-feature.blade.php
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<div class="wa-stack" style="max-height: 80vh;">
|
||||||
|
<a href="{{ url("posts/$post->id") }}" class="wa-frame wa-border-radius-l" style="max-inline-size: 100%; {{ $post->getAspectRatio() }}" wire:navigate.hover>
|
||||||
|
<img src="{{ $post->getPreviewUrl() }}" />
|
||||||
|
</a>
|
||||||
|
<span class="wa-caption-m">-{{ $post->user->name }}, <wa-format-date></wa-format-date></span>
|
||||||
|
</div>
|
||||||
49
resources/views/livewire/posts/edit.blade.php
Normal file
49
resources/views/livewire/posts/edit.blade.php
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<div class="wa-flank wa-align-items-start wa-gap-3xl" style="--flank-size: 20rem;">
|
||||||
|
|
||||||
|
{{-- Sidebar --}}
|
||||||
|
<div class="wa-stack" wire:poll>
|
||||||
|
|
||||||
|
<div class="wa-cluster">
|
||||||
|
<span class="wa-caption-m">Changes are automatically saved.</span>
|
||||||
|
<wa-button href="{{ url("posts/$post->id") }}" appearance="outlined" variant="neutral" size="small" wire:navigate.hover>
|
||||||
|
<wa-icon slot="prefix" name="check"></wa-icon>
|
||||||
|
<span>Finish</span>
|
||||||
|
</wa-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<wa-divider></wa-divider>
|
||||||
|
|
||||||
|
<form wire:submit class="wa-stack">
|
||||||
|
<wa-select wire:model.live="rating" label="Rating" value="{{ $post->rating }}" wire:loading.attr="disabled" @error('rating') hint="{{ $message }}" @enderror>
|
||||||
|
<wa-option value="safe">Safe</wa-option>
|
||||||
|
<wa-option value="suggestive">Suggestive</wa-option>
|
||||||
|
<wa-option value="explicit">Explicit</wa-option>
|
||||||
|
</wa-select>
|
||||||
|
|
||||||
|
@if ($post->rating == 'safe')
|
||||||
|
<wa-switch
|
||||||
|
wire:model.live="featured"
|
||||||
|
{{ $post->featured ? 'checked' : '' }}
|
||||||
|
wire:loading.attr="disabled"
|
||||||
|
hint="Featured posts will be randomly selected to show on the front page.">
|
||||||
|
Feature post
|
||||||
|
</wa-switch>
|
||||||
|
@endif
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<wa-divider></wa-divider>
|
||||||
|
|
||||||
|
{{-- Tags --}}
|
||||||
|
<div class="wa-cluster wa-heading-m">
|
||||||
|
<wa-icon fixed-width name="tags"></wa-icon>
|
||||||
|
<span>Tags</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Main content --}}
|
||||||
|
<div class="wa-stack wa-gap-2xl" wire:poll>
|
||||||
|
<livewire:posts.image :$post lazy />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
5
resources/views/livewire/posts/image.blade.php
Normal file
5
resources/views/livewire/posts/image.blade.php
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="wa-stack" style="display: flex; align-items: center; justify-content: center; max-height: 80vh;">
|
||||||
|
<div class="wa-frame wa-border-radius-l" style="max-inline-size: 100%; {{ $post->getAspectRatio() }}">
|
||||||
|
<img src="{{ $post->getPreviewUrl() }}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
8
resources/views/livewire/posts/index.blade.php
Normal file
8
resources/views/livewire/posts/index.blade.php
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="wa-stack wa-gap-3xl">
|
||||||
|
<h1>Posts</h1>
|
||||||
|
<div class="wa-cluster wa-gap-s">
|
||||||
|
@foreach ($posts as $post)
|
||||||
|
<livewire:posts.thumbnail :$post lazy />
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
10
resources/views/livewire/posts/thumbnail.blade.php
Normal file
10
resources/views/livewire/posts/thumbnail.blade.php
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<div style="max-inline-size: 256px;">
|
||||||
|
<a
|
||||||
|
id="post_{{ $post->id }}"
|
||||||
|
href="{{ url("posts/$post->id") }}"
|
||||||
|
class="wa-frame wa-border-radius-l"
|
||||||
|
style="border: 2px solid var(--wa-color-{{ $post->getRatingColor() }}-border-loud);"
|
||||||
|
wire:navigate.hover>
|
||||||
|
<img src="{{ $post->getThumbUrl() }}" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
72
resources/views/livewire/posts/view.blade.php
Normal file
72
resources/views/livewire/posts/view.blade.php
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
<div class="wa-flank wa-align-items-start wa-gap-3xl" style="--flank-size: 20rem;">
|
||||||
|
|
||||||
|
{{-- Sidebar --}}
|
||||||
|
<div class="wa-stack" wire:poll>
|
||||||
|
|
||||||
|
{{-- Post navigation --}}
|
||||||
|
<div class="wa-cluster">
|
||||||
|
@if ($prev = $post->getPreviousPost())
|
||||||
|
<wa-icon-button href="{{ url("posts/$prev->id") }}" name="arrow-left" style="color: var(--wa-color-text-link);" wire:navigate.hover></wa-icon-button>
|
||||||
|
@else
|
||||||
|
<wa-icon-button name="arrow-left" disabled></wa-icon-button>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($next = $post->getNextPost())
|
||||||
|
<wa-icon-button href="{{ url("posts/$next->id") }}" name="arrow-right" style="color: var(--wa-color-text-link);" wire:navigate.hover></wa-icon-button>
|
||||||
|
@else
|
||||||
|
<wa-icon-button name="arrow-right" disabled></wa-icon-button>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<wa-icon-button href="{{ url("posts/$post->id/edit") }}" name="file-pen" style="color: var(--wa-color-text-link);" wire:navigate.hover></wa-icon-button>
|
||||||
|
<wa-icon-button href="{{ url("posts/$post->id/download") }}" name="download" style="color: var(--wa-color-text-link);"></wa-icon-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<wa-divider></wa-divider>
|
||||||
|
|
||||||
|
{{-- Post ID --}}
|
||||||
|
<div class="wa-cluster">
|
||||||
|
<wa-icon fixed-width name="hashtag"></wa-icon>
|
||||||
|
<span>{{ $post->id }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Post author --}}
|
||||||
|
<div class="wa-cluster">
|
||||||
|
<wa-icon fixed-width name="user"></wa-icon>
|
||||||
|
<span>{{ $post->user->name }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Post upload date --}}
|
||||||
|
<div class="wa-cluster">
|
||||||
|
<wa-icon fixed-width name="calendar"></wa-icon>
|
||||||
|
<wa-format-date
|
||||||
|
month="numeric"
|
||||||
|
day="numeric"
|
||||||
|
year="numeric"
|
||||||
|
hour="numeric"
|
||||||
|
minute="numeric"
|
||||||
|
date="{{ $post->created_at }}">
|
||||||
|
</wa-format-date>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Post rating --}}
|
||||||
|
<div class="wa-cluster">
|
||||||
|
<wa-icon fixed-width name="face-hand-peeking"></wa-icon>
|
||||||
|
<span style="color: var(--wa-color-{{ $post->getRatingColor() }}-on-normal);">{{ $post->rating }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<wa-divider></wa-divider>
|
||||||
|
|
||||||
|
{{-- Tags --}}
|
||||||
|
<div class="wa-cluster wa-heading-m">
|
||||||
|
<wa-icon fixed-width name="tags"></wa-icon>
|
||||||
|
<span>Tags</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Main content --}}
|
||||||
|
<div class="wa-stack wa-gap-2xl" wire:poll>
|
||||||
|
<livewire:posts.image :$post lazy />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
7
routes/auth.php
Normal file
7
routes/auth.php
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::get('login', 'handleRedirect')->name('login');
|
||||||
|
Route::get('callback', 'handleCallback');
|
||||||
|
Route::get('logout', 'handleLogout')->name('logout');
|
||||||
Loading…
Add table
Add a link
Reference in a new issue