feat: adding ability to optimize and import older site content.

This commit is contained in:
Paul Couture 2023-08-22 16:04:37 -05:00
parent 5c218918ee
commit 9a7aa89362
35 changed files with 1267 additions and 398 deletions

1
.gitignore vendored
View File

@ -20,6 +20,7 @@ public_html/hot
db/data/
db/dump/
static/dist/
static/*
storage/*.key

3
Dockerfile Normal file
View File

@ -0,0 +1,3 @@
FROM shinsenter/laravel:latest
RUN apt update && apt install -y jpegoptim optipng pngquant gifsicle webp libavif-bin

View File

@ -3,7 +3,10 @@ version: '3'
services:
laravel-app:
env_file: .env
image: shinsenter/laravel:latest
build:
context: .
dockerfile: Dockerfile
container_name: ${CONTAINER_NAME:-pcag-laravel}
volumes:
- ./site:/var/www/html
- ./static:/static
@ -16,9 +19,9 @@ services:
DB_DATABASE: ${DB_DATABASE}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
# LARAVEL_QUEUE_ENABLED: true
# LARAVEL_QUEUE_OPTIONS: --timeout=60 --tries=3 redis
# LARAVEL_SCHEDULE_ENABLED: true
LARAVEL_QUEUE_ENABLED: true
LARAVEL_QUEUE_OPTIONS: --timeout=60 --tries=3 redis
LARAVEL_SCHEDULE_ENABLED: true
ports:
- "80:80"
links:
@ -28,7 +31,7 @@ services:
static:
image: nginx:alpine
volumes:
- ./static:/usr/share/nginx/html:ro
- ./static:/usr/share/nginx/html
environment:
TZ: UTC
PUID: ${UID:-1000}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use App\Models\Artwork;
use App\Models\Episode;
class MapLegacyArtworkToLegacyEpisodeCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'naart:map-legacy';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Maps legacy artwork to the legacy episode it related to.';
/**
* Execute the console command.
*/
public function handle()
{
$artworks = Artwork::whereNotNull('legacy_id')->get();
foreach ($artworks as $artwork) {
if ($artwork->id > 76200) {
$legacyDetailResponse = $this->getArtworkInfoFromApi($artwork->legacy_id);
$response = $legacyDetailResponse->object();
if ($response->artwork->episode_id) {
$episode = Episode::where('legacy_id', $response->artwork->episode_id)->first();
if ($episode) {
$artwork->episode_id = $episode->id;
$this->line('Artwork ID ' . $artwork->id . ' is mapped to Episode ID ' . $episode->id);
if ($artwork->isDirty()) {
$this->line('This is a new mapping.');
$artwork->save();
}
}
}
}
}
}
private function getArtworkInfoFromApi($artwork_legacy_id) {
$response = Http::timeout(180)
->get('https://noagendaartgenerator.com/artworkapi/' . $artwork_legacy_id,
[
'p' => '7476',
]
);
return $response;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use App\Models\Artwork;
use App\Models\Episode;
class MapSelectedLegacyArtworkToEpisode extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'naart:map-selected';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Maps the correct artwork selected to the legacy episodes';
/**
* Execute the console command.
*/
public function handle()
{
$episodes = Episode::all();
foreach ($episodes as $episode) {
$this->line('Checking episode ' . $episode->episode_number);
$legacyEpisodeResponse = $this->getEpisodeFromApi($episode->episode_number);
$response = $legacyEpisodeResponse->object();
if ($response->episode->artwork_id && $response->episode->artwork_id + 0 > 0) {
$selectedArtwork = Artwork::where('legacy_id', $response->episode->artwork_id)->first();
if ($selectedArtwork) {
$episode->artwork_id = $selectedArtwork->id;
$this->line('Artwork ID ' . $selectedArtwork->id . ' marked as episode artwork for episode ' . $episode->episode_number);
if ($episode->isDirty()) {
$this->line('This is a new mapping.');
$episode->save();
}
}
}
}
}
private function getEpisodeFromApi($episode_legacy_id) {
$response = Http::timeout(180)
->get('https://noagendaartgenerator.com/episodeapi/' . $episode_legacy_id,
[
'p' => '7476',
]
);
return $response;
}
}

View File

@ -3,13 +3,77 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use App\Models\Artwork;
use App\Models\Artist;
use App\Models\Episode;
class PageController extends Controller
{
public function landing()
{
$user = auth()->user();
return view('home.page');
$headerCounters = $this->getHeaderCounters();
$recentEpisodes = $this->mostRecentEpisodes();
return view('home.page', [
'user' => $user,
'headerCounters' => $headerCounters,
'recentEpisodes' => $recentEpisodes,
]);
}
private function mostRecentEpisodes()
{
$episodes = Cache::remember('latestEpisodes', 30, function() {
return Episode::where('published', true)
->with('podcast')
->with('artwork')
->with('artwork.artist')
->orderBy('episode_date', 'desc')
->limit(10)
->get();
});
return $episodes;
}
private function getHeaderCounters()
{
$headerCounters = [];
$artworkCountNumber = Cache::remember('artworkCountNumber', 10, function() {
return Artwork::all()->count();
});
$artistCountNumber = Cache::remember('artistCountNumber', 10, function() {
return Artist::all()->count();
});
$episodeCountNumber = Cache::remember('episodeCountNumber', 10, function() {
return Episode::all()->count();
});
$headerCounters['Artworks'] = $this->shortNumberCount($artworkCountNumber);
$headerCounters['Artists'] = $this->shortNumberCount($artistCountNumber);
$headerCounters['Episodes'] = $this->shortNumberCount($episodeCountNumber);
return $headerCounters;
}
private function shortNumberCount($number)
{
$units = ['', 'K', 'M', 'B', 'T'];
for ($i = 0; $number >= 1000; $i++) {
$number /= 1000;
}
return [
'number' => $this->numberFormatPrecision($number, 1), //number_format(floatval($number), 1),
'unit' => $units[$i],
];
}
private function numberFormatPrecision($number, $precision = 2, $separator = '.')
{
$numberParts = explode($separator, $number);
$response = $numberParts[0];
if (count($numberParts)>1 && $precision > 0) {
$response .= $separator;
$response .= substr($numberParts[1], 0, $precision);
}
return $response;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
use App\Models\Artist;
use Carbon\Carbon;
use App\Jobs\StashAndOptimizeLegacyArtworkJob;
class ImportLegacyUserJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $user;
/**
* Create a new job instance.
*/
public function __construct($user)
{
$this->user = $user;
}
/**
* Execute the job.
*/
public function handle(): void
{
$name = str_replace(' ', '', $this->user->username);
$email = strtolower(trim($this->user->email));
$email_verified_at = Carbon::parse($this->user->created_at);
$this->createUser($name, $email, $email_verified_at);
}
private function createUser($name, $email, $email_verified_at)
{
$user = User::where('name', $name)->first();
if (!$user) {
$user = User::factory()->state([
'name' => $name,
'email' => $email,
'email_verified_at' => $email_verified_at,
'remember_token' => null,
])->create();
}
$artist = Artist::where('user_id', $user->id)->first();
if (!$artist) {
$artist = Artist::factory()->state([
'user_id' => $user->id,
'name' => $this->user->profile->name,
'avatar' => null,
'header' => null,
'location' => $this->user->profile->location ?? 'No Agenda Art Generator',
'website' => $this->user->profile->website ?? null,
'bio' => null,
])->create();
}
foreach ($this->user->artworks as $artwork) {
StashAndOptimizeLegacyArtworkJob::dispatch($artist, $artwork);
}
}
}

View File

@ -0,0 +1,125 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Intervention\Image\Facades\Image;
use Carbon\Carbon;
use ImageOptimizer;
class StashAndOptimizeLegacyArtworkJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $artist;
protected $artwork;
public $tries = 2;
/**
* Create a new job instance.
*/
public function __construct($artist, $artwork)
{
$this->artist = $artist;
$this->artwork = $artwork;
}
/**
* Execute the job.
*/
public function handle(): void
{
$date = Carbon::parse($this->artwork->created_at);
$basename = $date->format('Y')
. '/' . $date->format('m')
. '/' . Str::slug($this->artist->name)
. '-' . Str::slug($this->artwork->title)
. '_' . $this->artwork->id . '.jpg';
$filename = 'artworks/' . $basename;
$thumbnailName = 'thumbnails/' . $basename;
if (Storage::disk('static')->exists($filename)) {
Log::channel('artwork_import')->error($filename . ' already exists. Filesize: ' . Storage::disk('static')->size($filename));
$this->createArtwork($basename);
return;
}
$img = Image::make('https://noagendaartgenerator.com' . $this->artwork->path . '/' . $this->artwork->filename)
->resize(3000, null, function ($constraint) {
$constraint->aspectRatio();
})
->encode('jpg', 100);
$thumbImg = Image::make('https://noagendaartgenerator.com' . $this->artwork->path . '/' . $this->artwork->filename)
->resize(512, null, function ($constraint) {
$constraint->aspectRatio();
})
->encode('jpg', 100);
$imgLocation = Storage::disk('static')->put($filename, $img);
$thumbLocation = Storage::disk('static')->put($thumbnailName, $thumbImg);
$size_before = Storage::disk('static')->size($filename);
$thumb_size_before = Storage::disk('static')->size($thumbnailName);
ImageOptimizer::optimize(Storage::disk('static')->path($filename));
ImageOptimizer::optimize(Storage::disk('static')->path($thumbnailName));
$size_after = Storage::disk('static')->size($filename);
$thumb_size_after = Storage::disk('static')->size($thumbnailName);
$diff = $size_before - $size_after;
$thumbDiff = $thumb_size_before - $thumb_size_after;
Log::channel('artwork_import')->info('Filesize Before: ' . $size_before);
Log::channel('artwork_import')->info('Filesize After: ' . $size_after);
$perc_smaller = ($diff / $size_before) * 100;
$thumb_perc_smaller = ($thumbDiff / $thumb_size_before) * 100;
Log::channel('artwork_import')->info(number_format($perc_smaller, 2) . '% smaller.');
Log::channel('artwork_import')->info(number_format($thumb_perc_smaller, 2) . '% smaller thumbnail - ' . $thumb_size_after);
Log::channel('artwork_import')->info('Saved and resized ' . $filename);
$this->createArtwork($basename);
}
private function createArtwork($basename) {
$artwork = Artwork::where('legacy_id', $this->artwork->id)->first();
if (!$this->artwork->episode_id) {
$episode = Episode::where('episode_date', '>=', Carbon::parse($this->artwork->created_at)->startOfDay())
->orderBy('episode_date', 'asc')
->first();
} else {
$episode = Episode::where('legacy_id', $this->artwork->episode_id)->first();
}
if (!$artwork) {
$artwork = Artwork::where('filename', $basename)->first();
if ($artwork) {
$artwork->legacy_id = $this->artwork->id;
$artwork->episode_id = $episode->id ?? null;
$artwork->save();
return;
}
}
if (!$artwork) {
Log::channel('artwork_import')->info('making new artwork model for ' . $basename);
Artwork::factory()->state([
'title' => $this->artwork->title,
'artist_id' => $this->artist->id,
'description' => '',
'podcast_id' => 1,
'overlay_id' => $this->artwork->overlay_id,
'episode_id' => $episode->id ?? null,
'filename' => $basename ?? null,
'created_at' => Carbon::parse($this->artwork->created_at),
'updated_at' => Carbon::parse($this->artwork->updated_at),
'legacy_id' => $this->artwork->id,
])->create();
} else {
Log::channel('artwork_import')->info($artwork->id . ' has a model it exists with ' . $artwork->filename);
}
}
}

View File

@ -21,7 +21,7 @@ public function podcast()
public function artwork()
{
return $this->hasOne(Artwork::class);
return $this->hasOne(Artwork::class, 'id', 'artwork_id');
}
public function artist()

View File

@ -11,7 +11,8 @@
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.2",
"laravel/tinker": "^2.8",
"livewire/livewire": "^2.12"
"livewire/livewire": "^2.12",
"spatie/laravel-image-optimizer": "^1.7"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",

579
site/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
<?php
use Spatie\ImageOptimizer\Optimizers\Cwebp;
use Spatie\ImageOptimizer\Optimizers\Gifsicle;
use Spatie\ImageOptimizer\Optimizers\Jpegoptim;
use Spatie\ImageOptimizer\Optimizers\Optipng;
use Spatie\ImageOptimizer\Optimizers\Pngquant;
use Spatie\ImageOptimizer\Optimizers\Svgo;
return [
/*
* When calling `optimize` the package will automatically determine which optimizers
* should run for the given image.
*/
'optimizers' => [
Jpegoptim::class => [
'-m72', // set maximum quality to 85%
'--strip-all', // this strips out all text information such as comments and EXIF data
'--all-progressive', // this will make sure the resulting image is a progressive one
],
Pngquant::class => [
'--force', // required parameter for this package
],
Optipng::class => [
'-i0', // this will result in a non-interlaced, progressive scanned image
'-o2', // this set the optimization level to two (multiple IDAT compression trials)
'-quiet', // required parameter for this package
],
Svgo::class => [
'--disable=cleanupIDs', // disabling because it is know to cause troubles
],
Gifsicle::class => [
'-b', // required parameter for this package
'-O3', // this produces the slowest but best results
],
Cwebp::class => [
'-m 6', // for the slowest compression method in order to get the best compression.
'-pass 10', // for maximizing the amount of analysis pass.
'-mt', // multithreading for some speed improvements.
'-q 90', // quality factor that brings the least noticeable changes.
],
],
/*
* The directory where your binaries are stored.
* Only use this when you binaries are not accessible in the global environment.
*/
'binary_path' => '',
/*
* The maximum time in seconds each optimizer is allowed to run separately.
*/
'timeout' => 60,
/*
* If set to `true` all output of the optimizer binaries will be appended to the default log.
* You can also set this to a class that implements `Psr\Log\LoggerInterface`.
*/
'log_optimizer_activity' => false,
];

View File

@ -65,6 +65,13 @@
'replace_placeholders' => true,
],
'artwork_import' => [
'driver' => 'single',
'path' => storage_path('logs/artwork_import.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),

View File

@ -29,7 +29,7 @@ public function definition()
'header' => fake()->imageUrl(270, 185),
'location' => fake()->city() . ', ' . fake()->state(),
'website' => rand(0, 1) ? fake()->url : null,
'bio' => fake()->paragraphs(rand(1,3, true)),
'bio' => fake()->paragraphs(rand(1, 3), true),
];
}
}

View File

@ -27,14 +27,18 @@ class ArtworkFactory extends Factory
*/
public function definition()
{
$created = fake()->dateTimeThisDecade();
return [
'title' => fake()->name(),
'description' => rand(0, 1) ? fake()->paragraphs(rand((1, 3, true))) : null,
'description' => rand(0, 1) ? fake()->paragraphs(rand(1, 3), true) : null,
'artist_id' => Artist::factory(),
'podcast_id' => Podcast::factory(),
'episode_id' => Episode::factory(),
'overlay_id' => rand(0, 1) ? Overlay::factory() : null,
'filename' => fake()->imageUrl(3000, 3000),
'created_at' => $created,
'updated_at' => $created,
'legacy_id' => null,
];
}
}

View File

@ -2,11 +2,14 @@
namespace Database\Factories;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Artwork;
use App\Models\Overlay;
use Illuminate\Database\Eloquent\Factories\Factory;
use Carbon\Carbon;
use Illuminate\Support\Str;
class EpisodeFactory extends Factory
{
@ -24,11 +27,20 @@ class EpisodeFactory extends Factory
*/
public function definition()
{
$title = fake()->name();
$slug = Str::slug($title);
$created = fake()->dateTimeThisDecade();
return [
'podcast_id' => Podcast::factory(),
'artwork_id' => Artwork::factory(),
'published' => fake()->boolean(),
'title' => fake()->name()
'episode_date' => fake()->dateTimeThisDecade(),
'slug' => $slug,
'title' => $title,
'mp3' => fake()->url(),
'created_at' => $created,
'updated_at' => $created,
'legacy_id' => null,
];
}
}

View File

@ -4,12 +4,23 @@
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use App\Models\Podcast;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Overlay;
class OverlayFactory extends Factory
{
/**
* The name of the factory's corresponding model
*
* @var string
*/
protected $model = Overlay::class;
/**
* Define the model's default state.
*

View File

@ -4,10 +4,21 @@
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Overlay;
class PodcastFactory extends Factory
{
/**
* The name of the factory's corresponding model
*
* @var string
*/
protected $model = Podcast::class;
/**
* Define the model's default state.
*

View File

@ -5,9 +5,22 @@
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Hash;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Overlay;
use App\Models\User;
class UserFactory extends Factory
{
/**
* The name of the factory's corresponding model
*
* @var string
*/
protected $model = User::class;
/**
* Define the model's default state.
*

View File

@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\User;
class CreateArtistsTable extends Migration
{

View File

@ -0,0 +1,28 @@
<?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::table('episodes', function (Blueprint $table) {
$table->decimal('episode_number', 8, 1)->after('episode_date')->default(0);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('episodes', function (Blueprint $table) {
$table->dropColumn('episode_number');
});
}
};

View File

@ -0,0 +1,28 @@
<?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::table('artworks', function (Blueprint $table) {
$table->bigInteger('legacy_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('artworks', function (Blueprint $table) {
$table->dropColumn('legacy_id');
});
}
};

View File

@ -0,0 +1,28 @@
<?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::table('episodes', function (Blueprint $table) {
$table->bigInteger('legacy_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('episodes', function (Blueprint $table) {
$table->dropColumn('legacy_id');
});
}
};

View File

@ -0,0 +1,60 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Episode;
use App\Models\Podcast;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Carbon\Carbon;
class EpisodeSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$current_page = 1;
$response = $this->getResponseFromApi($current_page);
$last_page = $response->object()->last_page;
$this->command->info('Last Page: ' . $last_page);
$podcast = Podcast::find(1);
while ($current_page <= $last_page) {
$this->command->info('Getting Page ' . $current_page);
foreach ($response->object()->data as $episode) {
$podcastEpisode = Episode::where('title', $episode->title)->first();
if (!$podcastEpisode) {
$podcastEpisode = Episode::factory()->state([
'podcast_id' => 1,
'episode_date' => Carbon::parse($episode->show_date),
'published' => (bool)$episode->published,
'artwork_id' => null,
'slug' => $episode->episode_number . '_' . Str::slug($episode->title),
'title' => $episode->title,
'mp3' => $episode->link,
'created_at' => Carbon::parse($episode->created_at),
'updated_at' => Carbon::parse($episode->updated_at),
'legacy_id' => $episode->id ?? null
])->create();
} else {
$podcastEpisode->legacy_id = $episode->id ?? null;
if ($podcastEpisode->isDirty()) {
$podcastEpisode->save();
}
}
$this->command->info('Created ' . $episode->show_date . ' - (' . $episode->episode_number . ') ' . $episode->title);
}
$current_page++;
$response = $this->getResponseFromApi($current_page);
}
}
private function getResponseFromApi($current_page) {
$response = Http::timeout(180)
->get('https://noagendaartgenerator.com/episodesjson?p=7476&page=' . $current_page);
return $response;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Artwork;
use Illuminate\Support\Str;
use Illuminate\Support\Facade\Log;
class FixLegacyEpisodeSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$episodes = Episode::all();
foreach ($episodes as $episode) {
if (is_null($episode->episode_number) || $episode->episode_number == 0) {
$ep_num_arr = explode('_', $episode->slug);
$episode->episode_number = $ep_num_arr[0];
}
if ($episode->isDirty()) {
$episode->save();
}
}
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class MapLegacyIdsSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
}
}

View File

@ -133,6 +133,13 @@ public function run()
$added = '2021-10-29 20:00:00';
$this->createPodcast($name, $description, $website, $slug, $feed, $added);
$name = 'The Boostagram Ball';
$description = 'The First Podcast with Value4Value Music hosted by Adam Curry';
$website = 'https://boostagramball.com';
$slug = 'boostagram-ball';
$feed = 'https://mp3s.nashownotes.com/bballrss.xml';
$added = '2023-07-29 20:00:00';
$this->createPodcast($name, $description, $website, $slug, $feed, $added);
}
private function createPodcast($name, $description, $website, $slug, $feed, $added) {

View File

@ -0,0 +1,105 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
use App\Models\Artist;
use App\Models\Artwork;
use App\Models\Episode;
use App\Models\Podcast;
use App\Models\Overlay;
use App\Jobs\StashAndOptimizeLegacyArtworkJob;
use App\Jobs\ImportLegacyUserJob;
use Carbon\Carbon;
class SeedFromOldApiSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//$this->populateLegacyArtworks();
//die();
$current_page = 283;
$totalArtworks = 0;
$missingArtworks = 0;
$missingModel = 0;
$response = $this->getResponseFromApi($current_page);
$last_page = $response->object()->users->last_page;
while ($current_page <= $last_page) {
$this->command->info('Getting Page ' . $current_page);
foreach ($response->object()->users->data as $user) {
$this->command->line('Getting Art for ' . $user->profile->name . ', found ' . count($user->artworks) . ' artworks.');
$totalArtworks += count($user->artworks);
$legacyUser = Artist::where('name', $user->profile->name)->first();
if (!$legacyUser) {
ImportLegacyUserJob::dispatch($user);
} else {
if ($legacyUser->artworks->count() < count($user->artworks)) {
$countDiff = count($user->artworks) - $legacyUser->artworks->count();
$missingArtworks += $countDiff;
$this->command->comment('Artist ID '
. $legacyUser->id
. ' '
. $legacyUser->name
. ' has '
. $legacyUser->artworks->count()
. ' artworks.');
$this->command->error('Missing ' . $countDiff . ' artworks.');
foreach ($user->artworks as $artwork) {
$date = Carbon::parse($artwork->created_at);
$basename = $date->format('Y')
. '/' . $date->format('m')
. '/' . Str::slug($legacyUser->name)
. '-' . Str::slug($artwork->title)
. '_' . $artwork->id . '.jpg';
if (Storage::disk('static')->exists('artworks/' . $basename)) {
$artworkModel = Artwork::where('filename', $basename)->first();
if (!$artworkModel) {
$missingModel++;
$this->command->error($basename . ' exists.');
StashAndOptimizeLegacyArtworkJob::dispatch($legacyUser, $artwork);
}
}
}
} else {
$this->command->line('Locally stored all of ' . $legacyUser->name . '\'s artworks.');
}
}
}
$current_page++;
$response = $this->getResponseFromApi($current_page);
}
$this->command->info('Total Artworks: ' . $totalArtworks);
$this->command->info('Total Missing: ' . $missingArtworks);
$this->command->info('Total Missing Model: ' . $missingModel);
}
private function getResponseFromApi($current_page) {
$response = Http::timeout(180)
->get('https://noagendaartgenerator.com/artistapi',
[
'p' => '7476',
'page' => $current_page,
]
);
return $response;
}
private function populateLegacyArtworks() {
$artworks = Artwork::whereNull('legacy_id')->get();
foreach ($artworks as $artwork) {
$file = pathinfo($artwork->filename);
$filename = explode('_', $file['filename']);
$legacy_id = end($filename);
$artwork->legacy_id = $legacy_id;
$artwork->save();
}
}
}

View File

@ -64,6 +64,7 @@
align-items: center;
min-height: 100vh;
padding-top: 50px;
padding-bottom: 50px;
@include lg-device {
padding: 180px 0 170px;
min-height: auto;
@ -143,6 +144,7 @@
}
}
// hero banner style two
.hero-banner-style-2 {
.banner-content {

View File

@ -186,7 +186,7 @@ body {
.bg-4,
.bg-3,
.bg-5 {
background-image: none;
background-image: url(../img/headerbg.jpg);
background-color: $body-bg-light;
}

View File

@ -0,0 +1,21 @@
<div class="banner-content">
<h1 class="mb-5 title" data-aos="fade-up">Producing Album Art<br>
<span>Live Since 2010</span>
</h1>
<p data-aos="fade-up" data-aos-delay="100">
A community collaboration producing the best podcast album art in the universe!
</p>
<div class="group-btn mt-8" data-aos="fade-up" data-aos-delay="200">
<a href="explore-filter-sidebar.html" class="btn btn-gradient">
<span><i class="ri-rocket-line"></i>Explore</span>
</a>
<a href="create.html" class="btn btn-outline">
<span><i class="ri-edit-line"></i> Create</span>
</a>
</div>
<div class="counter-wrapper counter-wrapper-style-two">
@foreach ($headerCounters as $headerCounterLabel => $headerCounterCount)
@include('home.hero.counter')
@endforeach
</div>
</div>

View File

@ -0,0 +1,7 @@
<div class="counter-style-1" data-aos="fade-up" data-aos-delay="250">
<div class="d-flex-center">
<div class="number counter-item-active">{{ $headerCounterCount['number'] }}</div>
<div class="count-kilo">{{ $headerCounterCount['unit'] }} <span>+</span></div>
</div>
<div class="counter-title">{{ $headerCounterLabel }}</div>
</div>

View File

@ -0,0 +1,43 @@
<div class="explore-style-one">
<div class="thumb">
<a href="product-details.html"> <img src="{{ 'http://' . config('app.static_asset_url') . '/thumbnails/' . $recentEpisode->artwork->filename ?? '#'}}"
alt="nft live auction thumbnail"></a>
<!-- End .reaction-count -->
</div>
<!-- End .thumb -->
<div class="content">
<div class="header d-flex-between pt-4 pb-1">
<h3 class="title">
<a href="product-details.html">"{{ $recentEpisode->title }}"</a>
</h3>
<div class="more-dropdown "><i class="ri-more-fill" data-bs-toggle="dropdown"></i>
<ul class="dropdown-menu dropdown-menu-dark">
<li><a class="dropdown-item" href="#">View Episode</a></li>
<li><a class="dropdown-item" href="#">View Podcast</a></li>
<li><a class="dropdown-item" href="#">View Show Notes</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item" href="#">View Selected Artist</a></li>
</ul>
</div>
</div>
<!-- .header -->
<div class="product-owner py-1 d-flex-between">
<span class="bid-owner">Artwork Selected For<br>
<strong><a href="#">{{ $recentEpisode->podcast->name }}</a></strong>
<br>
<a href="#">Episode {{ number_format($recentEpisode->episode_number + 0) }}</a></span>
</div>
<div class="product-owner py-1 d-flex-between">
<span class="bid-owner">Artwork By<br><strong><a
href="author-profile.html">{{ $recentEpisode->artwork->artist->name ?? 'Unknown' }}</a></strong></span>
<span class="profile-share d-flex-center"><a href="author-profile.html" class="avatar" data-bs-toggle="tooltip" data-bs-placement="top"
title="{{ $recentEpisode->artwork->artist->name }}"><img src="{{ Vite::asset('resources/img/default_avatars/default_avatar_users_' . str_pad(rand(1, 32), 2, '0', STR_PAD_LEFT) . '.svg') }}"
alt="{{ $recentEpisode->artwork->artist->name }}"></a></span>
</div>
<!-- End .product-owner -->
</div>
<!-- End .content -->
</div>

View File

@ -0,0 +1,7 @@
<div class="slider slider-activation-banner-4 slick-gutter-15 slick-pagination-50">
@foreach($recentEpisodes as $recentEpisode)
@if ($recentEpisode->artwork)
@include('home.hero.slider.slide')
@endif
@endforeach
</div>

View File

@ -6,167 +6,10 @@
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-xl-7 col-lg-6 col-md-12">
<div class="banner-content">
<h1 class="mb-5 title" data-aos="fade-up">Producing Album Art<br>
<span>Live Since 2010</span>
</h1>
<p data-aos="fade-up" data-aos-delay="100">A community collaboration producing the best
<br> album art in the universe!
</p>
<div class="group-btn mt-8" data-aos="fade-up" data-aos-delay="200">
<a href="explore-filter-sidebar.html" class="btn btn-gradient"><span><i class="ri-rocket-line"></i>
Explore</span></a>
<a href="create.html" class="btn btn-outline"><span><i class="ri-edit-line"></i> Create</span></a>
@include('home.hero.banner-left')
</div>
<div class="counter-wrapper counter-wrapper-style-two">
<div class="counter-style-1" data-aos="fade-up" data-aos-delay="250">
<div class="d-flex-center">
<div class="number counter-item-active">28</div>
<div class="count-kilo">K <span>+</span></div>
</div>
<div class="counter-title">Artworks</div>
</div>
<!-- End .single-counter -->
<div class="counter-style-1" data-aos="fade-up" data-aos-delay="300">
<div class="d-flex-center">
<div class="number counter-item-active">2.9</div>
<div class="count-kilo">K <span>+</span></div>
</div>
<div class="counter-title">Artists</div>
</div>
<!-- End .single-counter -->
<div class="counter-style-1" data-aos="fade-up" data-aos-delay="350">
<div class="d-flex-center">
<div class="number counter-item-active">1.6</div>
<div class="count-kilo">K <span>+</span></div>
</div>
<div class="counter-title">Episodes</div>
</div>
<!-- End .single-counter -->
</div>
<!-- End .counter-wrapper -->
</div>
<!-- End banner-content -->
</div>
<!-- End .col -->
<div class="col-xl-5 col-lg-6 col-md-12">
<div class="slider slider-activation-banner-4 slick-gutter-15 slick-pagination-50">
<div class="explore-style-one">
<div class="thumb">
<a href="product-details.html"> <img src="https://noagendaartgenerator.com/assets/artwork/episode/1528/PDzxZp7SxR.png"
alt="nft live auction thumbnail"></a>
<button class="reaction-btn"><i class="ri-heart-fill"></i><span>49</span></button>
<!-- End .reaction-count -->
</div>
<!-- End .thumb -->
<div class="content">
<div class="header d-flex-between pt-4 pb-3">
<h3 class="title"><a href="product-details.html">No Agenda (1528)<br>"HABIDAT"</a></h3>
<div class="more-dropdown "><i class="ri-more-fill" data-bs-toggle="dropdown"></i>
<ul class="dropdown-menu dropdown-menu-dark">
<li><a class="dropdown-item" href="#">New bid</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item" href="#">Refresh Metadata</a></li>
<li><a class="dropdown-item" href="#">Share</a></li>
<li><a class="dropdown-item" href="#">Report</a></li>
</ul>
</div>
</div>
<!-- .header -->
<div class="product-owner py-4 d-flex-between">
<span class="bid-owner">Artwork By<br><strong><a
href="author-profile.html">KorrectDaRekard</a></strong></span>
<span class="profile-share d-flex-center"><a href="author-profile.html" class="avatar" data-bs-toggle="tooltip" data-bs-placement="top"
title="Banuri Bari"><img src="{{ Vite::asset('resources/img/explore/avatar/1.png') }}" alt="Nft_Profile"></a></span>
</div>
<!-- End .product-owner -->
</div>
<!-- End .content -->
</div>
<!-- End explore-style-one -->
<div class="explore-style-one">
<div class="thumb">
<a href="product-details.html"> <img src="https://noagendaartgenerator.com/assets/artwork/episode/1527/obxPmNDIzeE.png"
alt="nft live auction thumbnail"></a>
<button class="reaction-btn"><i class="ri-heart-fill"></i><span>32</span></button>
<!-- End .reaction-count -->
</div>
<!-- End .thumb -->
<div class="content">
<div class="header d-flex-between pt-4 pb-3">
<h3 class="title"><a href="product-details.html">No Agenda (1527)<br>"Grip & Grin"</a></h3>
<div class="more-dropdown "><i class="ri-more-fill" data-bs-toggle="dropdown"></i>
<ul class="dropdown-menu dropdown-menu-dark">
<li><a class="dropdown-item" href="#">New bid</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item" href="#">Refresh Metadata</a></li>
<li><a class="dropdown-item" href="#">Share</a></li>
<li><a class="dropdown-item" href="#">Report</a></li>
</ul>
</div>
</div>
<!-- .header -->
<div class="product-owner py-4 d-flex-between">
<span class="bid-owner">Artwork By<br><strong><a
href="author-profile.html">Capitalist Agenda</a></strong></span>
<span class="profile-share d-flex-center"><a href="author-profile.html" class="avatar" data-bs-toggle="tooltip" data-bs-placement="top"
title="Capitalist Agenda"><img src="{{ Vite::asset('resources/img/avatar/capitalistagenda.png') }}" style="width:100%;height:auto;" alt="Nft_Profile"></a></span>
</div>
<!-- End .product-owner -->
</div>
<!-- End .content -->
</div>
<!-- End explore-style-one -->
<div class="explore-style-one">
<div class="thumb">
<a href="product-details.html"> <img src="https://noagendaartgenerator.com/assets/artwork/episode/1528/PDzxZp7SxR.png"
alt="nft live auction thumbnail"></a>
<button class="reaction-btn"><i class="ri-heart-fill"></i><span>49</span></button>
<!-- End .reaction-count -->
</div>
<!-- End .thumb -->
<div class="content">
<div class="header d-flex-between pt-4 pb-3">
<h3 class="title"><a href="product-details.html">No Agenda<br>"HABIDAT"</a></h3>
<div class="more-dropdown "><i class="ri-more-fill" data-bs-toggle="dropdown"></i>
<ul class="dropdown-menu dropdown-menu-dark">
<li><a class="dropdown-item" href="#">New bid</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item" href="#">Refresh Metadata</a></li>
<li><a class="dropdown-item" href="#">Share</a></li>
<li><a class="dropdown-item" href="#">Report</a></li>
</ul>
</div>
</div>
<!-- .header -->
<div class="product-owner py-4 d-flex-between">
<span class="bid-owner">Artwork By<br><strong><a
href="author-profile.html">KorrectDaRekard</a></strong></span>
<span class="profile-share d-flex-center"><a href="author-profile.html" class="avatar" data-bs-toggle="tooltip" data-bs-placement="top"
title="Banuri Bari"><img src="{{ Vite::asset('resources/img/explore/avatar/1.png') }}" alt="Nft_Profile"></a></span>
</div>
<!-- End .product-owner -->
</div>
<!-- End .content -->
</div>
<!-- End explore-style-one -->
</div>
@include('home.hero.slider.slider')
</div>
<!-- End .col -->
</div>