Compare commits
59 Commits
35686d82c3
...
1ddf2281c6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ddf2281c6 | ||
|
|
d398815acb | ||
|
|
f69657dc1d | ||
|
|
c9a688e80d | ||
|
|
4df456b9d5 | ||
|
|
9f7619146f | ||
|
|
43d464a0d7 | ||
|
|
58cb185db5 | ||
|
|
3d99998036 | ||
|
|
6a58103cb2 | ||
|
|
4bb8352d32 | ||
|
|
b51d8b00c2 | ||
|
|
640bd67556 | ||
|
|
f7c532a648 | ||
|
|
29e85c66b8 | ||
|
|
28e62539b1 | ||
|
|
7c3a1992ca | ||
|
|
681217b413 | ||
|
|
2f0543b625 | ||
|
|
f1ad83454d | ||
|
|
965951cdad | ||
|
|
ea1bb2e265 | ||
|
|
3246c1cb09 | ||
|
|
65c8458860 | ||
|
|
7cd623e6b8 | ||
|
|
25da2385db | ||
|
|
9dffe42adb | ||
|
|
542949d47b | ||
|
|
1625ce99ec | ||
|
|
d8a583959e | ||
|
|
46ec73e99d | ||
|
|
a4580d1d9d | ||
|
|
69567ee60e | ||
|
|
e67dff516b | ||
|
|
fed113c0b7 | ||
|
|
3aad54907e | ||
|
|
37a507c742 | ||
|
|
a4025256d4 | ||
|
|
37dd2c9d54 | ||
|
|
75af8220a7 | ||
|
|
299a98d506 | ||
|
|
c740b69675 | ||
|
|
1d2540a90d | ||
|
|
7a5438db2c | ||
|
|
a5f76a3657 | ||
|
|
b7e85f80f7 | ||
|
|
97b3cc6c47 | ||
|
|
32f9fd766f | ||
|
|
779b5101b0 | ||
|
|
f0a6a9e20a | ||
|
|
c823134ba7 | ||
|
|
6ea369941c | ||
|
|
169b1e606a | ||
|
|
373402eb1b | ||
|
|
2c0a585bf8 | ||
|
|
240bc7ccfb | ||
|
|
ae33539f72 | ||
| b14a3189b4 | |||
| dbec761a0b |
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
||||
.git/
|
||||
node_modules/
|
||||
.env
|
||||
28
.gitea/workflows/deploy.yml
Normal file
@ -0,0 +1,28 @@
|
||||
name: Deploy Backend
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: sh
|
||||
steps:
|
||||
- name: Install SSH client
|
||||
run: |
|
||||
V=$(cut -d'.' -f1-2 /etc/alpine-release)
|
||||
echo "https://dl-cdn.alpinelinux.org/alpine/v$V/main" > /etc/apk/repositories
|
||||
echo "https://dl-cdn.alpinelinux.org/alpine/v$V/community" >> /etc/apk/repositories
|
||||
apk add --no-cache openssh-client
|
||||
- name: Setup SSH key
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.DEPLOY_KEY }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "$SSH_KEY" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan -p 2234 -H 172.17.0.1 >> ~/.ssh/known_hosts
|
||||
- name: Deploy
|
||||
run: ssh -p 2234 devops@172.17.0.1 'bash /opt/services/parsa/backend/deploy.sh'
|
||||
21
Dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
FROM serversideup/php:8.3-fpm-nginx
|
||||
|
||||
ARG HTTP_PROXY
|
||||
ARG HTTPS_PROXY
|
||||
|
||||
COPY --chown=www-data:www-data . /var/www/html/
|
||||
|
||||
ENV COMPOSER_HOME=/tmp/composer
|
||||
USER www-data
|
||||
RUN if [ -d /var/www/html/vendor ]; then \
|
||||
echo "vendor/ found — installing offline" && \
|
||||
HTTP_PROXY="" HTTPS_PROXY="" http_proxy="" https_proxy="" \
|
||||
COMPOSER_DISABLE_NETWORK=1 \
|
||||
composer install --no-dev --optimize-autoloader --no-interaction; \
|
||||
else \
|
||||
echo "vendor/ not found — installing from proxy" && \
|
||||
composer config -g repos.packagist composer https://registry.parsaaghayi.ir/repository/composer-proxy/ && \
|
||||
composer install --no-dev --optimize-autoloader --no-interaction; \
|
||||
fi
|
||||
|
||||
EXPOSE 8080
|
||||
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 parsa aghayi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\AboutMe;
|
||||
class AboutMeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$aboutMe = AboutMe::with('skills')->first(); // بارگذاری مهارتها به همراه اطلاعات AboutMe
|
||||
return response()->json($aboutMe);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$aboutMe = AboutMe::with('skills')->findOrFail($id);
|
||||
return response()->json($aboutMe);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$aboutMe = AboutMe::create($request->all());
|
||||
return response()->json($aboutMe, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$aboutMe = AboutMe::findOrFail($id);
|
||||
$aboutMe->update($request->all());
|
||||
return response()->json($aboutMe);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
AboutMe::destroy($id);
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
170
app/Http/Controllers/Api/Blog/CategoryController.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Blog;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Blog\StoreCategoryRequest;
|
||||
use App\Http\Requests\Blog\UpdateCategoryRequest;
|
||||
use App\Http\Resources\CategoryResource;
|
||||
use App\Http\Resources\PostResource;
|
||||
use App\Models\Blog\Category;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="Categories", description="Blog categories")
|
||||
*/
|
||||
class CategoryController extends Controller
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/blog/categories",
|
||||
* tags={"Categories"},
|
||||
* summary="Paginated list of categories",
|
||||
* @OA\Parameter(name="per_page", in="query", @OA\Schema(type="integer")),
|
||||
* @OA\Parameter(name="filter", in="query", @OA\Schema(type="string")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$filter = $request->input('filter');
|
||||
$sortBy = $request->input('sort_by', 'created_at');
|
||||
$sortDirection = $request->input('sort_direction', 'desc');
|
||||
$perPage = $request->input('per_page', 10);
|
||||
|
||||
$query = Category::with('posts');
|
||||
|
||||
if (!empty($filter)) {
|
||||
$query->where('name', 'like', '%' . $filter . '%');
|
||||
}
|
||||
|
||||
$query->orderBy($sortBy, $sortDirection);
|
||||
|
||||
$categories = $query->paginate($perPage);
|
||||
|
||||
return CategoryResource::collection($categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/blog/categories/{id}",
|
||||
* tags={"Categories"},
|
||||
* summary="Get a category by ID",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$category = Category::with('posts')->findOrFail($id);
|
||||
return new CategoryResource($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/blog/categories/{slug}/posts",
|
||||
* tags={"Categories"},
|
||||
* summary="Get posts by category slug",
|
||||
* @OA\Parameter(name="slug", in="path", required=true, @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="per_page", in="query", @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="OK"),
|
||||
* @OA\Response(response=404, description="Category not found")
|
||||
* )
|
||||
*/
|
||||
public function getCategoryWithPosts(Request $request, $slug)
|
||||
{
|
||||
$category = Category::where('slug', $slug)->first();
|
||||
|
||||
if (!$category) {
|
||||
return response()->json([
|
||||
'message' => 'Category not found.'
|
||||
], 404);
|
||||
}
|
||||
|
||||
$perPage = $request->input('per_page', 10);
|
||||
$filter = $request->input('filter');
|
||||
$sortBy = $request->input('sort_by', 'published_at');
|
||||
$sortDirection = $request->input('sort_direction', 'desc');
|
||||
|
||||
$postsQuery = $category->posts()->with(['category', 'tags', 'author']);
|
||||
|
||||
if (!empty($filter)) {
|
||||
$postsQuery->where('title', 'like', '%' . $filter . '%');
|
||||
}
|
||||
|
||||
$postsQuery->orderBy($sortBy, $sortDirection);
|
||||
|
||||
$posts = $postsQuery->paginate($perPage);
|
||||
|
||||
$meta = [
|
||||
'total' => $posts->total(),
|
||||
'per_page' => $posts->perPage(),
|
||||
'current_page' => $posts->currentPage(),
|
||||
'last_page' => $posts->lastPage(),
|
||||
'from' => $posts->firstItem(),
|
||||
'to' => $posts->lastItem(),
|
||||
];
|
||||
|
||||
$links = [
|
||||
'first' => $posts->url(1),
|
||||
'last' => $posts->url($posts->lastPage()),
|
||||
'prev' => $posts->previousPageUrl(),
|
||||
'next' => $posts->nextPageUrl(),
|
||||
];
|
||||
|
||||
return response()->json([
|
||||
'category' => new CategoryResource($category),
|
||||
'posts' => PostResource::collection($posts),
|
||||
'meta' => $meta,
|
||||
'links' => $links,
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/blog/categories",
|
||||
* tags={"Categories"},
|
||||
* summary="Create a category",
|
||||
* @OA\RequestBody(@OA\MediaType(mediaType="application/json")),
|
||||
* @OA\Response(response=201, description="Created")
|
||||
* )
|
||||
*/
|
||||
public function store(StoreCategoryRequest $request)
|
||||
{
|
||||
$category = Category::create($request->validated());
|
||||
return new CategoryResource($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/blog/categories/{id}",
|
||||
* tags={"Categories"},
|
||||
* summary="Update a category",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\RequestBody(@OA\MediaType(mediaType="application/json")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function update(UpdateCategoryRequest $request, $id)
|
||||
{
|
||||
$category = Category::findOrFail($id);
|
||||
$category->update($request->validated());
|
||||
return new CategoryResource($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/blog/categories/{id}",
|
||||
* tags={"Categories"},
|
||||
* summary="Delete a category",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$category = Category::findOrFail($id);
|
||||
$category->delete();
|
||||
return response()->json(['message' => 'Category deleted successfully'], 200);
|
||||
}
|
||||
}
|
||||
@ -1,47 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Blog;
|
||||
namespace App\Http\Controllers\Api\Blog;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\CategoryResource;
|
||||
use App\Http\Resources\PostResource;
|
||||
use App\Models\Blog\Category;
|
||||
use App\Models\Blog\Post;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="Blog Home", description="Blog homepage aggregated data")
|
||||
*/
|
||||
class HomeController extends Controller
|
||||
{
|
||||
/**
|
||||
* برگرداندن دادههای مورد نیاز برای صفحهی اصلی
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @OA\Get(
|
||||
* path="/blog/homepage",
|
||||
* tags={"Blog Home"},
|
||||
* summary="Get homepage data: latest posts, categories, most viewed, random",
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// 1. 5 پست آخر
|
||||
$latestPosts = Post::with(['category', 'tags', 'author'])
|
||||
->orderBy('published_at', 'desc')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
// 2. 5 دستهبندی آخر
|
||||
$latestCategories = Category::orderBy('created_at', 'desc')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
// 3. 5 پست با بیشترین بازدید
|
||||
$mostViewedPosts = Post::with(['category', 'tags', 'author'])
|
||||
->orderBy('views', 'desc')
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
// 4. 4 پست تصادفی
|
||||
$randomPosts = Post::with(['category', 'tags', 'author'])
|
||||
->inRandomOrder()
|
||||
->take(4)
|
||||
->get();
|
||||
|
||||
// بازگشت به صورت JSON با استفاده از منابع (Resources)
|
||||
return response()->json([
|
||||
'latest_posts' => PostResource::collection($latestPosts),
|
||||
'latest_categories' => CategoryResource::collection($latestCategories),
|
||||
130
app/Http/Controllers/Api/Blog/PostController.php
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Blog;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Blog\StorePostRequest;
|
||||
use App\Http\Requests\Blog\UpdatePostRequest;
|
||||
use App\Http\Resources\PostResource;
|
||||
use App\Models\Blog\Post;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="Posts", description="Blog post management")
|
||||
*/
|
||||
class PostController extends Controller
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/blog/posts",
|
||||
* tags={"Posts"},
|
||||
* summary="Paginated list of posts",
|
||||
* @OA\Parameter(name="per_page", in="query", @OA\Schema(type="integer")),
|
||||
* @OA\Parameter(name="category", in="query", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="tag", in="query", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="search", in="query", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="sort_field", in="query", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="sort_direction", in="query", @OA\Schema(type="string", enum={"asc","desc"})),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$filters = $request->only(['category', 'tag', 'search', 'author', 'published']);
|
||||
|
||||
$sortField = $request->get('sort_field', 'published_at');
|
||||
$sortDirection = $request->get('sort_direction', 'desc');
|
||||
|
||||
$perPage = $request->get('per_page', 10);
|
||||
|
||||
$query = Post::with(['category', 'tags', 'author'])
|
||||
->filter($filters)
|
||||
->sort($sortField, $sortDirection);
|
||||
|
||||
$posts = $query->paginate($perPage);
|
||||
|
||||
return PostResource::collection($posts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/blog/posts/{slug}",
|
||||
* tags={"Posts"},
|
||||
* summary="Get a single post by slug with related posts",
|
||||
* @OA\Parameter(name="slug", in="path", required=true, @OA\Schema(type="string")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function show($slug)
|
||||
{
|
||||
$post = Post::with(['category', 'tags', 'author', 'relatedPosts.category', 'relatedPosts.tags', 'relatedPosts.author'])
|
||||
->where('slug', $slug)
|
||||
->firstOrFail();
|
||||
|
||||
return response()->json([
|
||||
'post' => new PostResource($post),
|
||||
'related_posts' => PostResource::collection($post->relatedPosts->sortByDesc('published_at')->take(5)),
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/blog/posts",
|
||||
* tags={"Posts"},
|
||||
* summary="Create a post",
|
||||
* @OA\RequestBody(@OA\MediaType(mediaType="application/json")),
|
||||
* @OA\Response(response=201, description="Created")
|
||||
* )
|
||||
*/
|
||||
public function store(StorePostRequest $request)
|
||||
{
|
||||
$post = Post::create($request->validated());
|
||||
|
||||
if ($request->has('tags')) {
|
||||
$post->tags()->attach($request->tags);
|
||||
}
|
||||
|
||||
return new PostResource($post->load(['category', 'tags', 'author']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/blog/posts/{id}",
|
||||
* tags={"Posts"},
|
||||
* summary="Update a post",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\RequestBody(@OA\MediaType(mediaType="application/json")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function update(UpdatePostRequest $request, $id)
|
||||
{
|
||||
$post = Post::findOrFail($id);
|
||||
$post->update($request->validated());
|
||||
|
||||
if ($request->has('tags')) {
|
||||
$post->tags()->sync($request->tags);
|
||||
}
|
||||
|
||||
return new PostResource($post->load(['category', 'tags', 'author']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/blog/posts/{id}",
|
||||
* tags={"Posts"},
|
||||
* summary="Delete a post",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$post = Post::findOrFail($id);
|
||||
$post->tags()->detach();
|
||||
$post->relatedPosts()->detach();
|
||||
$post->delete();
|
||||
|
||||
return response()->json(['message' => 'Post deleted successfully'], 200);
|
||||
}
|
||||
}
|
||||
94
app/Http/Controllers/Api/Blog/TagController.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Blog;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Blog\StoreTagRequest;
|
||||
use App\Http\Requests\Blog\UpdateTagRequest;
|
||||
use App\Http\Resources\TagResource;
|
||||
use App\Models\Blog\Tag;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="Tags", description="Blog tag management")
|
||||
*/
|
||||
class TagController extends Controller
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/blog/tags",
|
||||
* tags={"Tags"},
|
||||
* summary="Paginated list of tags",
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$tags = Tag::with('posts')->paginate(10);
|
||||
return TagResource::collection($tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/blog/tags/{id}",
|
||||
* tags={"Tags"},
|
||||
* summary="Get a tag by ID",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$tag = Tag::with('posts')->findOrFail($id);
|
||||
return new TagResource($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/blog/tags",
|
||||
* tags={"Tags"},
|
||||
* summary="Create a tag",
|
||||
* @OA\RequestBody(@OA\MediaType(mediaType="application/json")),
|
||||
* @OA\Response(response=201, description="Created")
|
||||
* )
|
||||
*/
|
||||
public function store(StoreTagRequest $request)
|
||||
{
|
||||
$tag = Tag::create($request->validated());
|
||||
return new TagResource($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/blog/tags/{id}",
|
||||
* tags={"Tags"},
|
||||
* summary="Update a tag",
|
||||
* @OA\Parameter(name="id", in="path", required=True, @OA\Schema(type="integer")),
|
||||
* @OA\RequestBody(@OA\MediaType(mediaType="application/json")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function update(UpdateTagRequest $request, $id)
|
||||
{
|
||||
$tag = Tag::findOrFail($id);
|
||||
$tag->update($request->validated());
|
||||
return new TagResource($tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/blog/tags/{id}",
|
||||
* tags={"Tags"},
|
||||
* summary="Delete a tag",
|
||||
* @OA\Parameter(name="id", in="path", required=True, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
$tag = Tag::findOrFail($id);
|
||||
$tag->posts()->detach();
|
||||
$tag->delete();
|
||||
return response()->json(['message' => 'Tag deleted successfully'], 200);
|
||||
}
|
||||
}
|
||||
131
app/Http/Controllers/Api/Website/AboutMeController.php
Normal file
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Website;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Website\StoreAboutMeRequest;
|
||||
use App\Http\Requests\Website\UpdateAboutMeRequest;
|
||||
use App\Models\Website\AboutMe;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="About Me", description="About Me section endpoints")
|
||||
*/
|
||||
class AboutMeController extends Controller
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/website/about-me",
|
||||
* tags={"About Me"},
|
||||
* summary="Get the first About Me entry with skills",
|
||||
* @OA\Response(response=200, description="OK", @OA\JsonContent(ref="#/components/schemas/AboutMe"))
|
||||
* )
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$aboutMe = AboutMe::with('skills')->first();
|
||||
return response()->json($aboutMe);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/website/about-me/{id}",
|
||||
* tags={"About Me"},
|
||||
* summary="Get a specific About Me entry",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
$aboutMe = AboutMe::with('skills')->findOrFail($id);
|
||||
return response()->json($aboutMe);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/website/about-me",
|
||||
* tags={"About Me"},
|
||||
* summary="Create an About Me entry",
|
||||
* @OA\RequestBody(@OA\MediaType(mediaType="application/json")),
|
||||
* @OA\Response(response=201, description="Created")
|
||||
* )
|
||||
*/
|
||||
public function store(StoreAboutMeRequest $request)
|
||||
{
|
||||
$aboutMe = AboutMe::create($request->validated());
|
||||
return response()->json($aboutMe, 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/website/about-me/{id}",
|
||||
* tags={"About Me"},
|
||||
* summary="Update an About Me entry",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\RequestBody(@OA\MediaType(mediaType="application/json")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function update(UpdateAboutMeRequest $request, $id)
|
||||
{
|
||||
$aboutMe = AboutMe::findOrFail($id);
|
||||
$aboutMe->update($request->validated());
|
||||
return response()->json($aboutMe);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/website/about-me/{id}",
|
||||
* tags={"About Me"},
|
||||
* summary="Delete an About Me entry",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=204, description="No Content")
|
||||
* )
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
AboutMe::destroy($id);
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/website/about-me/{id}/translation/{locale}",
|
||||
* tags={"About Me"},
|
||||
* summary="Get localized About Me with skills",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Parameter(name="locale", in="path", required=true, @OA\Schema(type="string", enum={"fa","en"})),
|
||||
* @OA\Response(response=200, description="OK", @OA\JsonContent(ref="#/components/schemas/AboutMe")),
|
||||
* @OA\Response(response=404, description="Translation not found")
|
||||
* )
|
||||
*/
|
||||
public function getTranslation($id, $locale)
|
||||
{
|
||||
$aboutMe = AboutMe::with('skills')->findOrFail($id);
|
||||
|
||||
if (!$aboutMe->hasTranslation('title', $locale)) {
|
||||
return response()->json(['message' => 'Translation not found', 'locale' => $locale], 404);
|
||||
}
|
||||
|
||||
$skills = $aboutMe->skills->map(function ($skill) use ($locale) {
|
||||
return [
|
||||
'id' => $skill->id,
|
||||
'title' => $skill->getTranslation('title', $locale),
|
||||
'description' => $skill->getTranslation('description', $locale),
|
||||
'image' => $skill->image,
|
||||
'link' => $skill->link,
|
||||
'percentage' => $skill->percentage,
|
||||
];
|
||||
});
|
||||
|
||||
$data = [
|
||||
'id' => $aboutMe->id,
|
||||
'title' => $aboutMe->getTranslation('title', $locale),
|
||||
'description' => $aboutMe->getTranslation('description', $locale),
|
||||
'image' => $aboutMe->image,
|
||||
'skills' => $skills,
|
||||
];
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
}
|
||||
158
app/Http/Controllers/Api/Website/ProjectController.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Website;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Website\StoreProjectRequest;
|
||||
use App\Http\Requests\Website\UpdateProjectRequest;
|
||||
use App\Models\Website\Project;
|
||||
use App\Models\Website\ProjectCategory;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="Projects", description="Projects section endpoints")
|
||||
*/
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/website/projects/translation/{locale}",
|
||||
* tags={"Projects"},
|
||||
* summary="List projects and categories in a locale",
|
||||
* @OA\Parameter(name="locale", in="path", required=true, @OA\Schema(type="string", enum={"fa","en"})),
|
||||
* @OA\Parameter(name="per_page", in="query", @OA\Schema(type="integer")),
|
||||
* @OA\Parameter(name="sort", in="query", @OA\Schema(type="string", enum={"asc","desc"})),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function index($locale): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
$perPage = request()->query('per_page', 10);
|
||||
|
||||
$projects = Project::with('category')->paginate($perPage);
|
||||
$formattedProjects = $projects->items();
|
||||
|
||||
$formattedProjects = array_map(function ($project) use ($locale) {
|
||||
return [
|
||||
'id' => $project->id,
|
||||
'title' => $project->getTranslation('title', $locale),
|
||||
'category' => $project->category?->getTranslation('title', $locale),
|
||||
'images' => [
|
||||
['src' => $project->image1, 'position' => '1', 'zIndex' => 1],
|
||||
['src' => $project->image2, 'position' => '2', 'zIndex' => 2],
|
||||
],
|
||||
'created_at' => $project->created_at,
|
||||
'updated_at' => $project->updated_at,
|
||||
];
|
||||
}, $formattedProjects);
|
||||
|
||||
return response()->json([
|
||||
'projects' => [
|
||||
'current_page' => $projects->currentPage(),
|
||||
'data' => $formattedProjects,
|
||||
'total' => $projects->total(),
|
||||
'last_page' => $projects->lastPage(),
|
||||
],
|
||||
'categories' => ProjectCategory::all()->map(function ($category) use ($locale) {
|
||||
return [
|
||||
'id' => $category->id,
|
||||
'title' => $category->getTranslation('title', $locale),
|
||||
'image' => $category->image,
|
||||
];
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/website/projects",
|
||||
* tags={"Projects"},
|
||||
* summary="Create a project",
|
||||
* @OA\RequestBody(@OA\MediaType(mediaType="application/json")),
|
||||
* @OA\Response(response=201, description="Created")
|
||||
* )
|
||||
*/
|
||||
public function store(StoreProjectRequest $request)
|
||||
{
|
||||
$project = Project::create($request->only(['category_id', 'image1', 'image2']));
|
||||
|
||||
foreach (['fa', 'en'] as $locale) {
|
||||
$project->setTranslation('title', $locale, $request->input("title_$locale"));
|
||||
$project->setTranslation('description', $locale, $request->input("description_$locale"));
|
||||
}
|
||||
$project->save();
|
||||
|
||||
return response()->json($project, 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/website/projects/{id}/translation/{locale}",
|
||||
* tags={"Projects"},
|
||||
* summary="Get a single project translation",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Parameter(name="locale", in="path", required=true, @OA\Schema(type="string", enum={"fa","en"})),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function show($id, $locale)
|
||||
{
|
||||
$project = Project::with('category')->findOrFail($id);
|
||||
|
||||
$formattedProject = [
|
||||
'id' => $project->id,
|
||||
'title' => $project->getTranslation('title', $locale),
|
||||
'category' => $project->category?->getTranslation('title', $locale),
|
||||
'images' => [
|
||||
['src' => $project->image1, 'position' => '1', 'zIndex' => 1],
|
||||
['src' => $project->image2, 'position' => '2', 'zIndex' => 2],
|
||||
],
|
||||
'created_at' => $project->created_at,
|
||||
'updated_at' => $project->updated_at,
|
||||
];
|
||||
|
||||
return response()->json(['project' => $formattedProject]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/website/projects/{id}",
|
||||
* tags={"Projects"},
|
||||
* summary="Update a project",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\RequestBody(@OA\MediaType(mediaType="application/json")),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function update(UpdateProjectRequest $request, $id)
|
||||
{
|
||||
$project = Project::findOrFail($id);
|
||||
$project->update($request->only(['category_id', 'image1', 'image2']));
|
||||
|
||||
foreach (['fa', 'en'] as $locale) {
|
||||
if ($request->has("title_$locale")) {
|
||||
$project->setTranslation('title', $locale, $request->input("title_$locale"));
|
||||
}
|
||||
if ($request->has("description_$locale")) {
|
||||
$project->setTranslation('description', $locale, $request->input("description_$locale"));
|
||||
}
|
||||
}
|
||||
$project->save();
|
||||
|
||||
return response()->json($project);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/website/projects/{id}",
|
||||
* tags={"Projects"},
|
||||
* summary="Delete a project",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=204, description="No Content")
|
||||
* )
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
Project::destroy($id);
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
62
app/Http/Controllers/Api/Website/ServiceController.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Website;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Website\Service;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="Services", description="Services section endpoints")
|
||||
*/
|
||||
class ServiceController extends Controller
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/website/services/{locale}",
|
||||
* tags={"Services"},
|
||||
* summary="List all services in a given locale",
|
||||
* @OA\Parameter(name="locale", in="path", required=true, @OA\Schema(type="string", enum={"fa","en"})),
|
||||
* @OA\Response(response=200, description="OK", @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Service")))
|
||||
* )
|
||||
*/
|
||||
public function index($locale)
|
||||
{
|
||||
$services = Service::all();
|
||||
|
||||
$response = $services->map(function ($service) use ($locale) {
|
||||
return [
|
||||
'id' => $service->id,
|
||||
'imageSrc' => $service->imageSrc,
|
||||
'altText' => '',
|
||||
'created_at' => $service->created_at,
|
||||
'updated_at' => $service->updated_at,
|
||||
'title' => $service->getTranslation('title', $locale),
|
||||
'description' => $service->getTranslation('description', $locale),
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/website/services/{id}/translation/{locale}",
|
||||
* tags={"Services"},
|
||||
* summary="Get a single service translation",
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Parameter(name="locale", in="path", required=true, @OA\Schema(type="string", enum={"fa","en"})),
|
||||
* @OA\Response(response=200, description="OK")
|
||||
* )
|
||||
*/
|
||||
public function showTranslation($id, $locale)
|
||||
{
|
||||
$service = Service::findOrFail($id);
|
||||
|
||||
return response()->json([
|
||||
'id' => $service->id,
|
||||
'imageSrc' => $service->imageSrc,
|
||||
'title' => $service->getTranslation('title', $locale),
|
||||
'description' => $service->getTranslation('description', $locale),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -1,168 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Blog;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\CategoryResource;
|
||||
use App\Http\Resources\PostResource;
|
||||
use App\Models\Blog\Category;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class CategoryController extends Controller
|
||||
{
|
||||
// نمایش لیست دستهبندیها با صفحهبندی
|
||||
public function index(Request $request)
|
||||
{
|
||||
// گرفتن فیلتر و سورت از درخواست کاربر
|
||||
$filter = $request->input('filter'); // فیلتر (به عنوان مثال، نام دستهبندی)
|
||||
$sortBy = $request->input('sort_by', 'created_at'); // مرتبسازی (پیشفرض بر اساس زمان ایجاد)
|
||||
$sortDirection = $request->input('sort_direction', 'desc'); // جهت مرتبسازی (پیشفرض نزولی)
|
||||
$perPage = $request->input('per_page', 10); // تعداد آیتمها در هر صفحه (پیشفرض ۱۰)
|
||||
|
||||
// شروع کوئری با مدل دستهبندی و روابط
|
||||
$query = Category::with('posts');
|
||||
|
||||
// اعمال فیلتر در صورت موجود بودن
|
||||
if (!empty($filter)) {
|
||||
$query->where('name', 'like', '%' . $filter . '%');
|
||||
}
|
||||
|
||||
// اعمال مرتبسازی
|
||||
$query->orderBy($sortBy, $sortDirection);
|
||||
|
||||
// گرفتن نتایج با پجینیشن
|
||||
$categories = $query->paginate($perPage);
|
||||
|
||||
// بازگشت دادهها با استفاده از ریسورس
|
||||
return CategoryResource::collection($categories);
|
||||
}
|
||||
|
||||
// نمایش یک دستهبندی خاص
|
||||
public function show($id)
|
||||
{
|
||||
$category = Category::with('posts')->findOrFail($id);
|
||||
return new CategoryResource($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* نمایش دستهبندی و پستهای مرتبط با آن بر اساس slug
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param string $slug
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function getCategoryWithPosts(Request $request, $slug)
|
||||
{
|
||||
// یافتن دستهبندی بر اساس slug
|
||||
$category = Category::where('slug', $slug)->first();
|
||||
|
||||
if (!$category) {
|
||||
return response()->json([
|
||||
'message' => 'دستهبندی مورد نظر یافت نشد.'
|
||||
], 404);
|
||||
}
|
||||
|
||||
// دریافت تعداد پستها در هر صفحه (پیشفرض: ۱۰)
|
||||
$perPage = $request->input('per_page', 10);
|
||||
|
||||
// دریافت فیلتر و سورت از درخواست
|
||||
$filter = $request->input('filter'); // فیلتر (به عنوان مثال، عنوان پست)
|
||||
$sortBy = $request->input('sort_by', 'published_at'); // مرتبسازی (پیشفرض بر اساس تاریخ انتشار)
|
||||
$sortDirection = $request->input('sort_direction', 'desc'); // جهت مرتبسازی (پیشفرض نزولی)
|
||||
|
||||
// شروع کوئری با پستهای مربوط به دستهبندی
|
||||
$postsQuery = $category->posts()->with(['category', 'tags', 'author']);
|
||||
|
||||
// اعمال فیلتر در صورت موجود بودن
|
||||
if (!empty($filter)) {
|
||||
$postsQuery->where('title', 'like', '%' . $filter . '%');
|
||||
}
|
||||
|
||||
// اعمال مرتبسازی
|
||||
$postsQuery->orderBy($sortBy, $sortDirection);
|
||||
|
||||
// گرفتن پستها با پجینیشن
|
||||
$posts = $postsQuery->paginate($perPage);
|
||||
|
||||
// ساخت meta و links برای pagination
|
||||
$meta = [
|
||||
'total' => $posts->total(),
|
||||
'per_page' => $posts->perPage(),
|
||||
'current_page' => $posts->currentPage(),
|
||||
'last_page' => $posts->lastPage(),
|
||||
'from' => $posts->firstItem(),
|
||||
'to' => $posts->lastItem(),
|
||||
];
|
||||
|
||||
$links = [
|
||||
'first' => $posts->url(1),
|
||||
'last' => $posts->url($posts->lastPage()),
|
||||
'prev' => $posts->previousPageUrl(),
|
||||
'next' => $posts->nextPageUrl(),
|
||||
];
|
||||
|
||||
// بازگشت اطلاعات دستهبندی و پستها
|
||||
return response()->json([
|
||||
'category' => new CategoryResource($category),
|
||||
'posts' => PostResource::collection($posts),
|
||||
'meta' => $meta,
|
||||
'links' => $links,
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ذخیره یک دستهبندی جدید
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255|unique:categories,name',
|
||||
'slug' => 'required|string|unique:categories,slug',
|
||||
'description' => 'required|string',
|
||||
'short_description' => 'nullable|string|max:500',
|
||||
'image' => 'nullable|url',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json($validator->errors(), 422);
|
||||
}
|
||||
|
||||
$category = Category::create($validator->validated());
|
||||
|
||||
return new CategoryResource($category);
|
||||
}
|
||||
|
||||
// بهروزرسانی یک دستهبندی خاص
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$category = Category::findOrFail($id);
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'sometimes|required|string|max:255|unique:categories,name,' . $category->id,
|
||||
'slug' => 'sometimes|required|string|unique:categories,slug,' . $category->id,
|
||||
'description' => 'sometimes|required|string',
|
||||
'short_description' => 'nullable|string|max:500',
|
||||
'image' => 'nullable|url',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json($validator->errors(), 422);
|
||||
}
|
||||
|
||||
$category->update($validator->validated());
|
||||
|
||||
return new CategoryResource($category);
|
||||
}
|
||||
|
||||
// حذف یک دستهبندی خاص
|
||||
public function destroy($id)
|
||||
{
|
||||
$category = Category::findOrFail($id);
|
||||
// قبل از حذف، مطمئن شوید که پستها به دستهبندی دیگری منتقل شدهاند یا حذف شوند
|
||||
// برای سادگی، اینجا فقط دستهبندی را حذف میکنیم
|
||||
$category->delete();
|
||||
|
||||
return response()->json(['message' => 'Category deleted successfully'], 200);
|
||||
}
|
||||
}
|
||||
@ -1,127 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Blog;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\PostResource;
|
||||
use App\Models\Blog\Post;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class PostController extends Controller
|
||||
{
|
||||
// نمایش لیست پستها با فیلتر و صفحهبندی
|
||||
public function index(Request $request)
|
||||
{
|
||||
// دریافت پارامترهای فیلتر از درخواست
|
||||
$filters = $request->only(['category', 'tag', 'search', 'author', 'published']);
|
||||
|
||||
// دریافت پارامترهای مرتبسازی از درخواست
|
||||
$sortField = $request->get('sort_field', 'published_at'); // فیلد پیشفرض: published_at
|
||||
$sortDirection = $request->get('sort_direction', 'desc'); // جهت پیشفرض: نزولی
|
||||
|
||||
// دریافت پارامترهای صفحهبندی از درخواست
|
||||
$perPage = $request->get('per_page', 10); // تعداد آیتمها در هر صفحه (پیشفرض: ۱۰)
|
||||
|
||||
// ساخت Query با اعمال فیلترها و مرتبسازی
|
||||
$query = Post::with(['category', 'tags', 'author'])
|
||||
->filter($filters)
|
||||
->sort($sortField, $sortDirection);
|
||||
|
||||
// اعمال صفحهبندی
|
||||
$posts = $query->paginate($perPage);
|
||||
|
||||
// بازگشت نتایج به صورت JSON با استفاده از Resource
|
||||
return PostResource::collection($posts);
|
||||
}
|
||||
|
||||
// نمایش یک پست خاص
|
||||
public function show($slug)
|
||||
{
|
||||
// یافتن پست بر اساس slug و بارگذاری روابط مرتبط
|
||||
$post = Post::with(['category', 'tags', 'author', 'relatedPosts.category', 'relatedPosts.tags', 'relatedPosts.author'])
|
||||
->where('slug', $slug)
|
||||
->firstOrFail(); // در صورت عدم وجود پست، خطای 404 برمیگرداند
|
||||
|
||||
// بازگشت پست و پستهای مرتبط به صورت JSON با استفاده از منابع
|
||||
return response()->json([
|
||||
'post' => new PostResource($post),
|
||||
'related_posts' => PostResource::collection($post->relatedPosts->sortByDesc('published_at')->take(5)), // بازگشت ۵ پست مرتبط جدیدترین
|
||||
], 200);
|
||||
}
|
||||
|
||||
// ذخیره یک پست جدید
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required|string|max:255',
|
||||
'body' => 'required|string',
|
||||
'excerpt' => 'nullable|string|max:500',
|
||||
'slug' => 'required|string|unique:posts,slug',
|
||||
'author_id' => 'required|exists:users,id',
|
||||
'category_id' => 'required|exists:categories,id',
|
||||
'featured_image' => 'nullable|url',
|
||||
'views' => 'nullable|integer',
|
||||
'likes' => 'nullable|integer',
|
||||
'published_at' => 'nullable|date',
|
||||
'tags' => 'nullable|array',
|
||||
'tags.*' => 'exists:tags,id',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json($validator->errors(), 422);
|
||||
}
|
||||
|
||||
$post = Post::create($validator->validated());
|
||||
|
||||
if ($request->has('tags')) {
|
||||
$post->tags()->attach($request->tags);
|
||||
}
|
||||
|
||||
return new PostResource($post->load(['category', 'tags', 'author']));
|
||||
}
|
||||
|
||||
// بهروزرسانی یک پست خاص
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$post = Post::findOrFail($id);
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'sometimes|required|string|max:255',
|
||||
'body' => 'sometimes|required|string',
|
||||
'excerpt' => 'nullable|string|max:500',
|
||||
'slug' => 'sometimes|required|string|unique:posts,slug,' . $post->id,
|
||||
'author_id' => 'sometimes|required|exists:users,id',
|
||||
'category_id' => 'sometimes|required|exists:categories,id',
|
||||
'featured_image' => 'nullable|url',
|
||||
'views' => 'nullable|integer',
|
||||
'likes' => 'nullable|integer',
|
||||
'published_at' => 'nullable|date',
|
||||
'tags' => 'nullable|array',
|
||||
'tags.*' => 'exists:tags,id',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json($validator->errors(), 422);
|
||||
}
|
||||
|
||||
$post->update($validator->validated());
|
||||
|
||||
if ($request->has('tags')) {
|
||||
$post->tags()->sync($request->tags);
|
||||
}
|
||||
|
||||
return new PostResource($post->load(['category', 'tags', 'author']));
|
||||
}
|
||||
|
||||
// حذف یک پست خاص
|
||||
public function destroy($id)
|
||||
{
|
||||
$post = Post::findOrFail($id);
|
||||
$post->tags()->detach();
|
||||
$post->relatedPosts()->detach();
|
||||
$post->delete();
|
||||
|
||||
return response()->json(['message' => 'Post deleted successfully'], 200);
|
||||
}
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Blog;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\TagResource;
|
||||
use App\Models\Blog\Tag;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class TagController extends Controller
|
||||
{
|
||||
// نمایش لیست تگها با صفحهبندی
|
||||
public function index(Request $request)
|
||||
{
|
||||
$tags = Tag::with('posts')->paginate(10);
|
||||
return TagResource::collection($tags);
|
||||
}
|
||||
|
||||
// نمایش یک تگ خاص
|
||||
public function show($id)
|
||||
{
|
||||
$tag = Tag::with('posts')->findOrFail($id);
|
||||
return new TagResource($tag);
|
||||
}
|
||||
|
||||
// ذخیره یک تگ جدید
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255|unique:tags,name',
|
||||
'slug' => 'required|string|unique:tags,slug',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json($validator->errors(), 422);
|
||||
}
|
||||
|
||||
$tag = Tag::create($validator->validated());
|
||||
|
||||
return new TagResource($tag);
|
||||
}
|
||||
|
||||
// بهروزرسانی یک تگ خاص
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$tag = Tag::findOrFail($id);
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'sometimes|required|string|max:255|unique:tags,name,' . $tag->id,
|
||||
'slug' => 'sometimes|required|string|unique:tags,slug,' . $tag->id,
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json($validator->errors(), 422);
|
||||
}
|
||||
|
||||
$tag->update($validator->validated());
|
||||
|
||||
return new TagResource($tag);
|
||||
}
|
||||
|
||||
// حذف یک تگ خاص
|
||||
public function destroy($id)
|
||||
{
|
||||
$tag = Tag::findOrFail($id);
|
||||
$tag->posts()->detach();
|
||||
$tag->delete();
|
||||
|
||||
return response()->json(['message' => 'Tag deleted successfully'], 200);
|
||||
}
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Project;
|
||||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$projects = Project::query();
|
||||
|
||||
// 1. اگر category مشخص شده باشد
|
||||
if ($request->has('category')) {
|
||||
$category = $request->input('category');
|
||||
// فیلتر کردن پروژهها بر اساس category با استفاده از LIKE
|
||||
$projects = $projects->where('category', 'like', '%' . $category . '%');
|
||||
}
|
||||
|
||||
// 2. انتخاب نوع مرتبسازی (جدیدترین یا قدیمیترین)
|
||||
$sortOrder = $request->input('sort', 'desc'); // 'desc' برای جدیدترین و 'asc' برای قدیمیترین
|
||||
$projects = $projects->orderBy('created_at', $sortOrder);
|
||||
|
||||
// 3. پیادهسازی pagination
|
||||
$perPage = $request->input('per_page', 10); // تعداد پروژهها در هر صفحه
|
||||
$paginatedProjects = $projects->paginate($perPage);
|
||||
|
||||
// 4. دریافت تمام categories موجود از ستون category
|
||||
$categories = Project::distinct()->pluck('category'); // گرفتن دستهبندیهای یکتا
|
||||
|
||||
return response()->json([
|
||||
'projects' => $paginatedProjects,
|
||||
'categories' => $categories,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
// برگرداندن ویو فرم ایجاد پروژه (اگر نیاز است)
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$project = Project::create($request->all());
|
||||
return response()->json($project, 201);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$project = Project::findOrFail($id);
|
||||
return response()->json($project);
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
// برگرداندن ویو فرم ویرایش پروژه (اگر نیاز است)
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$project = Project::findOrFail($id);
|
||||
$project->update($request->all());
|
||||
return response()->json($project);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
Project::destroy($id);
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Service;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class ServiceController extends Controller
|
||||
{
|
||||
// نمایش لیست تمامی سرویسها
|
||||
public function index()
|
||||
{
|
||||
return response()->json(Service::all(), Response::HTTP_OK);
|
||||
}
|
||||
|
||||
// نمایش یک سرویس خاص بر اساس ID
|
||||
public function show($id)
|
||||
{
|
||||
$service = Service::find($id);
|
||||
|
||||
if (!$service) {
|
||||
return response()->json(['message' => 'Service not found'], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return response()->json($service, Response::HTTP_OK);
|
||||
}
|
||||
|
||||
// ایجاد یک سرویس جدید
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validatedData = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'required|string',
|
||||
'imageSrc' => 'required|string|max:255',
|
||||
'altText' => 'required|string|max:255',
|
||||
]);
|
||||
|
||||
$service = Service::create($validatedData);
|
||||
|
||||
return response()->json($service, Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
// بروزرسانی یک سرویس موجود
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$service = Service::find($id);
|
||||
|
||||
if (!$service) {
|
||||
return response()->json(['message' => 'Service not found'], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$validatedData = $request->validate([
|
||||
'title' => 'string|max:255',
|
||||
'description' => 'string',
|
||||
'imageSrc' => 'string|max:255',
|
||||
'altText' => 'string|max:255',
|
||||
]);
|
||||
|
||||
$service->update(array_filter($validatedData));
|
||||
|
||||
return response()->json($service, Response::HTTP_OK);
|
||||
}
|
||||
|
||||
// حذف یک سرویس
|
||||
public function destroy($id)
|
||||
{
|
||||
$service = Service::find($id);
|
||||
|
||||
if (!$service) {
|
||||
return response()->json(['message' => 'Service not found'], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$service->delete();
|
||||
|
||||
return response()->json(['message' => 'Service deleted successfully'], Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Skill;
|
||||
|
||||
class SkillController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return response()->json(Skill::all());
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
return response()->json(Skill::findOrFail($id));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$skill = Skill::create($request->all());
|
||||
return response()->json($skill, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$skill = Skill::findOrFail($id);
|
||||
$skill->update($request->all());
|
||||
return response()->json($skill);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
Skill::destroy($id);
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Website;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Website\AboutMe;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class AboutMeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$aboutMe = AboutMe::with('skills.translations')->first(); // بارگذاری مهارتها و ترجمهها
|
||||
return response()->json($aboutMe);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$aboutMe = AboutMe::with('skills.translations')->findOrFail($id);
|
||||
return response()->json($aboutMe);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$aboutMe = AboutMe::create($request->all());
|
||||
return response()->json($aboutMe, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$aboutMe = AboutMe::findOrFail($id);
|
||||
$aboutMe->update($request->all());
|
||||
return response()->json($aboutMe);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
AboutMe::destroy($id);
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
// متد جدید برای دریافت ترجمه بر اساس locale از کوکی
|
||||
public function getTranslation($id, $locale)
|
||||
{
|
||||
// بارگذاری AboutMe با ترجمههای مربوط به locale و مهارتها
|
||||
$aboutMe = AboutMe::with(['translations' => function ($query) use ($locale) {
|
||||
$query->where('locale', $locale);
|
||||
}, 'skills.translations' => function ($query) use ($locale) {
|
||||
$query->where('locale', $locale);
|
||||
}])->findOrFail($id);
|
||||
|
||||
// اگر ترجمه موجود نیست، میتوانید ترجمه پیشفرض یا خطا را برگردانید
|
||||
if ($aboutMe->translations->isEmpty()) {
|
||||
return response()->json(['message' => 'Translation not found', 'locale' => $locale], 404);
|
||||
}
|
||||
|
||||
// ترکیب اطلاعات AboutMe و ترجمه
|
||||
$translation = $aboutMe->translations->first(); // فقط یک ترجمه دریافت میکنیم
|
||||
$skills = $aboutMe->skills->map(function ($skill) use ($locale) {
|
||||
$translation = $skill->getTranslation($locale); // گرفتن ترجمه برای هر مهارت
|
||||
|
||||
return [
|
||||
'id' => $skill->id,
|
||||
'title' => $translation ? $translation->title : null, // عنوان از ترجمه
|
||||
'description' => $translation ? $translation->description : null, // توضیحات از ترجمه
|
||||
'image' => $skill->image, // تصویر
|
||||
'link' => $skill->link, // لینک
|
||||
'percentage' => $skill->percentage, // درصد
|
||||
];
|
||||
});
|
||||
|
||||
$data = [
|
||||
'id' => $aboutMe->id,
|
||||
'title' => $translation->title,
|
||||
'description' => $translation->description,
|
||||
'image' => $aboutMe->image,
|
||||
'skills' => $skills, // مهارتها اکنون بارگذاری شدهاند
|
||||
];
|
||||
|
||||
return response()->json($data);
|
||||
}
|
||||
}
|
||||
@ -1,119 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\website;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Website\Project;
|
||||
use App\Models\Website\ProjectCategory;
|
||||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
public function index($locale): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
// Read per_page from query string or set a default value
|
||||
$perPage = request()->query('per_page', 10); // مقدار پیشفرض ۱۰ در نظر گرفته میشود
|
||||
|
||||
// Retrieve projects with translations and categories
|
||||
$projects = Project::with('translations', 'category.translations')->paginate($perPage);
|
||||
$formattedProjects = $projects->items();
|
||||
|
||||
$formattedProjects = array_map(function ($project) use ($locale) {
|
||||
$translation = $project->translations->firstWhere('locale', $locale);
|
||||
$categoryTranslation = $project->category->translations->firstWhere('locale', $locale);
|
||||
|
||||
return [
|
||||
'id' => $project->id,
|
||||
'title' => $translation ? $translation->title : null,
|
||||
'category' => $categoryTranslation ? $categoryTranslation->title : null,
|
||||
'images' => [
|
||||
['src' => $project->image1, 'position' => '1', 'zIndex' => 1],
|
||||
['src' => $project->image2, 'position' => '2', 'zIndex' => 2],
|
||||
],
|
||||
'created_at' => $project->created_at,
|
||||
'updated_at' => $project->updated_at,
|
||||
];
|
||||
}, $formattedProjects);
|
||||
|
||||
return response()->json([
|
||||
'projects' => [
|
||||
'current_page' => $projects->currentPage(),
|
||||
'data' => $formattedProjects,
|
||||
'total' => $projects->total(),
|
||||
'last_page' => $projects->lastPage(),
|
||||
],
|
||||
'categories' => ProjectCategory::all()->map(function ($category) use ($locale) {
|
||||
return [
|
||||
'id' => $category->id,
|
||||
'title' => $category->translations->firstWhere('locale', $locale)->title ?? null,
|
||||
'image' => $category->image,
|
||||
];
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$project = Project::create($request->only(['category_id', 'image1', 'image2']));
|
||||
|
||||
// ایجاد ترجمهها
|
||||
foreach (['fa', 'en'] as $locale) {
|
||||
$project->translations()->create([
|
||||
'locale' => $locale,
|
||||
'title' => $request->input("title_$locale"),
|
||||
'description' => $request->input("description_$locale"),
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json($project->load('translations'), 201);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$project = Project::with('translations', 'category.translations')->findOrFail($id);
|
||||
|
||||
$translation = $project->translations->firstWhere('locale', 'fa');
|
||||
$categoryTranslation = $project->category->translations->firstWhere('locale', 'fa');
|
||||
|
||||
$formattedProject = [
|
||||
'id' => $project->id,
|
||||
'title' => $translation ? $translation->title : null,
|
||||
'category' => $categoryTranslation ? $categoryTranslation->title : null,
|
||||
'images' => [
|
||||
['src' => $project->image1, 'position' => '1', 'zIndex' => 1],
|
||||
['src' => $project->image2, 'position' => '2', 'zIndex' => 2],
|
||||
],
|
||||
'created_at' => $project->created_at,
|
||||
'updated_at' => $project->updated_at,
|
||||
];
|
||||
|
||||
return response()->json(['project' => $formattedProject]);
|
||||
}
|
||||
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$project = Project::findOrFail($id);
|
||||
$project->update($request->only(['category_id', 'image1', 'image2']));
|
||||
|
||||
// بهروزرسانی ترجمهها
|
||||
foreach (['fa', 'en'] as $locale) {
|
||||
$project->translations()->updateOrCreate(
|
||||
['locale' => $locale],
|
||||
[
|
||||
'title' => $request->input("title_$locale"),
|
||||
'description' => $request->input("description_$locale"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return response()->json($project->load('translations'));
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
Project::destroy($id);
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Website;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Website\Service;
|
||||
|
||||
class ServiceController extends Controller
|
||||
{
|
||||
/**
|
||||
* دریافت لیست خدمات با ترجمهها بر اساس زبان
|
||||
*/
|
||||
public function index($locale)
|
||||
{
|
||||
// گرفتن همه خدمات به همراه ترجمهها
|
||||
$services = Service::with(['translations' => function ($query) use ($locale) {
|
||||
$query->where('locale', $locale);
|
||||
}])->get();
|
||||
|
||||
// فرمتی که میخواهیم به کاربر ارسال کنیم
|
||||
$response = $services->map(function ($service) use ($locale) {
|
||||
return [
|
||||
'id' => $service->id,
|
||||
'imageSrc' => $service->imageSrc,
|
||||
'altText' => $service->translations->first()->altText ?? '',
|
||||
'created_at' => $service->created_at,
|
||||
'updated_at' => $service->updated_at,
|
||||
'title' => optional($service->translations->first())->title,
|
||||
'description' => optional($service->translations->first())->description,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* نمایش یک خدمت خاص با ترجمه
|
||||
*/
|
||||
public function showTranslation($id, $locale)
|
||||
{
|
||||
$service = Service::with('translations')->findOrFail($id);
|
||||
|
||||
return response()->json([
|
||||
'id' => $service->id,
|
||||
'imageSrc' => $service->imageSrc,
|
||||
'title' => $service->getTitle($locale),
|
||||
'description' => $service->getDescription($locale),
|
||||
]);
|
||||
}
|
||||
}
|
||||
24
app/Http/Requests/Blog/StoreCategoryRequest.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Blog;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreCategoryRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255|unique:categories,name',
|
||||
'slug' => 'required|string|unique:categories,slug',
|
||||
'description' => 'required|string',
|
||||
'short_description' => 'nullable|string|max:500',
|
||||
'image' => 'nullable|url',
|
||||
];
|
||||
}
|
||||
}
|
||||
31
app/Http/Requests/Blog/StorePostRequest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Blog;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StorePostRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'required|string|max:255',
|
||||
'body' => 'required|string',
|
||||
'excerpt' => 'nullable|string|max:500',
|
||||
'slug' => 'required|string|unique:posts,slug',
|
||||
'author_id' => 'required|exists:users,id',
|
||||
'category_id' => 'required|exists:categories,id',
|
||||
'featured_image' => 'nullable|url',
|
||||
'views' => 'nullable|integer',
|
||||
'likes' => 'nullable|integer',
|
||||
'published_at' => 'nullable|date',
|
||||
'tags' => 'nullable|array',
|
||||
'tags.*' => 'exists:tags,id',
|
||||
];
|
||||
}
|
||||
}
|
||||
21
app/Http/Requests/Blog/StoreTagRequest.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Blog;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreTagRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255|unique:tags,name',
|
||||
'slug' => 'required|string|unique:tags,slug',
|
||||
];
|
||||
}
|
||||
}
|
||||
26
app/Http/Requests/Blog/UpdateCategoryRequest.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Blog;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateCategoryRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$categoryId = $this->route('category');
|
||||
|
||||
return [
|
||||
'name' => 'sometimes|required|string|max:255|unique:categories,name,' . $categoryId,
|
||||
'slug' => 'sometimes|required|string|unique:categories,slug,' . $categoryId,
|
||||
'description' => 'sometimes|required|string',
|
||||
'short_description' => 'nullable|string|max:500',
|
||||
'image' => 'nullable|url',
|
||||
];
|
||||
}
|
||||
}
|
||||
31
app/Http/Requests/Blog/UpdatePostRequest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Blog;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdatePostRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'sometimes|required|string|max:255',
|
||||
'body' => 'sometimes|required|string',
|
||||
'excerpt' => 'nullable|string|max:500',
|
||||
'slug' => 'sometimes|required|string|unique:posts,slug,' . $this->route('post'),
|
||||
'author_id' => 'sometimes|required|exists:users,id',
|
||||
'category_id' => 'sometimes|required|exists:categories,id',
|
||||
'featured_image' => 'nullable|url',
|
||||
'views' => 'nullable|integer',
|
||||
'likes' => 'nullable|integer',
|
||||
'published_at' => 'nullable|date',
|
||||
'tags' => 'nullable|array',
|
||||
'tags.*' => 'exists:tags,id',
|
||||
];
|
||||
}
|
||||
}
|
||||
23
app/Http/Requests/Blog/UpdateTagRequest.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Blog;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateTagRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$tagId = $this->route('tag');
|
||||
|
||||
return [
|
||||
'name' => 'sometimes|required|string|max:255|unique:tags,name,' . $tagId,
|
||||
'slug' => 'sometimes|required|string|unique:tags,slug,' . $tagId,
|
||||
];
|
||||
}
|
||||
}
|
||||
20
app/Http/Requests/Website/StoreAboutMeRequest.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Website;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreAboutMeRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'image' => 'required|string|max:255',
|
||||
];
|
||||
}
|
||||
}
|
||||
26
app/Http/Requests/Website/StoreProjectRequest.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Website;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreProjectRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'category_id' => 'required|exists:website_project_categories,id',
|
||||
'image1' => 'required|string|max:255',
|
||||
'image2' => 'required|string|max:255',
|
||||
'title_fa' => 'required|string|max:255',
|
||||
'title_en' => 'required|string|max:255',
|
||||
'description_fa' => 'required|string',
|
||||
'description_en' => 'required|string',
|
||||
];
|
||||
}
|
||||
}
|
||||
20
app/Http/Requests/Website/UpdateAboutMeRequest.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Website;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateAboutMeRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'image' => 'sometimes|required|string|max:255',
|
||||
];
|
||||
}
|
||||
}
|
||||
26
app/Http/Requests/Website/UpdateProjectRequest.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Website;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateProjectRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'category_id' => 'sometimes|required|exists:website_project_categories,id',
|
||||
'image1' => 'sometimes|required|string|max:255',
|
||||
'image2' => 'sometimes|required|string|max:255',
|
||||
'title_fa' => 'sometimes|required|string|max:255',
|
||||
'title_en' => 'sometimes|required|string|max:255',
|
||||
'description_fa' => 'sometimes|required|string',
|
||||
'description_en' => 'sometimes|required|string',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class User extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
// اضافه کردن سایر فیلدهای مورد نیاز
|
||||
'email' => $this->email,
|
||||
'created_at' => $this->created_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class UserCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return parent::toArray($request);
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AboutMe extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'about_me';
|
||||
protected $fillable = ['title', 'description', 'image'];
|
||||
|
||||
public function skills()
|
||||
{
|
||||
return $this->hasMany(Skill::class);
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Skill extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
protected $fillable = ['title', 'image', 'description', 'link', 'about_me_id'];
|
||||
|
||||
public function aboutMe()
|
||||
{
|
||||
return $this->belongsTo(AboutMe::class);
|
||||
}
|
||||
}
|
||||
@ -5,49 +5,52 @@
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
|
||||
class AboutMe extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'website_about_me';
|
||||
protected $fillable = ['image'];
|
||||
protected $fillable = ['image',
|
||||
'translations',];
|
||||
|
||||
/**
|
||||
* ارتباط با ترجمهها
|
||||
*/
|
||||
public function translations()
|
||||
{
|
||||
return $this->hasMany(AboutMeTranslation::class, 'about_me_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* گرفتن ترجمه بر اساس زبان فعلی
|
||||
*/
|
||||
public function getTranslation($locale)
|
||||
{
|
||||
return $this->translations()->where('locale', $locale)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* گرفتن عنوان بر اساس زبان فعلی
|
||||
*/
|
||||
public function getTitle($locale)
|
||||
{
|
||||
$translation = $this->getTranslation($locale);
|
||||
return $translation ? $translation->title : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* گرفتن توضیحات بر اساس زبان فعلی
|
||||
*/
|
||||
public function getDescription($locale)
|
||||
{
|
||||
$translation = $this->getTranslation($locale);
|
||||
return $translation ? $translation->description : null;
|
||||
}
|
||||
|
||||
public function skills()
|
||||
{
|
||||
return $this->hasMany(Skill::class);
|
||||
}
|
||||
|
||||
protected $casts = [
|
||||
'translations' => 'array',
|
||||
];
|
||||
|
||||
public function getTranslation(string $key, string $locale, bool $useFallbackLocale = true): mixed
|
||||
{
|
||||
$translations = $this->translations;
|
||||
if (isset($translations[$key][$locale])) {
|
||||
return $translations[$key][$locale];
|
||||
}
|
||||
if ($useFallbackLocale && isset($translations[$key]['en'])) {
|
||||
return $translations[$key]['en'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function hasTranslation(string $key, string $locale): bool
|
||||
{
|
||||
$translations = $this->translations;
|
||||
return isset($translations[$key][$locale]);
|
||||
}
|
||||
|
||||
public function getTitleAttribute()
|
||||
{
|
||||
return $this->translations['title'] ?? null;
|
||||
}
|
||||
|
||||
public function getDescriptionAttribute()
|
||||
{
|
||||
return $this->translations['description'] ?? null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Website;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AboutMeTranslation extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'website_about_me_translations';
|
||||
protected $fillable = ['about_me_id', 'locale', 'title', 'description'];
|
||||
|
||||
/**
|
||||
* ارتباط با AboutMe
|
||||
*/
|
||||
public function aboutMe()
|
||||
{
|
||||
return $this->belongsTo(AboutMe::class, 'about_me_id');
|
||||
}
|
||||
}
|
||||
@ -5,20 +5,46 @@
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
|
||||
class Project extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'website_projects'; // نام صحیح جدول
|
||||
protected $fillable = ['category_id', 'image1', 'image2'];
|
||||
protected $table = 'website_projects';
|
||||
protected $fillable = ['category_id', 'image1', 'image2',
|
||||
'translations',];
|
||||
|
||||
|
||||
public function translations()
|
||||
{
|
||||
return $this->hasMany(ProjectTranslation::class, 'project_id');
|
||||
}
|
||||
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(ProjectCategory::class, 'category_id');
|
||||
}
|
||||
|
||||
protected $casts = [
|
||||
'translations' => 'array',
|
||||
];
|
||||
|
||||
public function getTranslation(string $key, string $locale, bool $useFallbackLocale = true): mixed
|
||||
{
|
||||
$translations = $this->translations;
|
||||
if (isset($translations[$key][$locale])) {
|
||||
return $translations[$key][$locale];
|
||||
}
|
||||
if ($useFallbackLocale && isset($translations[$key]['en'])) {
|
||||
return $translations[$key]['en'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getTitleAttribute()
|
||||
{
|
||||
return $this->translations['title'] ?? null;
|
||||
}
|
||||
|
||||
public function getDescriptionAttribute()
|
||||
{
|
||||
return $this->translations['description'] ?? null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,15 +5,41 @@
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
|
||||
class ProjectCategory extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'website_project_categories'; // نام صحیح جدول
|
||||
protected $fillable = ['image'];
|
||||
protected $table = 'website_project_categories';
|
||||
protected $fillable = ['image',
|
||||
'translations',];
|
||||
|
||||
public function translations()
|
||||
|
||||
|
||||
protected $casts = [
|
||||
'translations' => 'array',
|
||||
];
|
||||
|
||||
public function getTranslation(string $key, string $locale, bool $useFallbackLocale = true): mixed
|
||||
{
|
||||
return $this->hasMany(ProjectCategoryTranslation::class, 'category_id');
|
||||
$translations = $this->translations;
|
||||
if (isset($translations[$key][$locale])) {
|
||||
return $translations[$key][$locale];
|
||||
}
|
||||
if ($useFallbackLocale && isset($translations[$key]['en'])) {
|
||||
return $translations[$key]['en'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getTitleAttribute()
|
||||
{
|
||||
return $this->translations['title'] ?? null;
|
||||
}
|
||||
|
||||
public function getDescriptionAttribute()
|
||||
{
|
||||
return $this->translations['description'] ?? null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Website;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProjectCategoryTranslation extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'website_project_category_translations'; // نام صحیح جدول
|
||||
protected $fillable = ['category_id', 'locale', 'title', 'description'];
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Website;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProjectTranslation extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'website_project_translations'; // نام صحیح جدول
|
||||
protected $fillable = ['project_id', 'locale', 'title', 'description'];
|
||||
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class, 'project_id');
|
||||
}
|
||||
}
|
||||
@ -9,40 +9,33 @@ class Service extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'website_services'; // نام جدول
|
||||
protected $fillable = ['imageSrc']; // فیلدهایی که قابل پر کردن هستند
|
||||
protected $table = 'website_services';
|
||||
protected $fillable = ['imageSrc',
|
||||
'translations'];
|
||||
|
||||
/**
|
||||
* ارتباط با جدول ترجمهها
|
||||
*/
|
||||
public function translations()
|
||||
protected $casts = [
|
||||
'translations' => 'array',
|
||||
];
|
||||
|
||||
public function getTranslation(string $key, string $locale, bool $useFallbackLocale = true): mixed
|
||||
{
|
||||
return $this->hasMany(ServiceTranslation::class, 'service_id');
|
||||
$translations = $this->translations;
|
||||
if (isset($translations[$key][$locale])) {
|
||||
return $translations[$key][$locale];
|
||||
}
|
||||
if ($useFallbackLocale && isset($translations[$key]['en'])) {
|
||||
return $translations[$key]['en'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* گرفتن ترجمه بر اساس زبان فعلی
|
||||
*/
|
||||
public function getTranslation($locale)
|
||||
public function getTitleAttribute()
|
||||
{
|
||||
return $this->translations()->where('locale', $locale)->first();
|
||||
return $this->translations['title'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* گرفتن عنوان بر اساس زبان فعلی
|
||||
*/
|
||||
public function getTitle($locale)
|
||||
public function getDescriptionAttribute()
|
||||
{
|
||||
$translation = $this->getTranslation($locale);
|
||||
return $translation ? $translation->title : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* گرفتن توضیحات بر اساس زبان فعلی
|
||||
*/
|
||||
public function getDescription($locale)
|
||||
{
|
||||
$translation = $this->getTranslation($locale);
|
||||
return $translation ? $translation->description : null;
|
||||
return $this->translations['description'] ?? null;
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Website;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ServiceTranslation extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'website_service_translations'; // نام جدول
|
||||
protected $fillable = ['service_id', 'locale', 'title', 'description']; // فیلدهایی که قابل پر کردن هستند
|
||||
|
||||
/**
|
||||
* ارتباط با جدول خدمات
|
||||
*/
|
||||
public function service()
|
||||
{
|
||||
return $this->belongsTo(Service::class, 'service_id');
|
||||
}
|
||||
}
|
||||
@ -5,52 +5,46 @@
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
|
||||
class Skill extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'website_skills'; // نام جدول
|
||||
protected $fillable = ['image', 'link', 'about_me_id', 'percentage']; // فیلدهایی که قابل پر کردن هستند
|
||||
protected $table = 'website_skills';
|
||||
protected $fillable = ['image', 'link', 'about_me_id', 'percentage',
|
||||
'translations',];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* ارتباط با جدول AboutMe
|
||||
*/
|
||||
public function aboutMe()
|
||||
{
|
||||
return $this->belongsTo(AboutMe::class, 'about_me_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* ارتباط با جدول ترجمهها
|
||||
*/
|
||||
public function translations()
|
||||
protected $casts = [
|
||||
'translations' => 'array',
|
||||
];
|
||||
|
||||
public function getTranslation(string $key, string $locale, bool $useFallbackLocale = true): mixed
|
||||
{
|
||||
return $this->hasMany(SkillTranslation::class, 'skill_id');
|
||||
$translations = $this->translations;
|
||||
if (isset($translations[$key][$locale])) {
|
||||
return $translations[$key][$locale];
|
||||
}
|
||||
if ($useFallbackLocale && isset($translations[$key]['en'])) {
|
||||
return $translations[$key]['en'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* گرفتن ترجمه بر اساس زبان فعلی
|
||||
*/
|
||||
public function getTranslation($locale)
|
||||
public function getTitleAttribute()
|
||||
{
|
||||
return $this->translations()->where('locale', $locale)->first();
|
||||
return $this->translations['title'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* گرفتن عنوان بر اساس زبان فعلی
|
||||
*/
|
||||
public function getTitle($locale)
|
||||
public function getDescriptionAttribute()
|
||||
{
|
||||
$translation = $this->getTranslation($locale);
|
||||
return $translation ? $translation->title : null;
|
||||
return $this->translations['description'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* گرفتن توضیحات بر اساس زبان فعلی
|
||||
*/
|
||||
public function getDescription($locale)
|
||||
{
|
||||
$translation = $this->getTranslation($locale);
|
||||
return $translation ? $translation->description : null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Website;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SkillTranslation extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'website_skill_translations'; // نام جدول
|
||||
protected $fillable = ['skill_id', 'locale', 'title', 'description']; // فیلدهایی که قابل پر کردن هستند
|
||||
|
||||
/**
|
||||
* ارتباط با جدول Skills
|
||||
*/
|
||||
public function skill()
|
||||
{
|
||||
return $this->belongsTo(Skill::class, 'skill_id');
|
||||
}
|
||||
}
|
||||
95
app/SwaggerDefinitions.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
#[OA\Info(
|
||||
title: 'Parsa Aghayi Portfolio API',
|
||||
version: '1.0.0',
|
||||
description: 'REST API for Parsa Aghayi\'s personal portfolio and blog'
|
||||
)]
|
||||
#[OA\Server(
|
||||
url: 'http://localhost:8000/api',
|
||||
description: 'Development server'
|
||||
)]
|
||||
#[OA\Server(
|
||||
url: 'https://backend.parsaaghayi.ir/api',
|
||||
description: 'Production server'
|
||||
)]
|
||||
#[OA\Schema(
|
||||
schema: 'Skill',
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'id', type: 'integer'),
|
||||
new OA\Property(property: 'title', type: 'string'),
|
||||
new OA\Property(property: 'description', type: 'string'),
|
||||
new OA\Property(property: 'image', type: 'string'),
|
||||
new OA\Property(property: 'link', type: 'string', nullable: true),
|
||||
new OA\Property(property: 'percentage', type: 'integer'),
|
||||
]
|
||||
)]
|
||||
#[OA\Schema(
|
||||
schema: 'AboutMe',
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'id', type: 'integer'),
|
||||
new OA\Property(property: 'title', type: 'string'),
|
||||
new OA\Property(property: 'description', type: 'string'),
|
||||
new OA\Property(property: 'image', type: 'string'),
|
||||
new OA\Property(property: 'skills', type: 'array', items: new OA\Items(ref: '#/components/schemas/Skill')),
|
||||
]
|
||||
)]
|
||||
#[OA\Schema(
|
||||
schema: 'Service',
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'id', type: 'integer'),
|
||||
new OA\Property(property: 'imageSrc', type: 'string'),
|
||||
new OA\Property(property: 'altText', type: 'string'),
|
||||
new OA\Property(property: 'title', type: 'string'),
|
||||
new OA\Property(property: 'description', type: 'string'),
|
||||
new OA\Property(property: 'created_at', type: 'string', format: 'datetime'),
|
||||
new OA\Property(property: 'updated_at', type: 'string', format: 'datetime'),
|
||||
]
|
||||
)]
|
||||
#[OA\Schema(
|
||||
schema: 'Project',
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'id', type: 'integer'),
|
||||
new OA\Property(property: 'title', type: 'string'),
|
||||
new OA\Property(property: 'category', type: 'string', nullable: true),
|
||||
new OA\Property(property: 'images', type: 'array', items: new OA\Items(type: 'object')),
|
||||
new OA\Property(property: 'created_at', type: 'string', format: 'datetime'),
|
||||
new OA\Property(property: 'updated_at', type: 'string', format: 'datetime'),
|
||||
]
|
||||
)]
|
||||
#[OA\Schema(
|
||||
schema: 'Category',
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'id', type: 'integer'),
|
||||
new OA\Property(property: 'title', type: 'string'),
|
||||
new OA\Property(property: 'image', type: 'string'),
|
||||
]
|
||||
)]
|
||||
#[OA\Schema(
|
||||
schema: 'Post',
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'id', type: 'integer'),
|
||||
new OA\Property(property: 'title', type: 'string'),
|
||||
new OA\Property(property: 'body', type: 'string'),
|
||||
new OA\Property(property: 'excerpt', type: 'string'),
|
||||
new OA\Property(property: 'slug', type: 'string'),
|
||||
new OA\Property(property: 'featured_image', type: 'string', nullable: true),
|
||||
new OA\Property(property: 'author', type: 'object'),
|
||||
new OA\Property(property: 'category', type: 'object'),
|
||||
new OA\Property(property: 'tags', type: 'array', items: new OA\Items(type: 'object')),
|
||||
new OA\Property(property: 'published_at', type: 'string', format: 'datetime', nullable: true),
|
||||
]
|
||||
)]
|
||||
class SwaggerDefinitions
|
||||
{
|
||||
}
|
||||
15
app/SwaggerGeneratorFactory.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use L5Swagger\CustomGeneratorInterface;
|
||||
use OpenApi\Generator as OpenApiGenerator;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
class SwaggerGeneratorFactory implements CustomGeneratorInterface
|
||||
{
|
||||
public function create(): OpenApiGenerator
|
||||
{
|
||||
return new OpenApiGenerator(new NullLogger());
|
||||
}
|
||||
}
|
||||
0
bootstrap/cache/.gitignore
vendored
Normal file → Executable file
1
ci_trigger.txt
Normal file
@ -0,0 +1 @@
|
||||
1782670160
|
||||
@ -6,9 +6,11 @@
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"laravel/framework": "^11.9",
|
||||
"darkaonline/l5-swagger": "^11.0",
|
||||
"laravel/framework": "^13.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^2.9"
|
||||
"laravel/tinker": "^3.0",
|
||||
"spatie/laravel-translatable": "^6.14"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
@ -16,7 +18,7 @@
|
||||
"laravel/sail": "^1.26",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.0",
|
||||
"phpunit/phpunit": "^11.0.1"
|
||||
"phpunit/phpunit": "^12.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
||||
3572
composer.lock
generated
@ -17,6 +17,8 @@
|
||||
|
||||
'default' => env('CACHE_STORE', 'database'),
|
||||
|
||||
'serializable_classes' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Stores
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
'allowed_methods' => ['*'],
|
||||
|
||||
'allowed_origins' => ['*'],
|
||||
'allowed_origins' => [env('FRONTEND_URL', 'https://parsaaghayi.ir')],
|
||||
|
||||
'allowed_origins_patterns' => [],
|
||||
|
||||
|
||||
326
config/l5-swagger.php
Normal file
@ -0,0 +1,326 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'default' => 'default',
|
||||
'documentations' => [
|
||||
'default' => [
|
||||
'api' => [
|
||||
'title' => 'L5 Swagger UI',
|
||||
],
|
||||
|
||||
'routes' => [
|
||||
/*
|
||||
* Route for accessing api documentation interface
|
||||
*/
|
||||
'api' => 'api/documentation',
|
||||
],
|
||||
'paths' => [
|
||||
/*
|
||||
* Edit to include full URL in ui for assets
|
||||
*/
|
||||
'use_absolute_path' => env('L5_SWAGGER_USE_ABSOLUTE_PATH', true),
|
||||
|
||||
/*
|
||||
* Edit to set path where swagger ui assets should be stored
|
||||
*/
|
||||
'swagger_ui_assets_path' => env('L5_SWAGGER_UI_ASSETS_PATH', 'vendor/swagger-api/swagger-ui/dist/'),
|
||||
|
||||
/*
|
||||
* File name of the generated json documentation file
|
||||
*/
|
||||
'docs_json' => 'api-docs.json',
|
||||
|
||||
/*
|
||||
* File name of the generated YAML documentation file
|
||||
*/
|
||||
'docs_yaml' => 'api-docs.yaml',
|
||||
|
||||
/*
|
||||
* Set this to `json` or `yaml` to determine which documentation file to use in UI
|
||||
*/
|
||||
'format_to_use_for_docs' => env('L5_FORMAT_TO_USE_FOR_DOCS', 'json'),
|
||||
|
||||
/*
|
||||
* Absolute paths to directory containing the swagger annotations are stored.
|
||||
*/
|
||||
'annotations' => [
|
||||
base_path('app'),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'defaults' => [
|
||||
'routes' => [
|
||||
/*
|
||||
* Route for accessing parsed swagger annotations.
|
||||
*/
|
||||
'docs' => 'docs',
|
||||
|
||||
/*
|
||||
* Route for Oauth2 authentication callback.
|
||||
*/
|
||||
'oauth2_callback' => 'api/oauth2-callback',
|
||||
|
||||
/*
|
||||
* Middleware allows to prevent unexpected access to API documentation
|
||||
*/
|
||||
'middleware' => [
|
||||
'api' => [],
|
||||
'asset' => [],
|
||||
'docs' => [],
|
||||
'oauth2_callback' => [],
|
||||
],
|
||||
|
||||
/*
|
||||
* Route Group options
|
||||
*/
|
||||
'group_options' => [],
|
||||
],
|
||||
|
||||
'paths' => [
|
||||
/*
|
||||
* Absolute path to location where parsed annotations will be stored
|
||||
*/
|
||||
'docs' => storage_path('api-docs'),
|
||||
|
||||
/*
|
||||
* Absolute path to directory where to export views
|
||||
*/
|
||||
'views' => base_path('resources/views/vendor/l5-swagger'),
|
||||
|
||||
/*
|
||||
* Edit to set the api's base path
|
||||
*/
|
||||
'base' => env('L5_SWAGGER_BASE_PATH', null),
|
||||
|
||||
/*
|
||||
* Absolute path to directories that should be excluded from scanning
|
||||
* @deprecated Please use `scanOptions.exclude`
|
||||
* `scanOptions.exclude` overwrites this
|
||||
*/
|
||||
'excludes' => [],
|
||||
],
|
||||
|
||||
'scanOptions' => [
|
||||
/**
|
||||
* Use a custom generator factory that suppresses swagger-php warnings
|
||||
* (which get converted to ErrorException by Laravel).
|
||||
*
|
||||
* @see \OpenApi\Generator
|
||||
*/
|
||||
'generator_factory' => \App\SwaggerGeneratorFactory::class,
|
||||
|
||||
/**
|
||||
* Configuration for default processors. Allows to pass processors configuration to swagger-php.
|
||||
*
|
||||
* @link https://zircote.github.io/swagger-php/reference/processors.html
|
||||
*/
|
||||
'default_processors_configuration' => [
|
||||
/** Example */
|
||||
/**
|
||||
* 'operationId.hash' => true,
|
||||
* 'pathFilter' => [
|
||||
* 'tags' => [
|
||||
* '/pets/',
|
||||
* '/store/',
|
||||
* ],
|
||||
* ],.
|
||||
*/
|
||||
],
|
||||
|
||||
/**
|
||||
* analyser: defaults to \OpenApi\StaticAnalyser .
|
||||
*
|
||||
* @see \OpenApi\scan
|
||||
*/
|
||||
'analyser' => null,
|
||||
|
||||
/**
|
||||
* analysis: defaults to a new \OpenApi\Analysis .
|
||||
*
|
||||
* @see \OpenApi\scan
|
||||
*/
|
||||
'analysis' => null,
|
||||
|
||||
/**
|
||||
* Custom query path processors classes.
|
||||
*
|
||||
* @link https://github.com/zircote/swagger-php/tree/master/Examples/processors/schema-query-parameter
|
||||
* @see \OpenApi\scan
|
||||
*/
|
||||
'processors' => [
|
||||
// new \App\SwaggerProcessors\SchemaQueryParameter(),
|
||||
],
|
||||
|
||||
/**
|
||||
* pattern: string $pattern File pattern(s) to scan (default: *.php) .
|
||||
*
|
||||
* @see \OpenApi\scan
|
||||
*/
|
||||
'pattern' => null,
|
||||
|
||||
/*
|
||||
* Absolute path to directories that should be excluded from scanning
|
||||
* @note This option overwrites `paths.excludes`
|
||||
* @see \OpenApi\scan
|
||||
*/
|
||||
'exclude' => [],
|
||||
|
||||
/*
|
||||
* Allows to generate specs either for OpenAPI 3.0.0 or OpenAPI 3.1.0.
|
||||
* By default the spec will be in version 3.0.0
|
||||
*/
|
||||
'open_api_spec_version' => env('L5_SWAGGER_OPEN_API_SPEC_VERSION', \L5Swagger\Generator::OPEN_API_DEFAULT_SPEC_VERSION),
|
||||
],
|
||||
|
||||
/*
|
||||
* API security definitions. Will be generated into documentation file.
|
||||
*/
|
||||
'securityDefinitions' => [
|
||||
'securitySchemes' => [
|
||||
/*
|
||||
* Examples of Security schemes
|
||||
*/
|
||||
/*
|
||||
'api_key_security_example' => [ // Unique name of security
|
||||
'type' => 'apiKey', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
|
||||
'description' => 'A short description for security scheme',
|
||||
'name' => 'api_key', // The name of the header or query parameter to be used.
|
||||
'in' => 'header', // The location of the API key. Valid values are "query" or "header".
|
||||
],
|
||||
'oauth2_security_example' => [ // Unique name of security
|
||||
'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
|
||||
'description' => 'A short description for oauth2 security scheme.',
|
||||
'flow' => 'implicit', // The flow used by the OAuth2 security scheme. Valid values are "implicit", "password", "application" or "accessCode".
|
||||
'authorizationUrl' => 'http://example.com/auth', // The authorization URL to be used for (implicit/accessCode)
|
||||
//'tokenUrl' => 'http://example.com/auth' // The authorization URL to be used for (password/application/accessCode)
|
||||
'scopes' => [
|
||||
'read:projects' => 'read your projects',
|
||||
'write:projects' => 'modify projects in your account',
|
||||
]
|
||||
],
|
||||
*/
|
||||
|
||||
/* Open API 3.0 support
|
||||
'passport' => [ // Unique name of security
|
||||
'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
|
||||
'description' => 'Laravel passport oauth2 security.',
|
||||
'in' => 'header',
|
||||
'scheme' => 'https',
|
||||
'flows' => [
|
||||
"password" => [
|
||||
"authorizationUrl" => config('app.url') . '/oauth/authorize',
|
||||
"tokenUrl" => config('app.url') . '/oauth/token',
|
||||
"refreshUrl" => config('app.url') . '/token/refresh',
|
||||
"scopes" => []
|
||||
],
|
||||
],
|
||||
],
|
||||
'sanctum' => [ // Unique name of security
|
||||
'type' => 'apiKey', // Valid values are "basic", "apiKey" or "oauth2".
|
||||
'description' => 'Enter token in format (Bearer <token>)',
|
||||
'name' => 'Authorization', // The name of the header or query parameter to be used.
|
||||
'in' => 'header', // The location of the API key. Valid values are "query" or "header".
|
||||
],
|
||||
*/
|
||||
],
|
||||
'security' => [
|
||||
/*
|
||||
* Examples of Securities
|
||||
*/
|
||||
[
|
||||
/*
|
||||
'oauth2_security_example' => [
|
||||
'read',
|
||||
'write'
|
||||
],
|
||||
|
||||
'passport' => []
|
||||
*/
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
* Set this to `true` in development mode so that docs would be regenerated on each request
|
||||
* Set this to `false` to disable swagger generation on production
|
||||
*/
|
||||
'generate_always' => env('L5_SWAGGER_GENERATE_ALWAYS', false),
|
||||
|
||||
/*
|
||||
* Set this to `true` to generate a copy of documentation in yaml format
|
||||
*/
|
||||
'generate_yaml_copy' => env('L5_SWAGGER_GENERATE_YAML_COPY', false),
|
||||
|
||||
/*
|
||||
* Edit to trust the proxy's ip address - needed for AWS Load Balancer
|
||||
* string[]
|
||||
*/
|
||||
'proxy' => false,
|
||||
|
||||
/*
|
||||
* Configs plugin allows to fetch external configs instead of passing them to SwaggerUIBundle.
|
||||
* See more at: https://github.com/swagger-api/swagger-ui#configs-plugin
|
||||
*/
|
||||
'additional_config_url' => null,
|
||||
|
||||
/*
|
||||
* Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically),
|
||||
* 'method' (sort by HTTP method).
|
||||
* Default is the order returned by the server unchanged.
|
||||
*/
|
||||
'operations_sort' => env('L5_SWAGGER_OPERATIONS_SORT', null),
|
||||
|
||||
/*
|
||||
* Pass the validatorUrl parameter to SwaggerUi init on the JS side.
|
||||
* A null value here disables validation.
|
||||
*/
|
||||
'validator_url' => null,
|
||||
|
||||
/*
|
||||
* Swagger UI configuration parameters
|
||||
*/
|
||||
'ui' => [
|
||||
'display' => [
|
||||
'dark_mode' => env('L5_SWAGGER_UI_DARK_MODE', false),
|
||||
/*
|
||||
* Controls the default expansion setting for the operations and tags. It can be :
|
||||
* 'list' (expands only the tags),
|
||||
* 'full' (expands the tags and operations),
|
||||
* 'none' (expands nothing).
|
||||
*/
|
||||
'doc_expansion' => env('L5_SWAGGER_UI_DOC_EXPANSION', 'none'),
|
||||
|
||||
/**
|
||||
* If set, enables filtering. The top bar will show an edit box that
|
||||
* you can use to filter the tagged operations that are shown. Can be
|
||||
* Boolean to enable or disable, or a string, in which case filtering
|
||||
* will be enabled using that string as the filter expression. Filtering
|
||||
* is case-sensitive matching the filter expression anywhere inside
|
||||
* the tag.
|
||||
*/
|
||||
'filter' => env('L5_SWAGGER_UI_FILTERS', true), // true | false
|
||||
],
|
||||
|
||||
'authorization' => [
|
||||
/*
|
||||
* If set to true, it persists authorization data, and it would not be lost on browser close/refresh
|
||||
*/
|
||||
'persist_authorization' => env('L5_SWAGGER_UI_PERSIST_AUTHORIZATION', false),
|
||||
|
||||
'oauth2' => [
|
||||
/*
|
||||
* If set to true, adds PKCE to AuthorizationCodeGrant flow
|
||||
*/
|
||||
'use_pkce_with_authorization_code_grant' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
/*
|
||||
* Constants which can be used in annotations
|
||||
*/
|
||||
'constants' => [
|
||||
'L5_SWAGGER_CONST_HOST' => env('L5_SWAGGER_CONST_HOST', 'http://my-default-host.com'),
|
||||
],
|
||||
],
|
||||
];
|
||||
@ -77,7 +77,7 @@
|
||||
'middleware' => [
|
||||
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
|
||||
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
|
||||
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
||||
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\PreventRequestForgery::class,
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
17
database/factories/Website/AboutMeFactory.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories\Website;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class AboutMeFactory extends Factory
|
||||
{
|
||||
protected $model = \App\Models\Website\AboutMe::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'image' => $this->faker->imageUrl(),
|
||||
];
|
||||
}
|
||||
}
|
||||
17
database/factories/Website/ProjectCategoryFactory.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories\Website;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ProjectCategoryFactory extends Factory
|
||||
{
|
||||
protected $model = \App\Models\Website\ProjectCategory::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'image' => $this->faker->imageUrl(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
database/factories/Website/ProjectFactory.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories\Website;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ProjectFactory extends Factory
|
||||
{
|
||||
protected $model = \App\Models\Website\Project::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'category_id' => \App\Models\Website\ProjectCategory::factory(),
|
||||
'image1' => $this->faker->imageUrl(),
|
||||
'image2' => $this->faker->imageUrl(),
|
||||
];
|
||||
}
|
||||
}
|
||||
17
database/factories/Website/ServiceFactory.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories\Website;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ServiceFactory extends Factory
|
||||
{
|
||||
protected $model = \App\Models\Website\Service::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'imageSrc' => $this->faker->imageUrl(),
|
||||
];
|
||||
}
|
||||
}
|
||||
20
database/factories/Website/SkillFactory.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories\Website;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class SkillFactory extends Factory
|
||||
{
|
||||
protected $model = \App\Models\Website\Skill::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'image' => $this->faker->imageUrl(),
|
||||
'link' => $this->faker->url(),
|
||||
'percentage' => $this->faker->numberBetween(10, 100),
|
||||
'about_me_id' => \App\Models\Website\AboutMe::factory(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// Add JSON columns for spatie/laravel-translatable on website_about_me
|
||||
Schema::table('website_about_me', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('website_about_me', 'title')) {
|
||||
$table->json('title')->nullable()->after('image');
|
||||
}
|
||||
if (!Schema::hasColumn('website_about_me', 'description')) {
|
||||
$table->json('description')->nullable()->after('title');
|
||||
}
|
||||
});
|
||||
|
||||
// Migrate data from translations table to JSON columns
|
||||
DB::statement('
|
||||
UPDATE website_about_me a
|
||||
SET a.title = (
|
||||
SELECT JSON_OBJECTAGG(locale, title)
|
||||
FROM website_about_me_translations t
|
||||
WHERE t.about_me_id = a.id
|
||||
),
|
||||
a.description = (
|
||||
SELECT JSON_OBJECTAGG(locale, description)
|
||||
FROM website_about_me_translations t
|
||||
WHERE t.about_me_id = a.id
|
||||
)
|
||||
');
|
||||
|
||||
// Add JSON columns for spatie/laravel-translatable on website_skills
|
||||
Schema::table('website_skills', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('website_skills', 'title')) {
|
||||
$table->json('title')->nullable()->after('image');
|
||||
}
|
||||
if (!Schema::hasColumn('website_skills', 'description')) {
|
||||
$table->json('description')->nullable()->after('title');
|
||||
}
|
||||
});
|
||||
|
||||
// Migrate data from skill translations table to JSON columns
|
||||
DB::statement('
|
||||
UPDATE website_skills s
|
||||
SET s.title = (
|
||||
SELECT JSON_OBJECTAGG(locale, title)
|
||||
FROM website_skill_translations t
|
||||
WHERE t.skill_id = s.id
|
||||
),
|
||||
s.description = (
|
||||
SELECT JSON_OBJECTAGG(locale, description)
|
||||
FROM website_skill_translations t
|
||||
WHERE t.skill_id = s.id
|
||||
)
|
||||
');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('website_about_me', function (Blueprint $table) {
|
||||
$table->dropColumn(['title', 'description']);
|
||||
});
|
||||
|
||||
Schema::table('website_skills', function (Blueprint $table) {
|
||||
$table->dropColumn(['title', 'description']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// === WEBSITE TABLES ===
|
||||
|
||||
// website_about_me
|
||||
Schema::table('website_about_me', function (Blueprint $table) {
|
||||
$table->json('translations')->nullable()->after('image');
|
||||
});
|
||||
$rows = DB::table('website_about_me_translations')->get();
|
||||
foreach ($rows->groupBy('about_me_id') as $aboutMeId => $translations) {
|
||||
$data = [];
|
||||
foreach ($translations as $t) {
|
||||
$data['title'][$t->locale] = $t->title;
|
||||
$data['description'][$t->locale] = $t->description;
|
||||
}
|
||||
DB::table('website_about_me')->where('id', $aboutMeId)->update(['translations' => json_encode($data)]);
|
||||
}
|
||||
Schema::dropIfExists('website_about_me_translations');
|
||||
|
||||
// website_skills
|
||||
Schema::table('website_skills', function (Blueprint $table) {
|
||||
$table->json('translations')->nullable()->after('percentage');
|
||||
});
|
||||
$rows = DB::table('website_skill_translations')->get();
|
||||
foreach ($rows->groupBy('skill_id') as $skillId => $translations) {
|
||||
$data = [];
|
||||
foreach ($translations as $t) {
|
||||
$data['title'][$t->locale] = $t->title;
|
||||
$data['description'][$t->locale] = $t->description;
|
||||
}
|
||||
DB::table('website_skills')->where('id', $skillId)->update(['translations' => json_encode($data)]);
|
||||
}
|
||||
Schema::dropIfExists('website_skill_translations');
|
||||
|
||||
// website_services
|
||||
Schema::table('website_services', function (Blueprint $table) {
|
||||
$table->json('translations')->nullable()->after('imageSrc');
|
||||
});
|
||||
$rows = DB::table('website_service_translations')->get();
|
||||
foreach ($rows->groupBy('service_id') as $serviceId => $translations) {
|
||||
$data = [];
|
||||
foreach ($translations as $t) {
|
||||
$data['title'][$t->locale] = $t->title;
|
||||
$data['description'][$t->locale] = $t->description;
|
||||
}
|
||||
DB::table('website_services')->where('id', $serviceId)->update(['translations' => json_encode($data)]);
|
||||
}
|
||||
Schema::dropIfExists('website_service_translations');
|
||||
|
||||
// website_project_categories
|
||||
Schema::table('website_project_categories', function (Blueprint $table) {
|
||||
$table->json('translations')->nullable()->after('image');
|
||||
});
|
||||
$rows = DB::table('website_project_category_translations')->get();
|
||||
foreach ($rows->groupBy('category_id') as $categoryId => $translations) {
|
||||
$data = [];
|
||||
foreach ($translations as $t) {
|
||||
$data['title'][$t->locale] = $t->title;
|
||||
$data['description'][$t->locale] = $t->description;
|
||||
}
|
||||
DB::table('website_project_categories')->where('id', $categoryId)->update(['translations' => json_encode($data)]);
|
||||
}
|
||||
Schema::dropIfExists('website_project_category_translations');
|
||||
|
||||
// website_projects
|
||||
Schema::table('website_projects', function (Blueprint $table) {
|
||||
$table->json('translations')->nullable()->after('image2');
|
||||
});
|
||||
$rows = DB::table('website_project_translations')->get();
|
||||
foreach ($rows->groupBy('project_id') as $projectId => $translations) {
|
||||
$data = [];
|
||||
foreach ($translations as $t) {
|
||||
$data['title'][$t->locale] = $t->title;
|
||||
$data['description'][$t->locale] = $t->description;
|
||||
}
|
||||
DB::table('website_projects')->where('id', $projectId)->update(['translations' => json_encode($data)]);
|
||||
}
|
||||
Schema::dropIfExists('website_project_translations');
|
||||
|
||||
// === BLOG TABLES ===
|
||||
Schema::table('posts', function (Blueprint $table) {
|
||||
$table->json('translations')->nullable()->after('featured_image');
|
||||
});
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->json('translations')->nullable()->after('image');
|
||||
});
|
||||
Schema::table('tags', function (Blueprint $table) {
|
||||
$table->json('translations')->nullable()->after('slug');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Restore website_about_me_translations
|
||||
Schema::create('website_about_me_translations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('about_me_id');
|
||||
$table->string('locale')->index();
|
||||
$table->string('title');
|
||||
$table->text('description');
|
||||
$table->timestamps();
|
||||
$table->foreign('about_me_id')->references('id')->on('website_about_me')->onDelete('cascade');
|
||||
$table->unique(['about_me_id', 'locale']);
|
||||
});
|
||||
DB::table('website_about_me')->whereNotNull('translations')->each(function ($row) {
|
||||
$translations = json_decode($row->translations, true);
|
||||
if (!$translations) return;
|
||||
foreach (($translations['title'] ?? []) as $locale => $title) {
|
||||
DB::table('website_about_me_translations')->insert([
|
||||
'about_me_id' => $row->id,
|
||||
'locale' => $locale,
|
||||
'title' => $title,
|
||||
'description' => $translations['description'][$locale] ?? '',
|
||||
]);
|
||||
}
|
||||
});
|
||||
Schema::table('website_about_me', function (Blueprint $table) {
|
||||
$table->dropColumn('translations');
|
||||
});
|
||||
|
||||
// Restore website_skill_translations
|
||||
Schema::create('website_skill_translations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('skill_id')->constrained('website_skills')->onDelete('cascade');
|
||||
$table->string('locale')->index();
|
||||
$table->string('title');
|
||||
$table->text('description');
|
||||
$table->unique(['skill_id', 'locale']);
|
||||
$table->timestamps();
|
||||
});
|
||||
DB::table('website_skills')->whereNotNull('translations')->each(function ($row) {
|
||||
$translations = json_decode($row->translations, true);
|
||||
if (!$translations) return;
|
||||
foreach (($translations['title'] ?? []) as $locale => $title) {
|
||||
DB::table('website_skill_translations')->insert([
|
||||
'skill_id' => $row->id,
|
||||
'locale' => $locale,
|
||||
'title' => $title,
|
||||
'description' => $translations['description'][$locale] ?? '',
|
||||
]);
|
||||
}
|
||||
});
|
||||
Schema::table('website_skills', function (Blueprint $table) {
|
||||
$table->dropColumn('translations');
|
||||
});
|
||||
|
||||
// Restore website_service_translations
|
||||
Schema::create('website_service_translations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('service_id');
|
||||
$table->string('locale')->index();
|
||||
$table->string('title');
|
||||
$table->text('description');
|
||||
$table->timestamps();
|
||||
$table->foreign('service_id')->references('id')->on('website_services')->onDelete('cascade');
|
||||
$table->unique(['service_id', 'locale']);
|
||||
});
|
||||
DB::table('website_services')->whereNotNull('translations')->each(function ($row) {
|
||||
$translations = json_decode($row->translations, true);
|
||||
if (!$translations) return;
|
||||
foreach (($translations['title'] ?? []) as $locale => $title) {
|
||||
DB::table('website_service_translations')->insert([
|
||||
'service_id' => $row->id,
|
||||
'locale' => $locale,
|
||||
'title' => $title,
|
||||
'description' => $translations['description'][$locale] ?? '',
|
||||
]);
|
||||
}
|
||||
});
|
||||
Schema::table('website_services', function (Blueprint $table) {
|
||||
$table->dropColumn('translations');
|
||||
});
|
||||
|
||||
// Restore website_project_category_translations
|
||||
Schema::create('website_project_category_translations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('category_id');
|
||||
$table->string('locale')->index();
|
||||
$table->string('title');
|
||||
$table->text('description');
|
||||
$table->timestamps();
|
||||
$table->foreign('category_id')->references('id')->on('website_project_categories')->onDelete('cascade');
|
||||
$table->unique(['category_id', 'locale']);
|
||||
});
|
||||
DB::table('website_project_categories')->whereNotNull('translations')->each(function ($row) {
|
||||
$translations = json_decode($row->translations, true);
|
||||
if (!$translations) return;
|
||||
foreach (($translations['title'] ?? []) as $locale => $title) {
|
||||
DB::table('website_project_category_translations')->insert([
|
||||
'category_id' => $row->id,
|
||||
'locale' => $locale,
|
||||
'title' => $title,
|
||||
'description' => $translations['description'][$locale] ?? '',
|
||||
]);
|
||||
}
|
||||
});
|
||||
Schema::table('website_project_categories', function (Blueprint $table) {
|
||||
$table->dropColumn('translations');
|
||||
});
|
||||
|
||||
// Restore website_project_translations
|
||||
Schema::create('website_project_translations', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('project_id');
|
||||
$table->string('locale')->index();
|
||||
$table->string('title');
|
||||
$table->text('description');
|
||||
$table->timestamps();
|
||||
$table->foreign('project_id')->references('id')->on('website_projects')->onDelete('cascade');
|
||||
$table->unique(['project_id', 'locale']);
|
||||
});
|
||||
DB::table('website_projects')->whereNotNull('translations')->each(function ($row) {
|
||||
$translations = json_decode($row->translations, true);
|
||||
if (!$translations) return;
|
||||
foreach (($translations['title'] ?? []) as $locale => $title) {
|
||||
DB::table('website_project_translations')->insert([
|
||||
'project_id' => $row->id,
|
||||
'locale' => $locale,
|
||||
'title' => $title,
|
||||
'description' => $translations['description'][$locale] ?? '',
|
||||
]);
|
||||
}
|
||||
});
|
||||
Schema::table('website_projects', function (Blueprint $table) {
|
||||
$table->dropColumn('translations');
|
||||
});
|
||||
|
||||
// Blog tables
|
||||
Schema::table('posts', function (Blueprint $table) {
|
||||
$table->dropColumn('translations');
|
||||
});
|
||||
Schema::table('categories', function (Blueprint $table) {
|
||||
$table->dropColumn('translations');
|
||||
});
|
||||
Schema::table('tags', function (Blueprint $table) {
|
||||
$table->dropColumn('translations');
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -7,6 +7,7 @@
|
||||
use Database\Seeders\Website\AboutMeSeeder;
|
||||
use Database\Seeders\Website\ProjectCategorySeeder;
|
||||
use Database\Seeders\Website\ProjectSeeder;
|
||||
use Database\Seeders\Website\ServiceSeeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
@ -23,13 +24,11 @@ public function run(): void
|
||||
'email' => 'ceo@parsaaghayi.ir',
|
||||
]);
|
||||
$this->call([
|
||||
AboutMeSeeder::class,
|
||||
// ProjectSeeder::class,
|
||||
ProjectCategorySeeder::class,
|
||||
AboutMeSeeder::class,
|
||||
ProjectSeeder::class,
|
||||
ServiceSeeder::class,
|
||||
BlogSeeder::class
|
||||
// دیگر seeders
|
||||
BlogSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Service;
|
||||
|
||||
class ServiceSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$services = [
|
||||
[
|
||||
'title' => 'Custom Web Development',
|
||||
'description' => 'We create tailored web applications that meet your unique business requirements, utilizing the latest technologies for optimal performance.',
|
||||
'imageSrc' => '/images/ui-ux-vector.svg',
|
||||
'altText' => 'Custom Web Development Vector',
|
||||
],
|
||||
[
|
||||
'title' => 'Frontend Development',
|
||||
'description' => 'Our frontend development services focus on crafting engaging, responsive user interfaces that enhance user experience across all devices.',
|
||||
'imageSrc' => '/images/web-design-vector.svg',
|
||||
'altText' => 'Frontend Development Vector',
|
||||
],
|
||||
[
|
||||
'title' => 'Backend Development',
|
||||
'description' => 'We provide reliable backend development solutions, building robust server-side architectures and APIs to ensure smooth and secure application functionality.',
|
||||
'imageSrc' => '/images/app-design-vector.svg',
|
||||
'altText' => 'Backend Development Vector',
|
||||
],
|
||||
[
|
||||
'title' => 'Website Maintenance and Support',
|
||||
'description' => 'Our ongoing maintenance and support services keep your website updated, secure, and performing at its best, allowing you to focus on your core business.',
|
||||
'imageSrc' => '/images/graphic-design-vector.svg',
|
||||
'altText' => 'Website Maintenance and Support Vector',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($services as $service) {
|
||||
Service::create($service);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,149 +3,59 @@
|
||||
namespace Database\Seeders\Website;
|
||||
|
||||
use App\Models\Website\AboutMe;
|
||||
use App\Models\Website\AboutMeTranslation;
|
||||
use App\Models\Website\Skill;
|
||||
use App\Models\Website\SkillTranslation;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class AboutMeSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
// ایجاد رکورد AboutMe بدون ترجمه
|
||||
$aboutMe = AboutMe::create([
|
||||
'image' => '/images/profile.png',
|
||||
'translations' => [
|
||||
'title' => [
|
||||
'en' => 'About Me',
|
||||
'fa' => 'درباره من',
|
||||
],
|
||||
'description' => [
|
||||
'en' => 'With over 10 years of experience in web development, I bridge the gap between pixel-perfect frontend engineering and robust backend architecture. Specialized in refactoring legacy codebases, optimizing core web vitals, and building scalable enterprise-grade Next.js applications.',
|
||||
'fa' => 'با بیش از ۱۰ سال تجربه در دنیای توسعه وب، مسیر کاری من ترکیبی از مهندسی دقیق فرانتاند و درک عمیق از معماری بکاند است. تخصص اصلی من در بازطراحی و Refactor کدهای پیچیده، بهینهسازی Performance پروژههای بزرگ Next.js و پیادهسازی زیرساختهای پایدار وب است.',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// ایجاد ترجمه انگلیسی
|
||||
AboutMeTranslation::create([
|
||||
'about_me_id' => $aboutMe->id,
|
||||
'locale' => 'en',
|
||||
'title' => 'About Me',
|
||||
'description' => 'I am a web developer with over 10 years of experience in creating dynamic and user-centric web applications. Specializing in React and Next.js, I build scalable solutions that enhance performance and user experience. I enjoy tackling complex challenges, from improving legacy systems to developing new applications. My focus on frontend development and seamless backend integration allows me to deliver innovative results while staying current with industry trends.',
|
||||
]);
|
||||
|
||||
// ایجاد ترجمه فارسی
|
||||
AboutMeTranslation::create([
|
||||
'about_me_id' => $aboutMe->id,
|
||||
'locale' => 'fa',
|
||||
'title' => 'درباره من',
|
||||
'description' => 'من یک توسعهدهنده وب با بیش از 10 سال تجربه در ایجاد برنامههای وب پویا و کاربرمحور هستم. با تخصص در React و Next.js، راهحلهای مقیاسپذیری ایجاد میکنم که عملکرد و تجربه کاربری را بهبود میبخشند. من از حل چالشهای پیچیده لذت میبرم، از بهبود سیستمهای قدیمی گرفته تا توسعه برنامههای جدید. تمرکز من بر توسعه فرانتاند و یکپارچهسازی بیدردسر با بکاند به من این امکان را میدهد که نتایج نوآورانهای ارائه دهم و همزمان با روندهای صنعت بهروز باشم.',
|
||||
]);
|
||||
|
||||
// ایجاد رکوردهای Skill و ترجمههای انگلیسی و فارسی
|
||||
|
||||
// HTML & CSS (SASS, Responsive Design)
|
||||
$htmlSkill = Skill::create([
|
||||
'about_me_id' => $aboutMe->id,
|
||||
$skills = [
|
||||
[
|
||||
'image' => '/images/frontend.png',
|
||||
'percentage' => 95,
|
||||
'link' => 'http://example.com/frontend',
|
||||
]);
|
||||
|
||||
SkillTranslation::create([
|
||||
'skill_id' => $htmlSkill->id,
|
||||
'locale' => 'en',
|
||||
'title' => 'HTML & CSS (SASS, Responsive Design)',
|
||||
'description' => 'Skilled in HTML5, CSS3, including SASS for modular and maintainable stylesheets. Experienced in building responsive web designs.',
|
||||
]);
|
||||
|
||||
SkillTranslation::create([
|
||||
'skill_id' => $htmlSkill->id,
|
||||
'locale' => 'fa',
|
||||
'title' => 'HTML و CSS (SASS، طراحی واکنشگرا)',
|
||||
'description' => 'مهارت در HTML5، CSS3، شامل SASS برای ساخت استایلهای مدولار و قابل نگهداری. تجربه در طراحی وبسایتهای واکنشگرا.',
|
||||
]);
|
||||
|
||||
// React.js and Next.js Development
|
||||
$reactSkill = Skill::create([
|
||||
'about_me_id' => $aboutMe->id,
|
||||
'percentage' => 100,
|
||||
'link' => '#',
|
||||
'translations' => [
|
||||
'title' => ['en' => 'React / Next.js / TypeScript', 'fa' => 'React / Next.js / TypeScript'],
|
||||
'description' => ['en' => 'Core Tech', 'fa' => 'تکنولوژیهای اصلی'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'image' => '/images/backend.png',
|
||||
'percentage' => 90,
|
||||
'link' => 'http://example.com/backend',
|
||||
]);
|
||||
|
||||
SkillTranslation::create([
|
||||
'skill_id' => $reactSkill->id,
|
||||
'locale' => 'en',
|
||||
'title' => 'React.js and Next.js Development',
|
||||
'description' => 'Extensive experience in React.js and Next.js for building scalable and performant web applications.',
|
||||
]);
|
||||
|
||||
SkillTranslation::create([
|
||||
'skill_id' => $reactSkill->id,
|
||||
'locale' => 'fa',
|
||||
'title' => 'توسعه React.js و Next.js',
|
||||
'description' => 'تجربه گسترده در توسعه React.js و Next.js برای ساخت برنامههای وب مقیاسپذیر و پرکاربرد.',
|
||||
]);
|
||||
|
||||
// Backend Development (RESTful APIs, Laravel)
|
||||
$backendSkill = Skill::create([
|
||||
'about_me_id' => $aboutMe->id,
|
||||
'percentage' => 100,
|
||||
'link' => '#',
|
||||
'translations' => [
|
||||
'title' => ['en' => 'Laravel / PHP / MySQL', 'fa' => 'Laravel / PHP / MySQL'],
|
||||
'description' => ['en' => 'Backend & Tools', 'fa' => 'بکاند و ابزارها'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'image' => '/images/ui.png',
|
||||
'percentage' => 85,
|
||||
'link' => 'http://example.com/ui',
|
||||
]);
|
||||
'percentage' => 100,
|
||||
'link' => '#',
|
||||
'translations' => [
|
||||
'title' => ['en' => 'TDD / Clean Architecture', 'fa' => 'TDD / Clean Architecture'],
|
||||
'description' => ['en' => 'Architecture & Others', 'fa' => 'معماری و سایر موارد'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
SkillTranslation::create([
|
||||
'skill_id' => $backendSkill->id,
|
||||
'locale' => 'en',
|
||||
'title' => 'Backend Development (RESTful APIs, Laravel)',
|
||||
'description' => 'Proficient in backend development, building robust RESTful APIs using Laravel and other technologies.',
|
||||
]);
|
||||
|
||||
SkillTranslation::create([
|
||||
'skill_id' => $backendSkill->id,
|
||||
'locale' => 'fa',
|
||||
'title' => 'توسعه بکاند (APIهای RESTful، لاراول)',
|
||||
'description' => 'تسلط به توسعه بکاند و ساخت APIهای RESTful با استفاده از لاراول و دیگر تکنولوژیها.',
|
||||
]);
|
||||
|
||||
// WordPress Development and Customization
|
||||
$wordpressSkill = Skill::create([
|
||||
'about_me_id' => $aboutMe->id,
|
||||
'image' => '/images/ux.png',
|
||||
'percentage' => 90,
|
||||
'link' => 'http://example.com/ux',
|
||||
]);
|
||||
|
||||
SkillTranslation::create([
|
||||
'skill_id' => $wordpressSkill->id,
|
||||
'locale' => 'en',
|
||||
'title' => 'WordPress Development and Customization',
|
||||
'description' => 'Skilled in WordPress development, including theme customization and plugin development.',
|
||||
]);
|
||||
|
||||
SkillTranslation::create([
|
||||
'skill_id' => $wordpressSkill->id,
|
||||
'locale' => 'fa',
|
||||
'title' => 'توسعه و شخصیسازی وردپرس',
|
||||
'description' => 'مهارت در توسعه وردپرس، شامل شخصیسازی قالبها و توسعه افزونهها.',
|
||||
]);
|
||||
|
||||
// Version Control (Git, GitHub)
|
||||
$gitSkill = Skill::create([
|
||||
'about_me_id' => $aboutMe->id,
|
||||
'image' => '/images/ux.png',
|
||||
'percentage' => 80,
|
||||
'link' => 'http://example.com/ux',
|
||||
]);
|
||||
|
||||
SkillTranslation::create([
|
||||
'skill_id' => $gitSkill->id,
|
||||
'locale' => 'en',
|
||||
'title' => 'Version Control (Git, GitHub)',
|
||||
'description' => 'Experienced in version control using Git and GitHub for managing codebases and collaboration.',
|
||||
]);
|
||||
|
||||
SkillTranslation::create([
|
||||
'skill_id' => $gitSkill->id,
|
||||
'locale' => 'fa',
|
||||
'title' => 'کنترل نسخه (Git, GitHub)',
|
||||
'description' => 'تجربه در کنترل نسخه با استفاده از Git و GitHub برای مدیریت کدها و همکاری تیمی.',
|
||||
]);
|
||||
foreach ($skills as $skillData) {
|
||||
$aboutMe->skills()->create($skillData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,38 +4,51 @@
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Website\ProjectCategory;
|
||||
use App\Models\Website\ProjectCategoryTranslation;
|
||||
|
||||
class ProjectCategorySeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$categories = [
|
||||
['fa' => 'طراحی اپلیکیشن', 'en' => 'App Design'],
|
||||
['fa' => 'فرانت اند', 'en' => 'Front End'],
|
||||
['fa' => 'بک اند', 'en' => 'Backend'],
|
||||
['fa' => 'طراحی وب', 'en' => 'Web Design'],
|
||||
['fa' => 'طراحی گرافیک', 'en' => 'Graphic Design'],
|
||||
[
|
||||
'image' => 'category1.jpg',
|
||||
'translations' => [
|
||||
'title' => ['en' => 'App Design', 'fa' => 'طراحی اپلیکیشن'],
|
||||
'description' => ['en' => 'Description for App Design', 'fa' => 'توضیحات طراحی اپلیکیشن'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'image' => 'category2.jpg',
|
||||
'translations' => [
|
||||
'title' => ['en' => 'Front End', 'fa' => 'فرانت اند'],
|
||||
'description' => ['en' => 'Description for Front End', 'fa' => 'توضیحات فرانت اند'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'image' => 'category3.jpg',
|
||||
'translations' => [
|
||||
'title' => ['en' => 'Backend', 'fa' => 'بک اند'],
|
||||
'description' => ['en' => 'Description for Backend', 'fa' => 'توضیحات بک اند'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'image' => 'category4.jpg',
|
||||
'translations' => [
|
||||
'title' => ['en' => 'Web Design', 'fa' => 'طراحی وب'],
|
||||
'description' => ['en' => 'Description for Web Design', 'fa' => 'توضیحات طراحی وب'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'image' => 'category5.jpg',
|
||||
'translations' => [
|
||||
'title' => ['en' => 'Graphic Design', 'fa' => 'طراحی گرافیک'],
|
||||
'description' => ['en' => 'Description for Graphic Design', 'fa' => 'توضیحات طراحی گرافیک'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($categories as $key => $category) {
|
||||
// ایجاد دستهبندی اصلی
|
||||
$createdCategory = ProjectCategory::create(['image' => 'category' . ($key + 1) . '.jpg']);
|
||||
|
||||
// ایجاد ترجمهها
|
||||
ProjectCategoryTranslation::create([
|
||||
'category_id' => $createdCategory->id,
|
||||
'locale' => 'fa',
|
||||
'title' => $category['fa'],
|
||||
'description' => 'توضیحات ' . $category['fa'],
|
||||
]);
|
||||
|
||||
ProjectCategoryTranslation::create([
|
||||
'category_id' => $createdCategory->id,
|
||||
'locale' => 'en',
|
||||
'title' => $category['en'],
|
||||
'description' => 'Description for ' . $category['en'],
|
||||
]);
|
||||
foreach ($categories as $category) {
|
||||
ProjectCategory::create($category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,41 +4,32 @@
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Website\Project;
|
||||
use App\Models\Website\ProjectTranslation;
|
||||
use App\Models\Website\ProjectCategory;
|
||||
|
||||
class ProjectSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
// دریافت تمام دستهبندیها
|
||||
$categories = ProjectCategory::all();
|
||||
|
||||
foreach ($categories as $category) {
|
||||
// ایجاد تعداد تصادفی پروژه برای هر دستهبندی
|
||||
$projectCount = rand(5, 10);
|
||||
|
||||
for ($i = 1; $i <= $projectCount; $i++) {
|
||||
// ایجاد پروژه جدید
|
||||
$project = Project::create([
|
||||
Project::create([
|
||||
'category_id' => $category->id,
|
||||
'image1' => 'project' . $i . '_image1.jpg',
|
||||
'image2' => 'project' . $i . '_image2.jpg'
|
||||
]);
|
||||
|
||||
// ایجاد ترجمهها
|
||||
ProjectTranslation::create([
|
||||
'project_id' => $project->id,
|
||||
'locale' => 'fa',
|
||||
'title' => 'پروژه ' . $i . ' - ' . $category->translations()->where('locale', 'fa')->first()->title,
|
||||
'description' => 'توضیحات پروژه ' . $i . ' برای دستهبندی ' . $category->translations()->where('locale', 'fa')->first()->title,
|
||||
]);
|
||||
|
||||
ProjectTranslation::create([
|
||||
'project_id' => $project->id,
|
||||
'locale' => 'en',
|
||||
'title' => 'Project ' . $i . ' - ' . $category->translations()->where('locale', 'en')->first()->title,
|
||||
'description' => 'Description for project ' . $i . ' in category ' . $category->translations()->where('locale', 'en')->first()->title,
|
||||
'image2' => 'project' . $i . '_image2.jpg',
|
||||
'translations' => [
|
||||
'title' => [
|
||||
'en' => 'Project ' . $i . ' - ' . $category->getTranslation('title', 'en'),
|
||||
'fa' => 'پروژه ' . $i . ' - ' . $category->getTranslation('title', 'fa'),
|
||||
],
|
||||
'description' => [
|
||||
'en' => 'Description for project ' . $i . ' in category ' . $category->getTranslation('title', 'en'),
|
||||
'fa' => 'توضیحات پروژه ' . $i . ' برای دستهبندی ' . $category->getTranslation('title', 'fa'),
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,96 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders\website;
|
||||
namespace Database\Seeders\Website;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Website\Service;
|
||||
use App\Models\Website\ServiceTranslation;
|
||||
|
||||
class ServiceSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
// خدمات با ترجمههای فارسی
|
||||
$services = [
|
||||
[
|
||||
'imageSrc' => '/images/ui-ux-vector.svg',
|
||||
'translations' => [
|
||||
[
|
||||
'locale' => 'fa',
|
||||
'title' => 'توسعه وب سفارشی',
|
||||
'description' => 'ما برنامههای وب سفارشی را ایجاد میکنیم که نیازهای خاص کسبوکار شما را برآورده میسازد و از آخرین تکنولوژیها برای عملکرد بهینه استفاده میکند.',
|
||||
'title' => [
|
||||
'en' => 'Senior Frontend Engineering',
|
||||
'fa' => 'توسعه فرانتاند ارشد',
|
||||
],
|
||||
[
|
||||
'locale' => 'en',
|
||||
'title' => 'Custom Web Development',
|
||||
'description' => 'We create tailored web applications that meet your unique business requirements, utilizing the latest technologies for optimal performance.',
|
||||
'description' => [
|
||||
'en' => 'Building ultra-fast SSR/Static apps, complex state management, and modern UI architectures focusing on core web vitals.',
|
||||
'fa' => 'خلق SPAها و SSRهای بسیار سریع با Next.js، مدیریت استیتهای پیچیده و بهینهسازی Core Web Vitals',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'imageSrc' => '/images/web-design-vector.svg',
|
||||
'translations' => [
|
||||
[
|
||||
'locale' => 'fa',
|
||||
'title' => 'توسعه فرانتاند',
|
||||
'description' => 'خدمات توسعه فرانتاند ما بر روی ایجاد رابطهای کاربری جذاب و پاسخگو تمرکز دارد که تجربه کاربری را در تمامی دستگاهها بهبود میبخشد.',
|
||||
'title' => [
|
||||
'en' => 'Backend & API Design',
|
||||
'fa' => 'معماری و توسعه بکاند',
|
||||
],
|
||||
[
|
||||
'locale' => 'en',
|
||||
'title' => 'Frontend Development',
|
||||
'description' => 'Our frontend development services focus on crafting engaging, responsive user interfaces that enhance user experience across all devices.',
|
||||
'description' => [
|
||||
'en' => 'Designing secure, modular, and optimized RESTful APIs using Laravel and database optimization for scalable systems.',
|
||||
'fa' => 'طراحی APIهای ماژولار، امن و بهینه با Laravel و مدیریت دیتابیسهای مقیاسپذیر',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'imageSrc' => '/images/app-design-vector.svg',
|
||||
'translations' => [
|
||||
[
|
||||
'locale' => 'fa',
|
||||
'title' => 'توسعه بکاند',
|
||||
'description' => 'ما راهحلهای قابل اعتماد توسعه بکاند را ارائه میدهیم و معماریهای سروری و APIهای قوی را برای اطمینان از عملکرد روان و ایمن برنامه ایجاد میکنیم.',
|
||||
'title' => [
|
||||
'en' => 'Code Refactoring & Modernization',
|
||||
'fa' => 'بازطراحی و مدرنسازی کد',
|
||||
],
|
||||
[
|
||||
'locale' => 'en',
|
||||
'title' => 'Backend Development',
|
||||
'description' => 'We provide reliable backend development solutions, building robust server-side architectures and APIs to ensure smooth and secure application functionality.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'imageSrc' => '/images/graphic-design-vector.svg',
|
||||
'translations' => [
|
||||
[
|
||||
'locale' => 'fa',
|
||||
'title' => 'نگهداری و پشتیبانی وبسایت',
|
||||
'description' => 'خدمات نگهداری و پشتیبانی مداوم ما وبسایت شما را بهروز، امن و با بهترین عملکرد نگه میدارد تا شما بتوانید بر روی کسبوکار اصلی خود تمرکز کنید.',
|
||||
],
|
||||
[
|
||||
'locale' => 'en',
|
||||
'title' => 'Website Maintenance and Support',
|
||||
'description' => 'Our ongoing maintenance and support services keep your website updated, secure, and performing at its best, allowing you to focus on your core business.',
|
||||
'description' => [
|
||||
'en' => 'Migrating legacy systems into clean, component-driven, and fully testable architectures to ensure long-term maintainability.',
|
||||
'fa' => 'تبدیل کدهای قدیمی و مگالیتی به معماریهای کامپوننتمحور، تمیز و تستپذیر',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// ذخیره خدمات و ترجمهها
|
||||
foreach ($services as $serviceData) {
|
||||
$service = Service::create([
|
||||
'imageSrc' => $serviceData['imageSrc'],
|
||||
]);
|
||||
|
||||
foreach ($serviceData['translations'] as $translation) {
|
||||
ServiceTranslation::create([
|
||||
'service_id' => $service->id,
|
||||
'locale' => $translation['locale'],
|
||||
'title' => $translation['title'],
|
||||
'description' => $translation['description'],
|
||||
]);
|
||||
}
|
||||
Service::create($serviceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,8 +22,8 @@
|
||||
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="CACHE_STORE" value="array"/>
|
||||
<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
|
||||
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
|
||||
<env name="DB_CONNECTION" value="sqlite"/>
|
||||
<env name="DB_DATABASE" value=":memory:"/>
|
||||
<env name="MAIL_MAILER" value="array"/>
|
||||
<env name="PULSE_ENABLED" value="false"/>
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||
|
||||
BIN
public/images/parsa2.jpg
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
public/images/parsa21.jpg
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
public/images/parsa22.jpg
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
public/images/projects/project1_image1.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
public/images/projects/project1_image2.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
public/images/projects/project2_image1.jpg
Normal file
|
After Width: | Height: | Size: 307 KiB |
BIN
public/images/projects/project2_image2.jpg
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
public/images/projects/project3_image1.jpg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
public/images/projects/project3_image2.jpg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
public/images/projects/project4_image1.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
public/images/projects/project4_image2.jpg
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
public/images/projects/project5_image1.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
public/images/projects/project5_image2.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
public/images/projects/project6_image1.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
public/images/projects/project6_image2.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
0
resources/views/vendor/l5-swagger/.gitkeep
vendored
Normal file
174
resources/views/vendor/l5-swagger/index.blade.php
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ $documentationTitle }}</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ l5_swagger_asset($documentation, 'swagger-ui.css') }}">
|
||||
<link rel="icon" type="image/png" href="{{ l5_swagger_asset($documentation, 'favicon-32x32.png') }}" sizes="32x32"/>
|
||||
<link rel="icon" type="image/png" href="{{ l5_swagger_asset($documentation, 'favicon-16x16.png') }}" sizes="16x16"/>
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
@if(config('l5-swagger.defaults.ui.display.dark_mode'))
|
||||
<style>
|
||||
body#dark-mode,
|
||||
#dark-mode .scheme-container {
|
||||
background: #1b1b1b;
|
||||
}
|
||||
#dark-mode .scheme-container,
|
||||
#dark-mode .opblock .opblock-section-header{
|
||||
box-shadow: 0 1px 2px 0 rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
#dark-mode .operation-filter-input,
|
||||
#dark-mode .dialog-ux .modal-ux,
|
||||
#dark-mode input[type=email],
|
||||
#dark-mode input[type=file],
|
||||
#dark-mode input[type=password],
|
||||
#dark-mode input[type=search],
|
||||
#dark-mode input[type=text],
|
||||
#dark-mode textarea{
|
||||
background: #343434;
|
||||
color: #e7e7e7;
|
||||
}
|
||||
#dark-mode .title,
|
||||
#dark-mode li,
|
||||
#dark-mode p,
|
||||
#dark-mode table,
|
||||
#dark-mode label,
|
||||
#dark-mode .opblock-tag,
|
||||
#dark-mode .opblock .opblock-summary-operation-id,
|
||||
#dark-mode .opblock .opblock-summary-path,
|
||||
#dark-mode .opblock .opblock-summary-path__deprecated,
|
||||
#dark-mode h1,
|
||||
#dark-mode h2,
|
||||
#dark-mode h3,
|
||||
#dark-mode h4,
|
||||
#dark-mode h5,
|
||||
#dark-mode .btn,
|
||||
#dark-mode .tab li,
|
||||
#dark-mode .parameter__name,
|
||||
#dark-mode .parameter__type,
|
||||
#dark-mode .prop-format,
|
||||
#dark-mode .loading-container .loading:after{
|
||||
color: #e7e7e7;
|
||||
}
|
||||
#dark-mode .opblock-description-wrapper p,
|
||||
#dark-mode .opblock-external-docs-wrapper p,
|
||||
#dark-mode .opblock-title_normal p,
|
||||
#dark-mode .response-col_status,
|
||||
#dark-mode table thead tr td,
|
||||
#dark-mode table thead tr th,
|
||||
#dark-mode .response-col_links,
|
||||
#dark-mode .swagger-ui{
|
||||
color: wheat;
|
||||
}
|
||||
#dark-mode .parameter__extension,
|
||||
#dark-mode .parameter__in,
|
||||
#dark-mode .model-title{
|
||||
color: #949494;
|
||||
}
|
||||
#dark-mode table thead tr td,
|
||||
#dark-mode table thead tr th{
|
||||
border-color: rgba(120,120,120,.2);
|
||||
}
|
||||
#dark-mode .opblock .opblock-section-header{
|
||||
background: transparent;
|
||||
}
|
||||
#dark-mode .opblock.opblock-post{
|
||||
background: rgba(73,204,144,.25);
|
||||
}
|
||||
#dark-mode .opblock.opblock-get{
|
||||
background: rgba(97,175,254,.25);
|
||||
}
|
||||
#dark-mode .opblock.opblock-put{
|
||||
background: rgba(252,161,48,.25);
|
||||
}
|
||||
#dark-mode .opblock.opblock-delete{
|
||||
background: rgba(249,62,62,.25);
|
||||
}
|
||||
#dark-mode .loading-container .loading:before{
|
||||
border-color: rgba(255,255,255,10%);
|
||||
border-top-color: rgba(255,255,255,.6);
|
||||
}
|
||||
#dark-mode svg:not(:root){
|
||||
fill: #e7e7e7;
|
||||
}
|
||||
#dark-mode .opblock-summary-description {
|
||||
color: #fafafa;
|
||||
}
|
||||
</style>
|
||||
@endif
|
||||
</head>
|
||||
|
||||
<body @if(config('l5-swagger.defaults.ui.display.dark_mode')) id="dark-mode" @endif>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="{{ l5_swagger_asset($documentation, 'swagger-ui-bundle.js') }}"></script>
|
||||
<script src="{{ l5_swagger_asset($documentation, 'swagger-ui-standalone-preset.js') }}"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
const urls = [];
|
||||
|
||||
@foreach($urlsToDocs as $title => $url)
|
||||
urls.push({name: "{{ $title }}", url: "{{ $url }}"});
|
||||
@endforeach
|
||||
|
||||
// Build a system
|
||||
const ui = SwaggerUIBundle({
|
||||
dom_id: '#swagger-ui',
|
||||
urls: urls,
|
||||
"urls.primaryName": "{{ $documentationTitle }}",
|
||||
operationsSorter: {!! isset($operationsSorter) ? '"' . $operationsSorter . '"' : 'null' !!},
|
||||
configUrl: {!! isset($configUrl) ? '"' . $configUrl . '"' : 'null' !!},
|
||||
validatorUrl: {!! isset($validatorUrl) ? '"' . $validatorUrl . '"' : 'null' !!},
|
||||
oauth2RedirectUrl: "{{ route('l5-swagger.'.$documentation.'.oauth2_callback', [], $useAbsolutePath) }}",
|
||||
|
||||
requestInterceptor: function(request) {
|
||||
request.headers['X-CSRF-TOKEN'] = '{{ csrf_token() }}';
|
||||
return request;
|
||||
},
|
||||
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
|
||||
layout: "StandaloneLayout",
|
||||
docExpansion : "{!! config('l5-swagger.defaults.ui.display.doc_expansion', 'none') !!}",
|
||||
deepLinking: true,
|
||||
filter: {!! config('l5-swagger.defaults.ui.display.filter') ? 'true' : 'false' !!},
|
||||
persistAuthorization: "{!! config('l5-swagger.defaults.ui.authorization.persist_authorization') ? 'true' : 'false' !!}",
|
||||
|
||||
})
|
||||
|
||||
window.ui = ui
|
||||
|
||||
@if(in_array('oauth2', array_column(config('l5-swagger.defaults.securityDefinitions.securitySchemes'), 'type')))
|
||||
ui.initOAuth({
|
||||
usePkceWithAuthorizationCodeGrant: "{!! (bool)config('l5-swagger.defaults.ui.authorization.oauth2.use_pkce_with_authorization_code_grant') !!}"
|
||||
})
|
||||
@endif
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,26 +1,18 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\Blog\CategoryController;
|
||||
use App\Http\Controllers\Blog\HomeController;
|
||||
use App\Http\Controllers\Blog\PostController;
|
||||
use App\Http\Controllers\Blog\TagController;
|
||||
// use App\Http\Controllers\SkillController;
|
||||
use App\Http\Controllers\Website\ServiceController;
|
||||
use App\Http\Controllers\Website\AboutMeController;
|
||||
use App\Http\Controllers\Website\ProjectController;
|
||||
use App\Http\Controllers\Api\Blog\CategoryController;
|
||||
use App\Http\Controllers\Api\Blog\HomeController;
|
||||
use App\Http\Controllers\Api\Blog\PostController;
|
||||
use App\Http\Controllers\Api\Blog\TagController;
|
||||
use App\Http\Controllers\Api\Website\AboutMeController;
|
||||
use App\Http\Controllers\Api\Website\ProjectController;
|
||||
use App\Http\Controllers\Api\Website\ServiceController;
|
||||
|
||||
Route::get('/user', function (Request $request) {
|
||||
return $request->user();
|
||||
})->middleware('auth:sanctum');
|
||||
|
||||
// Route::resource('projects', ProjectController::class);
|
||||
|
||||
Route::apiResource('services', ServiceController::class);
|
||||
Route::middleware('throttle:60,1')->group(function () {
|
||||
Route::prefix('website')->group(function () {
|
||||
Route::get('about-me/{id}/translation/{locale}', [AboutMeController::class, 'getTranslation']);
|
||||
Route::get('services/{locale}', [ServiceController::class, 'index']); // مسیر جدید برای لیست خدمات با ترجمه
|
||||
Route::get('services/{locale}', [ServiceController::class, 'index']);
|
||||
Route::get('services/{id}/translation/{locale}', [ServiceController::class, 'showTranslation']);
|
||||
Route::group(['prefix' => 'projects'], function () {
|
||||
Route::get('/translation/{locale}', [ProjectController::class, 'index']);
|
||||
@ -31,15 +23,11 @@
|
||||
});
|
||||
});
|
||||
|
||||
// مسیرهای بلاگ
|
||||
Route::prefix('blog')->group(function () {
|
||||
// پستها
|
||||
Route::apiResource('posts', PostController::class);
|
||||
// دستهبندیها
|
||||
Route::apiResource('categories', CategoryController::class);
|
||||
// تگها
|
||||
Route::apiResource('tags', TagController::class);
|
||||
// مسیر Homepage
|
||||
Route::get('homepage', [HomeController::class, 'index']);
|
||||
Route::get('categories/{slug}/posts', [CategoryController::class, 'getCategoryWithPosts']);
|
||||
});
|
||||
});
|
||||
|
||||
20
seed-about.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
$app = require_once __DIR__ . '/bootstrap/app.php';
|
||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||
$kernel->bootstrap();
|
||||
|
||||
use App\Models\Website\AboutMe;
|
||||
use App\Models\Website\AboutMeTranslation;
|
||||
|
||||
echo "Creating AboutMe record...\n";
|
||||
$about = AboutMe::create(["id" => 1, "image" => "", "resume" => ""]);
|
||||
echo "Created AboutMe id={$about->id}\n";
|
||||
|
||||
AboutMeTranslation::create(["about_me_id" => 1, "locale" => "en", "title" => "About Me", "description" => "Welcome to my portfolio"]);
|
||||
echo "Created EN translation\n";
|
||||
|
||||
AboutMeTranslation::create(["about_me_id" => 1, "locale" => "fa", "title" => "درباره من", "description" => "به پورتفولیوی من خوش آمدید"]);
|
||||
echo "Created FA translation\n";
|
||||
|
||||
echo "Done!\n";
|
||||
181
storage/api-docs/api-docs.json
Normal file
@ -0,0 +1,181 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "Parsa Aghayi Portfolio API",
|
||||
"description": "REST API for Parsa Aghayi's personal portfolio and blog",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://localhost:8000/api",
|
||||
"description": "Development server"
|
||||
},
|
||||
{
|
||||
"url": "https://backend.parsaaghayi.ir/api",
|
||||
"description": "Production server"
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Skill": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"type": "string"
|
||||
},
|
||||
"link": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"percentage": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AboutMe": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"type": "string"
|
||||
},
|
||||
"skills": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Skill"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Service": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"imageSrc": {
|
||||
"type": "string"
|
||||
},
|
||||
"altText": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Project": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"images": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Category": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Post": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"body": {
|
||||
"type": "string"
|
||||
},
|
||||
"excerpt": {
|
||||
"type": "string"
|
||||
},
|
||||
"slug": {
|
||||
"type": "string"
|
||||
},
|
||||
"featured_image": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"author": {
|
||||
"type": "object"
|
||||
},
|
||||
"category": {
|
||||
"type": "object"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"published_at": {
|
||||
"type": "string",
|
||||
"format": "datetime",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||