Skip to content

Commit

Permalink
feat: add articles API
Browse files Browse the repository at this point in the history
  • Loading branch information
ast21 committed Mar 28, 2023
1 parent 2eecb75 commit c26b6cc
Show file tree
Hide file tree
Showing 16 changed files with 346 additions and 19 deletions.
4 changes: 3 additions & 1 deletion composer.json
Expand Up @@ -21,7 +21,9 @@
"astrotomic/laravel-translatable": "^11.12",
"cviebrock/eloquent-sluggable": "^9.0",
"illuminate/contracts": "^9.0",
"orchid/platform": "^13.10"
"orchid/platform": "^13.10",
"spatie/laravel-data": "^3.2",
"spatie/laravel-query-builder": "^5.2"
},
"require-dev": {
"laravel/pint": "^1.0",
Expand Down
7 changes: 7 additions & 0 deletions src/Containers/ArticleSection/Article/Configs/admin-kit.php
@@ -0,0 +1,7 @@
<?php

return [
'articles' => [
'enable_routes' => false,
],
];
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace AdminKit\Core\Containers\ArticleSection\Article\Data\Factories;

use AdminKit\Core\Containers\ArticleSection\Article\Models\Article;
use Illuminate\Database\Eloquent\Factories\Factory;

class ArticleFactory extends Factory
{
protected $model = Article::class;

public function definition()
{
$locales = config('translatable.locales');
$translations = collect($locales)
->mapWithKeys(fn ($value) => [
$value => [
'title' => fake('ru')->realText(100),
'content' => fake('ru')->randomHtml,
'short_content' => fake('ru')->realText(500),
],
])
->toArray();

return [
'slug' => fake()->slug,
'published_at' => fake()->boolean ? fake()->dateTimeThisMonth : null,
'pinned' => fake()->boolean,
...$translations,
];
}
}
4 changes: 3 additions & 1 deletion src/Containers/ArticleSection/Article/Languages/ru.json
Expand Up @@ -8,5 +8,7 @@
"Pinned": "Закреплен",

"Enter title...": "Введите заголовок...",
"Enter short content...": "Введите короткое содержание..."
"Enter short content...": "Введите короткое содержание...",

"Article has not been published": "Новость не опубликована"
}
47 changes: 46 additions & 1 deletion src/Containers/ArticleSection/Article/Models/Article.php
Expand Up @@ -8,6 +8,7 @@
use Astrotomic\Translatable\Translatable;
use Carbon\Carbon;
use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
Expand All @@ -19,9 +20,15 @@
use Orchid\Screen\AsSource;

/**
* @property int $id
* @property string $slug
* @property bool $pinned
* @property Carbon|null $published_at
* @property Carbon $created_at
* @property Carbon $updated_at
* @property string $title
* @property string $content
* @property string $short_content
* @property Collection<Attachment> $image
*/
class Article extends Model implements TranslatableContract
Expand Down Expand Up @@ -82,7 +89,6 @@ class Article extends Model implements TranslatableContract
*/
protected $allowedSorts = [
'id',
'title',
'published_at',
];

Expand All @@ -104,4 +110,43 @@ public function image(): MorphToMany
{
return $this->morphToMany(Attachment::class, 'attachmentable', 'attachmentable');
}

public function scopeIsPublished(Builder $query): Builder
{
return $query->whereNotNull('published_at');
}

public function scopeIsTitleNotNull(Builder $query): Builder
{
return $query->whereHas('translation', function ($query) {
return $query->whereNotNull('title');
});
}

public function scopeTitle(Builder $query, $search): Builder
{
return $query->whereHas('translation', function ($query) use ($search) {
$search = mb_strtolower($search);

return $query->whereRaw('LOWER(title) LIKE (?)', ["%$search%"]);
});
}

public function scopeContent(Builder $query, $search): Builder
{
return $query->whereHas('translation', function ($query) use ($search) {
$search = mb_strtolower($search);

return $query->whereRaw('LOWER(content) LIKE (?)', ["%$search%"]);
});
}

public function scopeShortContent(Builder $query, $search): Builder
{
return $query->whereHas('translation', function ($query) use ($search) {
$search = mb_strtolower($search);

return $query->whereRaw('LOWER(short_content) LIKE (?)', ["%$search%"]);
});
}
}
Expand Up @@ -4,9 +4,19 @@

namespace AdminKit\Core\Containers\ArticleSection\Article\Providers;

use AdminKit\Core\Containers\ArticleSection\Article\UI\API\Repositories\ArticleInterface;
use AdminKit\Core\Containers\ArticleSection\Article\UI\API\Repositories\ArticleRepository;

class MainServiceProvider extends \AdminKit\Porto\Abstracts\Providers\MainServiceProvider
{
public array $serviceProviders = [
PlatformServiceProvider::class,
];

public function register(): void
{
$this->app->bind(ArticleInterface::class, ArticleRepository::class);

parent::register();
}
}
Expand Up @@ -4,17 +4,27 @@

namespace AdminKit\Core\Containers\ArticleSection\Article\UI\API\Controllers;

use AdminKit\Core\Containers\ArticleSection\Article\Models\Article;
use AdminKit\Core\Containers\ArticleSection\Article\UI\API\Repositories\ArticleInterface;

class ArticleController
{
public function __construct(
public ArticleInterface $repository,
) {
}

public function index()
{
return Article::all();
return $this->repository->getPaginatedList();
}

public function show(int $id)
{
return $this->repository->getById($id);
}

public function show(Article $article)
public function showBySlug(string $slug)
{
return $article;
return $this->repository->getBySlug($slug);
}
}
39 changes: 39 additions & 0 deletions src/Containers/ArticleSection/Article/UI/API/DTO/ArticleDTO.php
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace AdminKit\Core\Containers\ArticleSection\Article\UI\API\DTO;

use AdminKit\Core\Containers\ArticleSection\Article\Models\Article;
use Carbon\Carbon;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Lazy;

class ArticleDTO extends Data
{
public function __construct(
public Lazy|int $id,
public Lazy|string $slug,
public Lazy|string $title,
public Lazy|string $content,
public Lazy|string|null $short_content,
public Lazy|Carbon $published_at,
public Lazy|Carbon $created_at,
public Lazy|Carbon $updated_at,
) {
}

public static function fromModel(Article $article): self
{
return new self(
Lazy::when(fn () => isset($article->id), fn () => $article->id),
Lazy::when(fn () => isset($article->slug), fn () => $article->slug),
Lazy::when(fn () => isset($article->title), fn () => $article->title),
Lazy::when(fn () => isset($article->content), fn () => $article->content),
Lazy::when(fn () => isset($article->short_content), fn () => $article->short_content),
Lazy::when(fn () => isset($article->published_at), fn () => $article->published_at),
Lazy::when(fn () => isset($article->created_at), fn () => $article->created_at),
Lazy::when(fn () => isset($article->updated_at), fn () => $article->updated_at),
);
}
}
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace AdminKit\Core\Containers\ArticleSection\Article\UI\API\Repositories;

use AdminKit\Core\Repositories\RepositoryInterface;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\PaginatedDataCollection;

interface ArticleInterface extends RepositoryInterface
{
public function getPaginatedList(): PaginatedDataCollection;

public function getById(int $id): Data;

public function getBySlug(string $slug): Data;
}
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace AdminKit\Core\Containers\ArticleSection\Article\UI\API\Repositories;

use AdminKit\Core\Containers\ArticleSection\Article\Models\Article;
use AdminKit\Core\Containers\ArticleSection\Article\UI\API\DTO\ArticleDTO;
use AdminKit\Core\Repositories\AbstractRepository;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\PaginatedDataCollection;
use Spatie\QueryBuilder\AllowedFilter;
use Spatie\QueryBuilder\QueryBuilder;

class ArticleRepository extends AbstractRepository implements ArticleInterface
{
public function model(): string
{
return Article::class;
}

public function getPaginatedList(): PaginatedDataCollection
{
$perPage = (int) request()->query('per_page');

$articles = QueryBuilder::for($this->model())

Check failure on line 28 in src/Containers/ArticleSection/Article/UI/API/Repositories/ArticleRepository.php

View workflow job for this annotation

GitHub Actions / phpstan

Call to an undefined method Spatie\QueryBuilder\QueryBuilder::withTranslation().
->withTranslation()
->allowedFilters([
'id',
'slug',
'published_at',
'created_at',
'updated_at',
AllowedFilter::scope('title'),
AllowedFilter::scope('content'),
AllowedFilter::scope('short_content'),
'translation.title',
'translation.content',
'translation.short_content',
])
->allowedFields([
'id',
'slug',
'published_at',
'created_at',
'updated_at',
'translation.title',
//'translation.content',
'translation.short_content',
])
->allowedSorts(['id', 'published_at'])
->isPublished()
->isTitleNotNull()
->paginate($perPage);

return ArticleDTO::collection($articles)->except('content');
}

/**
* @throws Exception
*/
public function getById(int $id): Data
{
$article = $this->model->findOrFail($id);

$this->checkIsPublished($article);

return ArticleDTO::from($article);
}

/**
* @throws Exception
*/
public function getBySlug(string $slug): Data
{
$article = $this->model->where('slug', $slug)->first();

if (is_null($article)) {
throw (new ModelNotFoundException)->setModel($this->model(), $slug);
}

$this->checkIsPublished($article);

return ArticleDTO::from($article);
}

private function checkIsPublished(Article $article)
{
if (is_null($article->published_at)) {
throw new Exception(__('Article has not been published'));
}
}
}

This file was deleted.

This file was deleted.

10 changes: 10 additions & 0 deletions src/Containers/ArticleSection/Article/UI/API/Routes/api.php
@@ -0,0 +1,10 @@
<?php

use AdminKit\Core\Containers\ArticleSection\Article\UI\API\Controllers\ArticleController;
use Illuminate\Support\Facades\Route;

if (config('admin-kit.articles.enable_routes')) {
Route::get('/articles', [ArticleController::class, 'index']);
Route::get('/articles/{id}', [ArticleController::class, 'show'])->where('id', '[0-9]+');
Route::get('/articles/slug/{slug}', [ArticleController::class, 'showBySlug']);
}
2 changes: 2 additions & 0 deletions src/CoreServiceProvider.php
Expand Up @@ -25,6 +25,7 @@ public function register()
->registerConfigs()
->registerLocalizations()

// use porto register
->initPorto(AdminKit::srcPath())
->runLoaderRegister();

Expand All @@ -40,6 +41,7 @@ public function boot()
->publishConfigs()
->publishMigrations()

// use porto boot
->initPorto(AdminKit::srcPath())
->runLoaderBoot();
}
Expand Down

0 comments on commit c26b6cc

Please sign in to comment.