https github.com/emad-zaamout/laravel-9-complete-course-blog-implementation
github project
Project link: if anyone needs the github project I can link it. wont let me post here cause its my first day
My apologies I really wanna learn laravel.
(Please forgive me as I am a novice to Laravel) I fonund a tutorial project I am using it to study the laravel system.
This project is a complete blog for laravel, the only issue is it uses URL's to fetch images rather than an upload/retrieve setup. I will post complete code in hopes to get some answers, I've been studying on the issue however, the way this code I formatted is a little different from your normal how to documentation.
------------------------------------------BlogsController
?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Http\Requests\BlogsUpdateRequest; use App\Http\Requests\DatatablesRequest; use App\Modules\Blogs\BlogsService; use Exception; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response;
class BlogsController extends Controller {
public function __construct(
private readonly BlogsService $service
)
{
}
public function index(DatatablesRequest $request): JsonResponse
{
try {
return response()->json($this->service->index($request->data()));
} catch (Exception $error) {
return response()->json(
[
"exception" => get_class($error),
"errors" => $error->getMessage()
],
Response::HTTP_BAD_REQUEST
);
}
}
public function get(int $id): JsonResponse
{
try {
return response()->json($this->service->get($id));
} catch (Exception $error) {
return response()->json(
[
"exception" => get_class($error),
"errors" => $error->getMessage()
],
Response::HTTP_BAD_REQUEST
);
}
}
public function update(BlogsUpdateRequest $request): JsonResponse
{
try {
return response()->json($this->service->update($request));
} catch (Exception $error) {
return response()->json(
[
"exception" => get_class($error),
"errors" => $error->getMessage()
],
Response::HTTP_BAD_REQUEST
);
}
}
public function delete(int $id): JsonResponse
{
try {
$this->service->delete($id);
return response()->json(null, Response::HTTP_NO_CONTENT);
} catch (Exception $error) {
return response()->json(
[
"exception" => get_class($error),
"errors" => $error->getMessage()
],
Response::HTTP_BAD_REQUEST
);
}
}
}
-------------------------------------BlogsupdateRequest
?php
declare(strict_types=1);
namespace App\Http\Requests;
use App\Models\Blogs; use Illuminate\Foundation\Http\FormRequest;
class BlogsUpdateRequest extends FormRequest { public function rules(): array { return [ "id" => "nullable|numeric", "is_trending" => "required|boolean", "author" => "required|string", "author_image_url" => "required|string", "image_url_portrait" => "required|string", "image_url_landscape" => "required|string", "date" => "required|string", "title" => "required|string", "tags" => "required|string", "description" => "required|string", "content" => "required|string", ]; }
public function data(): array
{
$id = $this->input("id", null);
return [
"id" => ($id === null) ? null : (int)$id,
"is_trending" => $this->input("is_trending", 0),
"author" => $this->input("author"),
"author_image_url" => $this->input("author_image_url"),
"image_url_portrait" => $this->input("image_url_portrait"),
"image_url_landscape" => $this->input("image_url_landscape"),
"date" => $this->input("date"),
"url" => $this->generateUrl($this->input("title")),
"title" => $this->input("title"),
"tags" => array_map(function($row) {
return trim($row);
}, explode(",", $this->input("tags", []))),
"description" => $this->input("description"),
"content" => $this->input("content"),
];
}
private function generateUrl(string $title): string {
$newUrl = trim(strtolower($title));
$newUrl = str_replace(" ", " ", $newUrl);
$newUrl = str_replace(" ", "-", $newUrl);
return preg_replace("/[^A-Za-z0-9\-]/", "", $newUrl);
}
}
------------------------------------------BlogsResource
?php
declare(strict_types=1);
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class BlogsResource extends JsonResource {
public function toArray($request)
{
return [
"id" => $this->id,
"url" => $this->url,
"is_trending" => $this->is_trending,
"author" => $this->author,
"author_image_url" => $this->author_image_url,
"image_url_portrait" => $this->image_url_portrait,
"image_url_landscape" => $this->image_url_landscape,
"title" => $this->title,
"date" => $this->date,
"description" => $this->description,
"content" => $this->content,
"tags" => implode(
", ",
array_map(function($row) {
return $row["tag"];
}, json_decode(json_encode($this->tags), true))
)
];
}
}
----------------------------------------BlogsModel ...
?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Blogs extends Model {
protected $table = "blogs"; protected $fillable = [ "id", "is_trending", "author", "author_image_url", "image_url_portrait", "image_url_landscape", "title", "date", "description", "content", "created_at", "updated_at",
];
public function tags (){ return $this->hasMany(BlogTags::class); }
}
----------------------------------BlogsRepository
?php
declare(strict_types=1);
namespace App\Modules\Blogs;
use App\Http\Requests\BlogsUpdateRequest; use App\Models\Blogs; use App\Models\BlogTags;
class BlogsRepository { const RECENT_BLOGS_LIMIT = 5;
public function getTotalCount(): int
{
return Blogs::all()->count();
}
public function UIList(int $page, int $pageLength, array $filters = []): array
{
if ($filters !== []) {
return Blogs::with(["tags"])
->where($filters)
->where("id", ">", 0)
->limit($pageLength)
->offset(($page - 1) * $pageLength)
->get()
->toArray();
}
return Blogs::with(["tags"])
->where("id", ">", 0)
->limit($pageLength)
->offset(($page - 1) * $pageLength)
->get()
->toArray();
}
public function UIListRecent(): array
{
return Blogs::with(["tags"])
->where("id", ">", 0)
->limit(self::RECENT_BLOGS_LIMIT)
->orderBy("created_at", "desc")
->get()
->toArray();
}
public function get(int $id): Blogs
{
return Blogs::with("tags")->findOrFail($id);
}
public function update(BlogsUpdateRequest $request): Blogs
{
$data = $request->data();
$newBlog = ($data["id"] === null)
? new Blogs()
: $this->get($data["id"]);
$newBlog->url = $data["url"];
$newBlog->is_trending = $data["is_trending"];
$newBlog->author = $data["author"];
$newBlog->author_image_url = $data["author_image_url"];
$newBlog->title = $data["title"];
$newBlog->date = $data["date"];
$newBlog->description = $data["description"];
$newBlog->content = $data["content"];
$newBlog->image_url_landscape = $data["image_url_landscape"];
$newBlog->image_url_portrait= $data["image_url_portrait"];
$newBlog->save();
BlogTags::where("blogs_id", $newBlog->id)->delete();
$toInsertTags = [];
for ($i = 0; $i < count($data["tags"]); $i++) {
$toInsertTags [] = [
"tag" => $data["tags"][$i],
"blogs_id" => $newBlog->id
];
}
BlogTags::insert($toInsertTags);
return $this->get($newBlog->id);
}
public function delete(int $id): void
{
BlogTags::where("blogs_id", $id)->delete();
Blogs::findOrFail($id)->delete();
}
public function getByUrl(string $url) : Blogs
{
return Blogs::where("url", $url)->first();
}
}
----------------------------- Form.Blade
<div class="modal-dialog modal-fullscreen text-dark">
<div class="modal-content">
<div class="modal-content container" style="overflow-y:scroll;">
<div class="row">
<div class="col-12 text-end">
<span type="button" class="btn-close" data-bs-dismiss="modal"></span>
</div>
<div class="col-12">
<div id="BlogsErrorsContainer" class="alert alert-danger errorContainer" style="display:none;">
<h5 class="font-weight-bolder">Error!</h5>
<ul></ul>
</div>
</div>
<div class="col-12">
<hr>
<h5 class="modal-title font-weight-bolder">New Blog</h5>
<hr>
<form id="BlogsForm">
<input type="hidden" class="form-control" id="itemId">
<div class="row">
<div class="col-12 mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="1" id="blogsIsTrending">
<label class="form-check-label">Trending</label>
</div>
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Title</label>
<input type="text" class="form-control" id="blogsTitle">
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Date</label>
<input type="text" class="form-control" id="blogsDate">
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Author Name</label>
<input type="text" class="form-control" id="blogsAuthorName">
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Author Image URL</label>
<input type="text" class="form-control" id="blogsAuthorImageUrl">
</div>
<div class="col-12">
<hr>
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Image Url Landscape</label>
<input type="text" class="form-control" id="blogsImageUrlLandscape">
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<label class="form-label font-weight-bolder">Image Url Portrait</label>
<input type="text" class="form-control" id="blogsImageUrlPortrait">
</div>
<div class="col-12">
<hr>
</div>
<div class="col-12">
<label class="form-label font-weight-bolder">Tags</label>
<input type="text" class="form-control" id="blogsTags">
<div class="form-text">Tags must be seperated by a comma. Example: <i>Laravel, Hosting</i></div>
</div>
<div class="col-12">
<hr>
</div>
<div class="col-12">
<label class="form-label font-weight-bolder">Description</label>
<textarea rows="2" type="text" class="form-control" id="blogsDescription"></textarea>
<div class="form-text">1-3 sentences max.</div>
</div>
<div class="col-12 mb-5">
<label class="form-label font-weight-bolder">Content</label>
<div id="blogsContent"></div>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="modal-footer container">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
<button id="SubmitBlogsForm" type="button" class="btn btn-sm btn-primary">Save Changes</button>
</div>
</div>
</div>
let formSubmitted = false;
let blogsEndpoint = "/api/dashboard/blogs";
let quillToolbar = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{
'header': 1
}, {
'header': 2
}], // custom button values
[{
'list': 'ordered'
}, {
'list': 'bullet'
}],
[{
'script': 'sub'
}, {
'script': 'super'
}], // superscript/subscript
[{
'indent': '-1'
}, {
'indent': '+1'
}], // outdent/indent
[{
'direction': 'rtl'
}], // text direction
[{
'size': ['small', false, 'large', 'huge']
}], // custom dropdown
[{
'header': [1, 2, 3, 4, 5, 6, false]
}],
[{
'color': []
}, {
'background': []
}], // dropdown with defaults from theme
[{
'font': []
}],
[{
'align': []
}],
['clean'] // remove formatting button
];
let quill = new Quill(
"#blogsContent", {
modules: {
toolbar: quillToolbar
},
placeholder: "Compose an epic ...",
theme: "snow"
}
);
function clearForm() {
$("#itemId").val(null);
$("#blogsTitle").val(null);
$("#blogsAuthorName").val(null),
$("#blogsAuthorImageUrl").val(null);
$("#blogsImageUrlLandscape").val(null);
$("#blogsImageUrlPortrait").val(null);
$("#blogsTags").val(null);
$("#blogsDescription").val(null);
$("#blogsDate").val(null);
$("#blogsIsTrending").prop("checked", false);
$("#blogsContent").val(null);
quill.setContents([{
insert: "\n"
}]);
clearErrors();
}
function clearErrors() {
$("#BlogsErrorsContainer ul").empty();
$("#BlogsErrorsContainer").hide();
}
function showErrors(errorsList = []) {
Object.keys(errorsList).forEach(key => {
$("#BlogsModal .errorContainer ul").append(
"<li><b>" + key + ": </b>" + errorsList[key] + "</li>"
);
$("#BlogsModal .errorContainer").show();
});
}
$(document).ready(function() {
$("#SubmitBlogsForm").click(function() {
event.preventDefault();
clearErrors();
if (formSubmitted !== true) {
formSubmitted = true;
$.ajax({
type: "POST",
url: blogsEndpoint,
dataType: "json",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({
id: $("#itemId").val(),
title: $("#blogsTitle").val(),
author: $("#blogsAuthorName").val(),
author_image_url: $("#blogsAuthorImageUrl").val(),
image_url_landscape: $("#blogsImageUrlLandscape").val(),
image_url_portrait: $("#blogsImageUrlPortrait").val(),
tags: $("#blogsTags").val(),
description: $("#blogsDescription").val(),
date: $("#blogsDate").val(),
is_trending: $("#blogsIsTrending").prop("checked"),
content: quill.root.innerHTML.trim(),
}),
success: function(response) {
formSubmitted = false;
$BlogsModal.hide();
$("#dataTable").DataTable().ajax.reload();
}
}).fail(function(response) {
formSubmitted = false;
if (response.status === 422) {
showErrors(response.responseJSON["errors"]);
} else {
showErrors({
Error: "Could not process your request! Please try again later."
});
}
});
}
});
});
Please or to participate in this conversation.