firstcommit

This commit is contained in:
2025-08-17 16:23:14 +05:45
commit 76bf4c0a18
2648 changed files with 362795 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
<?php
namespace Modules\Blog\app\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use App\Jobs\SendNewBlogNotification;
use Illuminate\Http\RedirectResponse;
use Modules\Blog\app\Repositories\BlogRepository;
use Modules\Blog\app\Http\Requests\CreateBlogRequest;
class BlogController extends Controller
{
protected $blogRepository;
public function __construct()
{
$this->blogRepository = new BlogRepository;
}
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$perPage = $request->has('per-page') ? $request->input('per-page') : null;
$filter = $request->has('filter') ? $request->input('filter') : [];
$blogs = $this->blogRepository->allBlogs($perPage, $filter);
return view('blog::index', compact('blogs'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('blog::create');
}
/**
* Store a newly created resource in storage.
*/
public function store(CreateBlogRequest $request)
{
try {
$validated = $request->validated();
$blog = $this->blogRepository->storeBlog($validated);
dispatch(new SendNewBlogNotification($blog));
toastr()->success('Blog created successfully.');
return redirect()->route('cms.blogs.index');
} catch (\Throwable $th) {
report($th);
toastr()->error('Something went wrong.');
return back();
}
}
/**
* Show the specified resource.
*/
public function show($id)
{
return view('blog::show');
}
/**
* Show the form for editing the specified resource.
*/
public function edit($uuid)
{
$blog = $this->blogRepository->findBlogByUuid($uuid);
if (!$blog) {
toastr()->error('Blog not found.');
return back();
}
return view('blog::edit', compact('blog'));
}
/**
* Update the specified resource in storage.
*/
public function update(CreateBlogRequest $request, $uuid): RedirectResponse
{
try {
$validated = $request->validated();
$blog = $this->blogRepository->updateBlog($validated, $uuid);
if (!$blog) {
toastr()->error('Blog not found !');
return back();
}
toastr()->success('Blog updated successfully.');
return redirect()->route('cms.blogs.index');
} catch (\Throwable $th) {
report($th);
toastr()->error('Something went wrong.');
return back();
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy($uuid)
{
try {
$blog = $this->blogRepository->deleteBlog($uuid);
if (!$blog) {
toastr()->error('Blog not found.');
return back();
}
DB::commit();
toastr()->success('Blog deleted successfully.');
return redirect()->route('cms.blogs.index');
} catch (\Throwable $th) {
DB::rollback();
report($th);
toastr()->error('Something went wrong.');
return back();
}
}
}

View File

View File

@@ -0,0 +1,76 @@
<?php
namespace Modules\Blog\app\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class CreateBlogRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'content' => 'required|string',
'author' => 'required|string|max:255',
'summary' => 'required|string',
'published_date' => 'required|date',
'status' => 'required|in:active,inactive',
'meta_title' => 'sometimes|nullable|string|max:255',
'meta_description' => 'sometimes|nullable|string',
'meta_keywords' => 'sometimes|nullable|string|max:255',
'image' => 'sometimes|nullable|mimes:png,jpg,jpeg',
'slug' => 'required',
];
}
public function messages()
{
return [
'title.required' => 'The title field is required.',
'title.string' => 'The title field must be a string.',
'title.max' => 'The title may not be greater than 255 characters.',
'content.required' => 'The content field is required.',
'content.string' => 'The content field must be a string.',
'author.required' => 'The author field is required.',
'author.string' => 'The author field must be a string.',
'author.max' => 'The author may not be greater than 255 characters.',
'summary.required' => 'The summary field is required.',
'summary.string' => 'The summary field must be a string.',
'published_date.required' => 'The published date field is required.',
'published_date.date' => 'The published date field must be a valid date.',
'status.required' => 'The status field is required.',
'status.in' => 'The status field must be either "active" or "inactive".',
'meta_title.string' => 'The meta title field must be a string.',
'meta_title.max' => 'The meta title may not be greater than 255 characters.',
'meta_description.string' => 'The meta description field must be a string.',
'meta_keywords.string' => 'The meta keywords field must be a string.',
'meta_keywords.max' => 'The meta keywords may not be greater than 255 characters.',
'image.mimes' => 'The image must be a file of type: png, jpg, jpeg.',
'slug.required' => 'The slug field is required.',
];
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
// return auth()->user()->can('users.create');
}
}

View File

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Blog\app\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Modules\Blog\Database\factories\BlogFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
class Blog extends Model
{
// use HasFactory;
use SoftDeletes;
protected $dates = ['published_date'];
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'uuid',
'title',
'summary',
'content',
'author',
'published_date',
'image',
'image_path',
'status',
'slug',
];
protected $casts = [
'published_date' => 'date:Y-m-d H:i:s',
];
/**
*
*/
public function getFullImageAttribute()
{
$result = null;
if ($this->image_path) {
$result = asset('storage/uploads/' . $this->image_path);
}
return $result;
}
public function blogMeta()
{
return $this->hasOne(BlogMeta::class, 'blog_id');
}
// protected static function newFactory(): BlogFactory
// {
// //return BlogFactory::new();
// }
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Modules\Blog\app\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Modules\Blog\Database\factories\BlogMetaFactory;
class BlogMeta extends Model
{
use SoftDeletes;
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'blog_id',
'meta_title',
'meta_description',
'meta_keywords',
];
public function blog()
{
return $this->belongsTo(Blog::class, 'blog_id');
}
// protected static function newFactory(): BlogMetaFactory
// {
// //return BlogMetaFactory::new();
// }
}

View File

View File

@@ -0,0 +1,114 @@
<?php
namespace Modules\Blog\app\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
class BlogServiceProvider extends ServiceProvider
{
protected string $moduleName = 'Blog';
protected string $moduleNameLower = 'blog';
/**
* Boot the application events.
*/
public function boot(): void
{
$this->registerCommands();
$this->registerCommandSchedules();
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
$this->loadMigrationsFrom(module_path($this->moduleName, 'database/migrations'));
}
/**
* Register the service provider.
*/
public function register(): void
{
$this->app->register(RouteServiceProvider::class);
}
/**
* Register commands in the format of Command::class
*/
protected function registerCommands(): void
{
// $this->commands([]);
}
/**
* Register command Schedules.
*/
protected function registerCommandSchedules(): void
{
// $this->app->booted(function () {
// $schedule = $this->app->make(Schedule::class);
// $schedule->command('inspire')->hourly();
// });
}
/**
* Register translations.
*/
public function registerTranslations(): void
{
$langPath = resource_path('lang/modules/'.$this->moduleNameLower);
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, $this->moduleNameLower);
$this->loadJsonTranslationsFrom($langPath);
} else {
$this->loadTranslationsFrom(module_path($this->moduleName, 'lang'), $this->moduleNameLower);
$this->loadJsonTranslationsFrom(module_path($this->moduleName, 'lang'));
}
}
/**
* Register config.
*/
protected function registerConfig(): void
{
$this->publishes([module_path($this->moduleName, 'config/config.php') => config_path($this->moduleNameLower.'.php')], 'config');
$this->mergeConfigFrom(module_path($this->moduleName, 'config/config.php'), $this->moduleNameLower);
}
/**
* Register views.
*/
public function registerViews(): void
{
$viewPath = resource_path('views/modules/'.$this->moduleNameLower);
$sourcePath = module_path($this->moduleName, 'resources/views');
$this->publishes([$sourcePath => $viewPath], ['views', $this->moduleNameLower.'-module-views']);
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->moduleNameLower);
$componentNamespace = str_replace('/', '\\', config('modules.namespace').'\\'.$this->moduleName.'\\'.config('modules.paths.generator.component-class.path'));
Blade::componentNamespace($componentNamespace, $this->moduleNameLower);
}
/**
* Get the services provided by the provider.
*/
public function provides(): array
{
return [];
}
private function getPublishableViewPaths(): array
{
$paths = [];
foreach (config('view.paths') as $path) {
if (is_dir($path.'/modules/'.$this->moduleNameLower)) {
$paths[] = $path.'/modules/'.$this->moduleNameLower;
}
}
return $paths;
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Modules\Blog\app\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
/**
* The module namespace to assume when generating URLs to actions.
*/
protected string $moduleNamespace = 'Modules\Blog\app\Http\Controllers';
/**
* Called before routes are registered.
*
* Register any model bindings or pattern based filters.
*/
public function boot(): void
{
parent::boot();
}
/**
* Define the routes for the application.
*/
public function map(): void
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*/
protected function mapWebRoutes(): void
{
Route::middleware('web')
->namespace($this->moduleNamespace)
->group(module_path('Blog', '/routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*/
protected function mapApiRoutes(): void
{
Route::prefix('api')
->middleware('api')
->namespace($this->moduleNamespace)
->group(module_path('Blog', '/routes/api.php'));
}
}

View File

@@ -0,0 +1,152 @@
<?php
namespace Modules\Blog\app\Repositories;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Modules\Banner\app\Services\FileManagementService;
use Modules\Blog\app\Models\Blog;
use Modules\Blog\app\Models\BlogMeta;
class BlogRepository
{
//-- Retrieve all Services
public function allBlogs($perPage = null, $filter = [], $sort = ['by' => 'id', 'sort' => 'DESC'])
{
return Blog::with('blogMeta')->when(array_keys($filter, true), function ($query) use ($filter) {
if (!empty($filter['title'])) {
$query->where('title', $filter['title']);
}
if (!empty($filter['author'])) {
$query->where('author', 'like', '%' . $filter['author'] . '%');
}
})
->orderBy($sort['by'], $sort['sort'])
->paginate($perPage ?: env('PAGE_LIMIT', 999));
}
//-- Find Blog by uuid
public function findBlogByUuid($uuid)
{
return Blog::where('uuid', $uuid)->first();
}
public function storeBlog(array $validated)
{
DB::beginTransaction();
try {
$blog = new Blog();
$blog->uuid = Str::uuid();
$blog->title = $validated['title'];
$blog->content = $validated['content'];
$blog->author = $validated['author'];
$blog->summary = $validated['summary'];
$blog->published_date = $validated['published_date'];
$blog->status = $validated['status'];
$blog->slug = $validated['slug'];
$blog->save();
if (isset($validated['image']) && $validated['image']->isValid()) {
FileManagementService::storeFile(
file: $validated['image'],
uploadedFolderName: 'blogs',
model: $blog
);
}
$blogMeta = new BlogMeta();
$blogMeta->uuid = Str::uuid();
$blogMeta->meta_title = $validated['meta_title'];
$blogMeta->meta_description = $validated['meta_description'];
$blogMeta->meta_keywords = $validated['meta_keywords'];
$blog->blogMeta()->save($blogMeta);
DB::commit();
return $blog;
} catch (\Throwable $th) {
report($th);
DB::rollback();
return null;
}
}
public function updateBlog($validated, $uuid)
{
DB::beginTransaction();
try {
$blog = $this->findBlogByUuid($uuid);
if (!$blog) {
return null;
}
$blog->title = $validated['title'];
$blog->content = $validated['content'];
$blog->author = $validated['author'];
$blog->summary = $validated['summary'];
$blog->published_date = $validated['published_date'];
$blog->status = $validated['status'];
$blog->slug = $validated['slug'];
$blog->save();
if (isset($validated['image']) && $validated['image']->isValid()) {
FileManagementService::uploadFile(
file: $validated['image'],
uploadedFolderName: 'blogs',
filePath: $blog->image_path,
model: $blog
);
}
// Update or create blog meta
$blogMeta = $blog->blogMeta()->firstOrNew([]);
if (!$blogMeta->exists) {
$blogMeta->uuid = Str::uuid();
}
$blogMeta->meta_title = $validated['meta_title'];
$blogMeta->meta_description = $validated['meta_description'];
$blogMeta->meta_keywords = $validated['meta_keywords'];
$blogMeta->save();
DB::commit();
return $blog;
} catch (\Throwable $th) {
dd($th);
report($th);
DB::rollBack();
return null;
}
}
//-- Delete Blog
public function deleteBlog(string $uuid)
{
DB::beginTransaction();
try {
$blog = $this->findBlogByUuid($uuid);
if (!$blog) {
return null;
}
// Delete the image file associated with the blog
if ($blog->image_path !== null) {
FileManagementService::deleteFile($blog->image_path);
}
// Delete associated blog meta and the blog itself
$blog->blogMeta()->delete();
$blog->delete();
DB::commit();
return $blog;
} catch (\Throwable $th) {
DB::rollBack();
report($th);
return null;
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Modules\Blog\app\Services;
use Illuminate\Support\Facades\Storage;
class FileManagementService
{
//-- store file
public static function storeFile($file, $uploadedFolderName, $model)
{
try {
$originalFileName = $file->getClientOriginalName();
$modifiedFileName = date('YmdHis') . "_" . uniqid() . "." . $originalFileName;
$file->storeAs($uploadedFolderName, $modifiedFileName, 'public_uploads'); // This line uses 'public_uploads' disk
$model->image = $modifiedFileName;
$model->image_path = $uploadedFolderName . '/' . $modifiedFileName;
$model->save();
} catch (\Throwable $th) {
report($th);
toastr()->error('Something went wrong.');
return redirect()->back();
}
}
//-- update file
public static function uploadFile($file, $uploadedFolderName ,$filePath, $model)
{
try {
if ($filePath && Storage::disk('public_uploads')->exists($filePath)) {
Storage::disk('public_uploads')->delete($filePath);
}
$originalFileName = $file->getClientOriginalName();
$modifiedFileName = date('YmdHis') . "_" . uniqid() . "." . $originalFileName;
$file->storeAs($uploadedFolderName, $modifiedFileName, 'public_uploads'); // This line uses 'public_uploads' disk
$model->image = $modifiedFileName;
$model->image_path = $uploadedFolderName . '/' . $modifiedFileName;
$model->save();
} catch (\Throwable $th) {
report($th);
toastr()->error('Something went wrong.');
return redirect()->back();
}
}
//-- delete file
public static function deleteFile($filePath)
{
try {
if ($filePath && Storage::disk('public_uploads')->exists($filePath)) {
Storage::disk('public_uploads')->delete($filePath);
} else {
toastr()->error('File Not wrong.');
}
} catch (\Throwable $th) {
report($th);
toastr()->error('Something went wrong while deleting the file.');
}
}
}

View File

@@ -0,0 +1,31 @@
{
"name": "nwidart/blog",
"description": "",
"authors": [
{
"name": "Nicolas Widart",
"email": "n.widart@gmail.com"
}
],
"extra": {
"laravel": {
"providers": [],
"aliases": {
}
}
},
"autoload": {
"psr-4": {
"Modules\\Blog\\": "",
"Modules\\Blog\\App\\": "app/",
"Modules\\Blog\\Database\\Factories\\": "database/factories/",
"Modules\\Blog\\Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Modules\\Blog\\Tests\\": "tests/"
}
}
}

View File

View File

@@ -0,0 +1,5 @@
<?php
return [
'name' => 'Blog',
];

View File

View File

@@ -0,0 +1,37 @@
<?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('blogs', function (Blueprint $table) {
$table->id();
$table->uuid();
$table->string('title');
$table->string('author');
$table->text('summary');
$table->text('content');
$table->timestamp('published_date');
$table->string('image')->nullable();
$table->string('image_path')->nullable();
$table->string('status')->default('active');
$table->softDeletes();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('blogs');
}
};

View File

@@ -0,0 +1,34 @@
<?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('blog_metas', function (Blueprint $table) {
$table->id();
$table->uuid();
$table->unsignedBigInteger('blog_id');
$table->string('meta_title')->nullable();
$table->longText('meta_description')->nullable();
$table->longText('meta_keywords')->nullable();
$table->softDeletes();
$table->timestamps();
$table->foreign('blog_id')->references('id')->on('blogs');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('blog_metas');
}
};

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('blogs', function (Blueprint $table) {
$table->string('slug', 300)->after('title');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('blogs', function (Blueprint $table) {
$table->dropColumn('slug');
});
}
};

View File

@@ -0,0 +1,2 @@
<?php
blogs

View File

View File

@@ -0,0 +1,93 @@
<?php
namespace Modules\Blog\database\seeders;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Illuminate\Database\Seeder;
use Modules\Blog\app\Models\Blog;
use Illuminate\Support\Facades\Storage;
class BlogDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$blogs = [
[
'title' => 'How to Reach Benagil Cave in 2023, Algarve, Portugal (Tours + Kayaking + tips)',
'content' => "Algarves Benagil Cave beach is one of those extra special ones. The only way in is by kayaking or paddle boating, which obviously adds a more elusive touch to this already amazing place.
didnt know already, the Algarve region is in the South of Portugal and is a lovely coastal area. The thing that makes the Algarve coastline unique is the stunning rock formations. They are everywhere and are mindblowing. I spent a week in Lagos and then a week in Carvoeiro both these places are in the Algarve and are stunning.
In Portuguese, the Benagil Cave is called Algar de Benagil. The holes in the rock formations are called algares (or holes) and there are many of them. I did mention in my Seven Hanging Valleys post that theres a hike that goes on top of the cliff inside which this cave is situated. You can see the holes in these cliffs from the top.
This post is about the best way of getting inside the cave from the water. Actually, there are many caves in this short stretch and they are collectively called the Benagil Caves, but in this post, we will mostly talk about the most famous Benagil Cave one with a hidden sandy beach. THIS one (below).",
'author' => 'Jhigu CMS Travel & Tour',
'summary' => 'Algarves Benagil Cave beach is one of those extra special ones. The only way in is by kayaking or paddle boating, which obviously adds a more elusive touch to this already amazing place',
'published_date' => Carbon::now(),
'image' => 'b1.jpg',
'slug' => "Slug for blog",
],
[
'title' => 'The Ultimate Italy Road Trip: 2 Weeks Itinerary (with Amalfi Coast)',
'content' => "The first time I visited Italy, it was just North Italy. We landed in Venice and drove to Trentino in our rental car. The second time was in South Italy where we spent one entire month in Puglia. We actually drove from Germany to Puglia but realized it would have been easier to just fly to Bari or Brindisi and drive a rental car from there.
For the purpose of travel, it is important to understand what are the regions of Italy. You can pick and choose some of them or get a taste of them all. Here are the regions in Italy that you can visit
Northeast Italy, (the Dolomites, Trentino, Venice and Bologna)
Northwest Italy, (Cinque Terre, Milan and the Alps)
Central Italy, (Tuscany region and Rome)
Southern Italy, (Naples, Puglia, Amalfi and Capri)
The islands Sicily and Sardinia.
If you ever see the list of the most visited countries in the world, Italy usually is in top 5 year after year. It is because there is so much to see & experience in every single region of
Keep in mind that to properly explore each region of Italy, you would probably need at least two weeks each. However, this itinerary focuses on the entire Italy, so I will help you move from one region to another and tell you the best of each. Thats the difference between a region-specific itinerary and a country-specific itinerary.
Matera in Puglia, Italy road trip two weeks itinerary via Pixabay
If you think you will get to visit Italy multiple times, then by all means pick just one region or maximum two for each trip. If youre going to visit Italy just once or twice in your life then I suggest you visit more than just 2 regions because they all have something to offer.
ont try to cover it all, it isnt possible to do so. Instead, pick a few destinations and spend some quality time in each place that you visit so that you dont feel rushed or drained out.",
'author' => 'Jhigu CMS Travel & Tour',
'summary' => 'Algarves Benagil Cave beach is one of those extra special ones. The only way in is by kayaking or paddle boating, which obviously adds a more elusive touch to this already amazing place',
'published_date' => Carbon::now(),
'image' => 'b2.jpg',
'slug' => "Slug for blog",
]
];
foreach ($blogs as $blog) {
$cmsblog = Blog::create([
'uuid' => Str::uuid(),
'title' => $blog['title'],
'content' => $blog['content'],
'author' => $blog['author'],
'summary' => $blog['summary'],
'published_date' => $blog['published_date'],
'slug' => $blog['slug'],
]);
// Add image to the created banner
$this->uploadImageForBlog($blog['image'], $cmsblog);
}
}
private function uploadImageForBlog(string $imageFileName, $cmsbanner)
{
$seederDirPath = 'Blogs/';
// Generate a unique filename for the new image
$newFileName = Str::uuid() . '.jpg';
// Storage path for the new image
$storagePath = '/blogs/' . $newFileName;
// Check if the image exists in the seeder_disk
if (Storage::disk('seeder_disk')->exists($seederDirPath . $imageFileName)) {
// Copy the image from seeder to public
$fileContents = Storage::disk('seeder_disk')->get($seederDirPath . $imageFileName);
Storage::disk('public_uploads')->put($storagePath, $fileContents);
$cmsbanner->image = $newFileName;
$cmsbanner->image_path = $storagePath;
$cmsbanner->save();
}
}
}

View File

11
Modules/Blog/module.json Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "Blog",
"alias": "blog",
"description": "",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\Blog\\app\\Providers\\BlogServiceProvider"
],
"files": []
}

15
Modules/Blog/package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"axios": "^1.1.2",
"laravel-vite-plugin": "^0.7.5",
"sass": "^1.69.5",
"postcss": "^8.3.7",
"vite": "^4.0.0"
}
}

View File

View File

View File

View File

@@ -0,0 +1,38 @@
@extends('admin::layouts.master')
@section('title')
Create Blog
@endsection
@section('breadcrumb')
@php
$breadcrumbData = [
[
'title' => 'Blog',
'link' => 'null',
],
[
'title' => 'Dashboard',
'link' => route('dashboard'),
],
[
'title' => 'Blogs',
'link' => null,
],
[
'title' => 'Add Blog',
'link' => null,
],
];
@endphp
@include('admin::layouts.partials.breadcrumb', $breadcrumbData)
@endsection
@section('content')
<form action="{{ route('cms.blogs.store') }}" method="POST" enctype="multipart/form-data">
@csrf
@include('blog::partial.form')
</form>
@endsection

View File

@@ -0,0 +1,43 @@
@extends('admin::layouts.master')
@section('title')
Update Blog
@endsection
@section('breadcrumb')
@php
$breadcrumbData = [
[
'title' => 'Blog',
'link' => 'null',
],
[
'title' => 'Dashboard',
'link' => route('dashboard'),
],
[
'title' => 'Blogs',
'link' => null,
],
[
'title' => 'Update Blog',
'link' => null,
],
];
@endphp
@include('admin::layouts.partials.breadcrumb', $breadcrumbData)
@endsection
@section('content')
<div class="card-body">
<form action="{{ route('cms.blogs.update', ['uuid' => $blog->uuid]) }}" method="POST"
enctype="multipart/form-data">
@csrf
@method('PUT')
@include('blog::partial.form')
</form>
</div>
@endsection

View File

@@ -0,0 +1,138 @@
@extends('admin::layouts.master')
@section('title')
Blog
@endsection
@section('breadcrumb')
@php
$breadcrumbData = [
[
'title' => 'Blog',
'link' => 'null',
],
[
'title' => 'Dashboard',
'link' => route('dashboard'),
],
[
'title' => 'Blogs',
'link' => null,
],
];
@endphp
@include('admin::layouts.partials.breadcrumb', $breadcrumbData)
@endsection
@section('content')
<div class="card">
<div class="row">
<div class="col-md-6">
<h4 class="card-header">List of Blog</h4>
</div>
<div class="col-md-6">
<div class="flex-column flex-md-row">
<div class="dt-action-buttons text-end pt-3 px-3">
<div class="dt-buttons btn-group flex-wrap">
<a href="{{ route('cms.blogs.create') }}"
class="btn btn-secondary create-new btn-primary d-none d-sm-inline-block text-white">
<i class="bx bx-plus me-sm-1"></i>
Add New
</a>
</div>
</div>
</div>
</div>
</div>
<div class="card-datatable table-responsive">
<table class="datatables-users table border-top">
<thead class="table-light">
<tr>
<th>S.N</th>
<th>Title With Image</th>
<th>Author</th>
<th>Published Date</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody class="table-border-bottom-0">
@if (count($blogs) > 0)
@foreach ($blogs ?? [] as $blog)
<tr>
<td>
#{{ $loop->iteration }}
</td>
<td>
<div class="d-flex align-items-center me-3">
<img src="{{ asset($blog->image_path ? 'storage/uploads/' . $blog->image_path : 'backend/uploads/images/no-Image.jpg') }}"
alt="Image" class="rounded me-3" height="40" width="60"
style="object-fit: cover">
<div class="card-title mb-0 px-3">
<h6 class="mb-0">{{ $blog->title }}</h6>
<small class="text-muted">{{ Str::limit($blog->summary, 80) }}</small>
</div>
</div>
</td>
<td>
{{ $blog->author }}
</td>
<td>
{{ $blog->published_date->toFormattedDateString() }}
</td>
<td>
<span class="badge bg-label-{{ $blog->status == 'active' ? 'success' : 'danger' }}">
{{ $blog->status }}</span>
</td>
<td>
<div class="dropdown">
<button type="button" class="btn p-0 dropdown-toggle hide-arrow"
data-bs-toggle="dropdown">
<i class="bx bx-dots-vertical-rounded"></i>
</button>
<div class="dropdown-menu">
<a class="dropdown-item"
href="{{ route('cms.blogs.edit', ['uuid' => $blog->uuid]) }}"><i
class="bx bx-edit-alt me-1"></i>
Edit</a>
<form method="POST"
action="{{ route('cms.blogs.delete', ['uuid' => $blog->uuid]) }}"
id="deleteForm_{{ $blog->uuid }}" class="dropdown-item">
@csrf
@method('DELETE')
<button type="submit" class="border-0 bg-transparent deleteBtn"
style="color:inherit"><i class="bx bx-trash me-1"></i> Delete</button>
</form>
</div>
</div>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="6">No record found.</td>
</tr>
@endif
</tbody>
</table>
</div>
<div class="px-3">
{{ $blogs->links('admin::layouts.partials.pagination') }}
</div>
</div>
@endsection
{{-- style --}}
@push('required-styles')
@include('admin::vendor.dataTables.style')
@endpush
{{-- script --}}
@push('required-scripts')
@include('admin::vendor.dataTables.script')
@endpush

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Blog Module - {{ config('app.name', 'Laravel') }}</title>
<meta name="description" content="{{ $description ?? '' }}">
<meta name="keywords" content="{{ $keywords ?? '' }}">
<meta name="author" content="{{ $author ?? '' }}">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
{{-- Vite CSS --}}
{{-- {{ module_vite('build-blog', 'resources/assets/sass/app.scss') }} --}}
</head>
<body>
@yield('content')
{{-- Vite JS --}}
{{-- {{ module_vite('build-blog', 'resources/assets/js/app.js') }} --}}
</body>

View File

@@ -0,0 +1,43 @@
<div class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6">
<div class="flex flex-1 justify-between sm:hidden">
<a href="#" class="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">Previous</a>
<a href="#" class="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">Next</a>
</div>
<div class="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
<div>
<p class="text-sm text-gray-700">
Showing
<span class="font-medium">1</span>
to
<span class="font-medium">10</span>
of
<span class="font-medium">97</span>
results
</p>
</div>
<div>
<nav class="isolate inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
<a href="#" class="relative inline-flex items-center rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0">
<span class="sr-only">Previous</span>
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z" clip-rule="evenodd" />
</svg>
</a>
<!-- Current: "z-10 bg-indigo-600 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600", Default: "text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:outline-offset-0" -->
<a href="#" aria-current="page" class="relative z-10 inline-flex items-center bg-indigo-600 px-4 py-2 text-sm font-semibold text-white focus:z-20 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">1</a>
<a href="#" class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0">2</a>
<a href="#" class="relative hidden items-center px-4 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0 md:inline-flex">3</a>
<span class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-700 ring-1 ring-inset ring-gray-300 focus:outline-offset-0">...</span>
<a href="#" class="relative hidden items-center px-4 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0 md:inline-flex">8</a>
<a href="#" class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0">9</a>
<a href="#" class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0">10</a>
<a href="#" class="relative inline-flex items-center rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-20 focus:outline-offset-0">
<span class="sr-only">Next</span>
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
</svg>
</a>
</nav>
</div>
</div>
</div>

View File

@@ -0,0 +1,132 @@
<div class="row">
<div class="col-8">
<div class="card mb-4">
<h5 class="card-header">Blog Details</h5>
<hr class="my-0">
<div class="card-body">
<div class="row" x-data="generateSlug()" x-init="title = '{{ addslashes(old('title', $blog->title ?? '')) }}'; slug = '{{ addslashes(old('slug', $blog->slug ?? '')) }}'">
<div class="mb-3 fv-plugins-icon-container">
<label class="form-label" for="basic-default-name">Title</label>
<input type="text" class="form-control" x-model="title" x-on:input="updateSlug()" name="title"
value="{{ old('title', $blog->title ?? '') }}"
placeholder="e.g. The Ultimate Italy Road Trip: 2 Weeks Itinerary (with Amalfi Coast)"
required />
</div>
<div class="mb-3 col-md-6 fv-plugins-icon-container">
<label for="slug" class="form-label">Slug</label>
<input class="form-control" type="text" x-model="slug" name="slug" id=""
value="{{ old('slug', $blog->slug ?? '') }}" placeholder="e.g:blog slug" required>
</div>
<div class="mb-3 col-md-6 fv-plugins-icon-container">
<label class="form-label" for="basic-default-company">Author</label>
<input type="text" class="form-control" name="author"
value="{{ old('author', $blog->author ?? '') }}" placeholder="e.g. Jhigu CMS Travel & Tour"
required />
</div>
<div class="mb-3 col-md-12">
<label for="exampleFormControlTextarea1" class="form-label">Summary</label>
<textarea class="form-control" id="exampleFormControlTextarea1" rows="3" name="summary"
placeholder="e.g:Write Summary here..." required>{{ old('summary', $blog->summary ?? '') }}</textarea>
</div>
<div class="mb-3 col-md-12">
<label class="form-label" for="basic-default-message">Content</label><br>
<textarea name="content" id="advance" placeholder="Description..">{{ !empty($blog) ? $blog->content : '' }}</textarea>
</div>
<div class="mb-3 col-md-6">
<label class="form-label" for="basic-default-company">Published Date</label>
<input class="date form-control" type="text" id="flatpickr-datetime" name="published_date"
value="{{ old('published_date', $blog->published_date ?? '') }}"
placeholder="e.g. 2023-11-25 05:21:42" required />
</div>
<div class="mb-3 col-md-6">
<label for="language" class="form-label">Status</label>
<div class="position-relative">
<select id="language select2Basic" class="select2 form-select select2-hidden-accessible"
data-select2-id="language" tabindex="-1" aria-hidden="true" name="status" required>
<option value="" data-select2-id="4">Select Status</option>
<option value="active"
{{ old('status', $blog->status ?? '') == 'active' ? 'selected' : '' }}>
Active</option>
<option value="inactive"
{{ old('status', $blog->status ?? '') == 'inactive' ? 'selected' : '' }}>Inactive
</option>
</select>
</div>
</div>
<div class="mt-2">
<button type="submit" class="btn btn-primary me-2">
{{ !isset($blog) ? 'Save Changes' : 'Update Changes' }}</button>
<button type="reset" class="btn btn-label-secondary">Reset</button>
</div>
</div>
</div>
<!-- /Account -->
</div>
</div>
{{-- Right sidebar --}}
<div class="col-4">
<div class="card mb-4">
<h5 class="card-header">Images</h5>
<div class="card-body">
<div class="d-flex align-items-start align-items-sm-center gap-4">
<img src="{{ asset(!empty($blog->image_path) ? 'storage/uploads/' . $blog->image_path : 'backend/uploads/images/no-Image.jpg') }}"
alt="blog-image input-file" class="d-block rounded show-image" height="100" width="100" />
<div class="button-wrapper">
<label for="upload" class="btn btn-primary me-2 mb-4" tabindex="0">
<span class="d-none d-sm-block">Upload</span>
<i class="bx bx-upload d-block d-sm-none"></i>
<input type="file" id="upload" class="input-file" name="image" hidden
accept="image/png, image/jpeg" />
</label>
<button type="button" class="btn btn-label-secondary image-reset mb-4">
<i class="bx bx-reset d-block d-sm-none"></i>
<span class="d-none d-sm-block">Reset</span>
</button>
<p class="mb-0">Allowed JPG, GIF or PNG. Max size of 3Mb</p>
</div>
</div>
</div>
</div>
<div class="card mb-4">
<h5 class="card-header">Blog Meta Details</h5>
<div class="card-body">
<div class="mb-3 col-md-12">
<label for="exampleFormControlTextarea1" class="form-label">Meta Title</label>
<textarea class="form-control" id="exampleFormControlTextarea1" rows="3" name="meta_title">{{ old('meta_title', $blog->blogMeta->meta_title ?? '') }}</textarea>
</div>
<div class="mb-3 col-md-12">
<label for="exampleFormControlTextarea1" class="form-label">Meta Description</label>
<textarea class="form-control" id="exampleFormControlTextarea1" rows="3" name="meta_description">{{ old('meta_description', $blog->blogMeta->meta_description ?? '') }}</textarea>
</div>
<div class="mb-3 col-md-12">
<label for="exampleFormControlTextarea1" class="form-label">Meta Keywords</label>
<textarea class="form-control" id="exampleFormControlTextarea1" rows="3" name="meta_keywords">{{ old('meta_keywords', $blog->blogMeta->meta_keywords ?? '') }}</textarea>
</div>
</div>
</div>
</div>
{{-- style --}}
@push('required-styles')
@include('admin::vendor.datePicker.style')
@include('admin::vendor.select2.style')
@endpush
{{-- script --}}
@push('required-scripts')
@include('admin::vendor.tinymce.script')
@include('admin::vendor.imageUpload.script')
@include('admin::vendor.datePicker.script')
@include('admin::vendor.select2.script')
@endpush

View File

View File

@@ -0,0 +1,19 @@
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::middleware(['auth:sanctum'])->prefix('v1')->name('api.')->group(function () {
Route::get('blog', fn (Request $request) => $request->user())->name('blog');
});

View File

@@ -0,0 +1,40 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Blog\app\Http\Controllers\BlogController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::group(
[
'prefix' => 'apanel',
'middleware' => ['auth'],
'as' => 'cms.',
],
function () {
Route::group(
[
'prefix' => 'cms',
'as' => 'blogs.',
'controller' => 'BlogController',
],
function () {
Route::get('blogs', 'index')->name('index');
Route::get('blogs/create', 'create')->name('create');
Route::post('blogs/store', 'store')->name('store');
Route::get('blogs/{uuid}/edit', 'edit')->name('edit');
Route::put('blogs/{uuid}/update', 'update')->name('update');
Route::delete('blogs/{uuid}/delete', 'destroy')->name('delete');
}
);
}
);

View File

View File

View File

@@ -0,0 +1,26 @@
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
build: {
outDir: '../../public/build-blog',
emptyOutDir: true,
manifest: true,
},
plugins: [
laravel({
publicDirectory: '../../public',
buildDirectory: 'build-blog',
input: [
__dirname + '/resources/assets/sass/app.scss',
__dirname + '/resources/assets/js/app.js'
],
refresh: true,
}),
],
});
//export const paths = [
// 'Modules/$STUDLY_NAME$/resources/assets/sass/app.scss',
// 'Modules/$STUDLY_NAME$/resources/assets/js/app.js',
//];