Merge pull request 'add: image and blog import' (#14) from feature/import-blog-and-images into dev
Reviewed-on: https://codeberg.org/headshed/share-lt/pulls/14
This commit is contained in:
commit
d9dca29bc9
5 changed files with 235 additions and 2 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -2,6 +2,18 @@
|
|||
|
||||
## 2026-01-08
|
||||
|
||||
added text widgets
|
||||
|
||||
added categories
|
||||
|
||||
updated api to access text widgets and categories
|
||||
|
||||
added url and call to action for entries for use in cards
|
||||
|
||||
added import image and blogs import commands
|
||||
|
||||
## 2026-01-08
|
||||
|
||||
added tags to entry model
|
||||
|
||||
added text widget and category
|
||||
|
|
|
|||
144
app/Console/Commands/ImportBlogs.php
Normal file
144
app/Console/Commands/ImportBlogs.php
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Models\Entry;
|
||||
|
||||
class ImportBlogs extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:import-blogs';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
/*
|
||||
|
||||
{
|
||||
"id": 7,
|
||||
"title": "Low/No Code FlowiseAI",
|
||||
"slug": "lowno-code-flowiseai",
|
||||
"content": "<p><img alt=\"\" src=\"http://127.0.0.1:8000/storage/99/ZsTHWAQRbdvgRUEwGmCIsm4TyChIjBwiY71VmnnR.webp\"/></p>\n<p>In <a href=\"https://flowiseai.com/\">FlowiseAI</a>, applications based on the JavaScript fork of LangChain are modeled in a 'no/low-code' environment. If you are coming from a closed source world, yet trying to implement devops principles this may fill you with fear, dread and uncertainty. FlowiseAI offers the advantage of using plain-text JSON files to represent each workflow. These files are easy to understand, open, and readily backup-able, unlike opaque proprietary binary formats\nThe data used at runtime and other component prerequisites like credentials are stored in the FlowiseAI data volume, which looks like this</p>\n<p><code>bash\nMode LastWriteTime ..... 8ZS1C0ZBB.webp",
|
||||
"created_at": "2024-12-17 11:24:59",
|
||||
"updated_at": "2024-12-17 12:53:54",
|
||||
"category_id": 1,
|
||||
"blog_date": "2024-02-26 00:00",
|
||||
"is_featured": 0,
|
||||
"published": 1
|
||||
},
|
||||
|
||||
*/
|
||||
|
||||
$filePath = '/home/user/projects/laravel/12/media_library/boring-astro-static/imported_database/blogs.json';
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
$this->error("File not found: $filePath");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$jsonContent = file_get_contents($filePath);
|
||||
$blogs = json_decode($jsonContent, true);
|
||||
|
||||
if (!$blogs) {
|
||||
$this->error("Could not parse JSON file: $filePath");
|
||||
return 1;
|
||||
}
|
||||
|
||||
foreach ($blogs as $blog) {
|
||||
// Only process the blog with ID 51
|
||||
if (($blog['id'] ?? null) !== 51) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$slug = $blog['slug'] ?? null;
|
||||
$this->info("Processing blog ID: {$blog['id']} with slug: {$slug}");
|
||||
// Check if the entry already exists
|
||||
$existingEntry = Entry::where('slug', $slug)->first();
|
||||
|
||||
if ($existingEntry) {
|
||||
// Update existing entry with cleaned content
|
||||
$existingEntry->update([
|
||||
'content' => $this->cleanHtmlForFilament($blog['content'] ?? ''),
|
||||
'description' => $this->extractPlainTextFromHtml($blog['content'] ?? ''),
|
||||
]);
|
||||
$this->info("Updated content for: {$existingEntry->title}");
|
||||
} else {
|
||||
// Create new entry
|
||||
Entry::create([
|
||||
'title' => $blog['title'] ?? null,
|
||||
'slug' => $slug,
|
||||
'description' => $this->extractPlainTextFromHtml($blog['content'] ?? ''),
|
||||
'is_published' => $blog['published'] ?? false,
|
||||
'is_featured' => $blog['is_featured'] ?? false,
|
||||
'published_at' => $blog['blog_date'] ?? null,
|
||||
'content' => $this->cleanHtmlForFilament($blog['content'] ?? ''),
|
||||
'category_id' => 1, // Default category
|
||||
]);
|
||||
$this->info("Created new entry: " . ($blog['title'] ?? 'Untitled'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Blogs imported successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract plain text from HTML for description field
|
||||
*/
|
||||
private function extractPlainTextFromHtml(string $html): string
|
||||
{
|
||||
if (empty($html)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Decode HTML entities and strip tags
|
||||
$text = html_entity_decode(strip_tags($html));
|
||||
|
||||
// Clean up whitespace
|
||||
$text = preg_replace('/\s+/', ' ', $text);
|
||||
|
||||
// Limit to reasonable length for description
|
||||
return trim(substr($text, 0, 500));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean HTML content for Filament rich editor
|
||||
*/
|
||||
private function cleanHtmlForFilament(string $html): string
|
||||
{
|
||||
if (empty($html)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Convert escaped newlines to actual newlines
|
||||
$html = str_replace(['\\n', '\\r\\n', '\\r'], "\n", $html);
|
||||
|
||||
// Decode HTML entities
|
||||
$html = html_entity_decode($html, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
|
||||
// Clean up excessive whitespace but preserve paragraph structure
|
||||
$html = preg_replace('/\s*\n\s*/', ' ', $html);
|
||||
$html = preg_replace('/[ \t]+/', ' ', $html);
|
||||
|
||||
// Ensure paragraphs have proper spacing
|
||||
$html = str_replace('</p><p>', '</p>' . "\n" . '<p>', $html);
|
||||
|
||||
return trim($html);
|
||||
}
|
||||
}
|
||||
61
app/Console/Commands/ImportImages.php
Normal file
61
app/Console/Commands/ImportImages.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Entry;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ImportImages extends Command
|
||||
{
|
||||
protected $signature = 'app:import-images {--entry-id=1}';
|
||||
protected $description = 'Import images into the media library';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$entryId = $this->option('entry-id');
|
||||
$entry = Entry::find($entryId);
|
||||
|
||||
if (!$entry) {
|
||||
$this->error("Entry with ID {$entryId} not found");
|
||||
return;
|
||||
}
|
||||
|
||||
$files = Storage::disk('public')->files('imported_images');
|
||||
|
||||
if (empty($files)) {
|
||||
$this->info('No files found in storage/app/public/imported_images/');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info("Found " . count($files) . " files to import");
|
||||
|
||||
foreach ($files as $filePath) {
|
||||
try {
|
||||
$fullPath = storage_path('app/public/' . $filePath);
|
||||
$fileName = pathinfo($fullPath, PATHINFO_BASENAME);
|
||||
|
||||
if (!file_exists($fullPath)) {
|
||||
$this->error("File not found: {$fullPath}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if already exists
|
||||
$existingMedia = $entry->getMedia()->where('file_name', $fileName)->first();
|
||||
if ($existingMedia) {
|
||||
$this->info("Skipping existing: {$fileName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$media = $entry->addMedia($fullPath)
|
||||
->toMediaCollection('default');
|
||||
|
||||
$this->info("Imported: {$fileName}");
|
||||
} catch (\Exception $e) {
|
||||
$this->error("Failed to import {$filePath}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Import completed');
|
||||
}
|
||||
}
|
||||
|
|
@ -17,18 +17,33 @@ class EntriesTable
|
|||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('ID')
|
||||
->sortable(query: function ($query, $direction) {
|
||||
$query->orderBy('id', $direction);
|
||||
}),
|
||||
SpatieMediaLibraryImageColumn::make('featured_image')
|
||||
->collection('featured-image')
|
||||
->label('Image')
|
||||
->circular()
|
||||
->stacked()
|
||||
->limit(3),
|
||||
TextColumn::make('title')
|
||||
->searchable(),
|
||||
TextColumn::make('slug')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('category.name')
|
||||
->label('Category')
|
||||
->sortable(query: function ($query, $direction) {
|
||||
$query->join('categories', 'entries.category_id', '=', 'categories.id')
|
||||
->orderBy('categories.name', $direction)
|
||||
->select('entries.*');
|
||||
})
|
||||
->searchable(),
|
||||
IconColumn::make('is_published')
|
||||
->label('pub')
|
||||
->boolean(),
|
||||
IconColumn::make('is_featured')
|
||||
->label('feat')
|
||||
->boolean(),
|
||||
TextColumn::make('published_at')
|
||||
->date()
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class Entry extends Model implements HasMedia, HasRichContent
|
|||
'content',
|
||||
'call_to_action_link',
|
||||
'call_to_action_text',
|
||||
'category_id',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue