All authors

Claude Skills by HoangNguyen0403
github.com/HoangNguyen04031,434 skills30 installs1,975 views
- Kotlin LanguageReplace Java accessor methods with a Kotlin property: ```kotlin class Person(var name: String) ``` Usage: ```kotlin val person = Person("Alice") println(person.name) person.name = "Bob" ``` Use `val` when the value should be read-only: ```kotlin class Person(val name: String) ``` For a DTO, prefer a `data class`: ```kotlin data class Person(var name: String) ``` Kotlin automatically generates JVM-compatible `getName()` and `setName(String)` methods for Java callers. If Java may provide `null`...Votes: 0GitHub stars: 549
- Kotlin Language```kotlin sealed interface Result<out T> { data class Success<T>(val data: T) : Result<T> data class Failure(val message: String) : Result<Nothing> } ``` Use an exhaustive `when`: ```kotlin fun display(result: Result<String>) = when (result) { is Result.Success -> result.data is Result.Failure -> "Error: ${result.message}" } ``` `sealed interface` models the fixed state hierarchy, while `data class` provides value semantics and readable properties. `Result<out T>` allows `Failure` to work wit...Votes: 0GitHub stars: 549
- Kotlin ToolingUse MockK, not Mockito, for Kotlin because Mockito's `when/then` model conflicts with Kotlin and has weaker first-class support. Use MockK's `every` and `verify`, JUnit 5 for tests, and Truth or Kotest Assertions for readable assertions.Votes: 0GitHub stars: 549
- Kotlin ToolingUse a Gradle Version Catalog in `libs.versions.toml` for all dependency and plugin versions. Reference catalog aliases from `build.gradle.kts`; never inline hardcoded versions in build files.Votes: 0GitHub stars: 549
- Kotlin ToolingMigrate to Kotlin DSL and use `build.gradle.kts` exclusively for type safety and IDE support. Move version declarations to `libs.versions.toml`, then verify the build and test configuration rather than retaining the legacy Groovy script.Votes: 0GitHub stars: 549
- Laravel ApiBuild the endpoint as a versioned, resource-backed route: 1. Create a Form Request for validation, for example `StorePostRequest`, and use `$request->validated()`. 2. Create `PostResource` with `php artisan make:resource PostResource`; expose only deliberate fields in `toArray()`. 3. Implement a controller action that creates the model and returns `new PostResource($post)` with status `201`. 4. Register it under `routes/api.php`, such as `Route::prefix('v1')->middleware('throttle:api')->group...Votes: 0GitHub stars: 549
- Laravel ApiUse a `JsonResource` to define the public representation and its collection helper for lists: ```bash php artisan make:resource UserResource ``` ```php class UserResource extends JsonResource { public function toArray($request): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'links' => ['self' => route('users.show', $this->resource)], ]; } } ``` Return one model as `new UserResource($user)` and a list as `UserResource::collection($users)`. The resource sho...Votes: 0GitHub stars: 549
- Laravel ApiInstall and configure Sanctum, then use tokens and the `auth:sanctum` middleware: ```bash php artisan install:api php artisan migrate ``` Ensure `App\Models\User` uses `Laravel\Sanctum\HasApiTokens`: ```php use HasApiTokens; $token = $user->createToken('mobile-client')->plainTextToken; ``` Return the token only through the login response and send it on later requests as `Authorization: Bearer <token>`. Protect API routes in `routes/api.php` with `Route::middleware('auth:sanctum')`. Use Sanctu...Votes: 0GitHub stars: 549
- Laravel ApiDefine a named limiter and attach it to the API route group: ```php use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter; RateLimiter::for('api', function ($request) { return Limit::perMinute(60)->by( $request->user()?->id ?: $request->ip() ); }); ``` ```php Route::prefix('v1') ->middleware(['throttle:api', 'auth:sanctum']) ->group(function () { Route::get('/posts', [PostController::class, 'index']); }); ``` Key authenticated users by user ID and anonymous calle...Votes: 0GitHub stars: 549
- Laravel ApiVersion routes with a prefix and keep version-specific controllers or resources isolated: ```php use App\Http\Controllers\Api\V1\PostController; Route::prefix('v1')->middleware('throttle:api')->group(function () { Route::apiResource('posts', PostController::class); }); ``` For larger APIs, place controllers under `App\Http\Controllers\Api\V1` and use a corresponding `V1` resource representation when the response contract differs. Add `auth:sanctum` to protected groups. Keep route names and co...Votes: 0GitHub stars: 549
- Laravel ApiReturn status codes that describe the result and keep response bodies resource-backed: - Creation: return `new PostResource($post)` with `201 Created` (or `->response()->setStatusCode(201)`). - Validation failure: let a Form Request produce Laravel's `422 Unprocessable Entity` JSON response. - Successful deletion with no representation: return `response()->noContent()` for `204 No Content`. - Successful reads: use `200 OK` and a resource or resource collection. For example: ```php public func...Votes: 0GitHub stars: 549
- Laravel ArchitectureKeep controllers thin and make them coordinate HTTP concerns only: 1. Put input rules in a Form Request with `authorize()` and `rules()`. 2. Inject a single-responsibility Action through the constructor or method signature. 3. Pass `$request->validated()` to the Action. 4. Return a resource or view from the Action result and map HTTP status codes at the controller boundary. ```php public function store(StoreUserRequest $request, CreateUser $create): UserResource { return new UserResource($cre...Votes: 0GitHub stars: 549
- Laravel ArchitecturePut business logic in a focused Action class or domain/service layer, not in the controller. The controller should receive a validated request, call one use case, and translate its result into an HTTP response. For example, `CreateOrder::handle(CreateOrderData $data)` can enforce order rules and coordinate repositories. The controller injects that Action and passes `$request->validated()` or a typed DTO. If the use case needs persistence abstraction, inject a repository interface and bind its...Votes: 0GitHub stars: 549
- Laravel ArchitectureCreate a Form Request and keep validation there: ```bash php artisan make:request StoreUserRequest ``` ```php class StoreUserRequest extends FormRequest { public function authorize(): bool { return $this->user()?->can('create', User::class) ?? false; } public function rules(): array { return ['name' => ['required', 'string', 'max:255'], 'email' => ['required', 'email', 'unique:users,email']]; } } ``` Inject it into the controller and pass only `$request->validated()` to the Action or model. D...Votes: 0GitHub stars: 549
- Laravel ArchitectureUse Laravel's service container and type-hint dependencies instead of manually instantiating them: ```php final class CreateOrder { public function __construct(private OrderRepository $orders) {} } ``` Bind an interface to its implementation in `AppServiceProvider`: ```php $this->app->bind(OrderRepository::class, EloquentOrderRepository::class); ``` Then inject `CreateOrder` into the controller. Laravel resolves the dependency graph automatically, and the interface allows tests or deployments...Votes: 0GitHub stars: 549
- Laravel ArchitectureCommon Laravel architecture anti-patterns include: - Fat controllers containing validation, business rules, Eloquent queries, and serialization. - Inline `$request->validate()` everywhere instead of reusable Form Requests. - Passing `$request->all()` into `Model::create()`. - Manually constructing services instead of letting the container inject them. - Depending directly on concrete repositories when an interface is the boundary. - One God Service that owns unrelated use cases. - Business lo...Votes: 0GitHub stars: 549
- Laravel ArchitectureFor a Laravel controller change, follow this sequence: 1. Define the use case and create a Form Request for authorization and rules. 2. Create a focused Action with one `handle()` method. 3. Inject the Action into the controller and pass `$request->validated()`. 4. Put persistence behind an interface when it is a meaningful boundary; bind it in `AppServiceProvider`. 5. Keep the controller thin and return the appropriate resource/view/status. 6. Test validation, authorization, the Action, and ...Votes: 0GitHub stars: 549
- Laravel Background ProcessingCreate the job with Laravel's generator and make it queueable: ```bash php artisan make:job ProcessOrder ``` The class should implement `ShouldQueue`, put work in `handle()`, and receive only durable identifiers: ```php final class ProcessOrder implements ShouldQueue { public function __construct(public int $orderId) {} public function handle(): void { $order = Order::findOrFail($this->orderId); // process the order } } ``` Dispatch it with `ProcessOrder::dispatch($order->id)`. Do not put the...Votes: 0GitHub stars: 549
- Laravel Background ProcessingInstall Horizon, publish its configuration, and run it as the production queue supervisor: ```bash composer require laravel/horizon php artisan horizon:install php artisan horizon ``` Configure supervisors and queues in `config/horizon.php`, including process counts, balance strategy, retries, and environment-specific settings. Protect the `/horizon` dashboard with its authorization gate in `HorizonServiceProvider`; do not expose it publicly without access control. Horizon gives queue through...Votes: 0GitHub stars: 549
- Laravel Background ProcessingGenerate an event and listener, then dispatch the event from the application boundary: ```bash php artisan make:event OrderPlaced php artisan make:listener SendConfirmation --event=OrderPlaced ``` Keep the event as the communication fact and the listener as the reaction. Add `ShouldQueue` to `SendConfirmation` when sending the confirmation is asynchronous, and dispatch with `Event::dispatch(new OrderPlaced($order))`. Pass stable identifiers or deliberately serialized data rather than large mu...Votes: 0GitHub stars: 549
- Laravel Background ProcessingUse `Bus::chain()` when each job depends on the successful completion of the previous job: ```php Bus::chain([ new ProcessPayment($order->id), new SendReceipt($order->id), new UpdateFulfillment($order->id), ])->catch(function (Throwable $exception): void { // record or notify about the failed chain })->dispatch(); ``` Each job should implement `ShouldQueue`, do its work in `handle()`, and receive IDs rather than full models. A failure stops subsequent jobs and invokes the catch callback, so m...Votes: 0GitHub stars: 549
- Laravel Background ProcessingUse `Bus::batch()` for independent jobs whose collective progress and completion matter: ```php $batch = Bus::batch([ new ImportRow(1), new ImportRow(2), new ImportRow(3), ])->then(fn (Batch $batch) => finalizeImport($batch)) ->catch(fn (Batch $batch, Throwable $e) => recordImportFailure($batch, $e)) ->dispatch(); ``` Inside a batch-aware job, call `$this->batch()->cancel()` when a domain condition requires cancellation, and expose `$batch->progress()` to operators or a UI. Keep each job smal...Votes: 0GitHub stars: 549
- Laravel Background ProcessingMake failures explicit and retryable on the job: ```php final class ProcessOrder implements ShouldQueue { public int $tries = 3; public int $backoff = 60; public function failed(Throwable $exception): void { // mark the order failed and notify an operator } } ``` Run the failed-job-table migration (`php artisan queue:failed-table` followed by `php artisan migrate`) so exhausted jobs are retained for inspection and retry. Configure Horizon rather than relying on an unmonitored `queue:work` pro...Votes: 0GitHub stars: 549
- Laravel Clean ArchitectureUse domain-oriented folders and keep framework persistence behind explicit boundaries. For an Order feature, create `app/Domains/Order/{Actions,DTOs,Contracts}/`, keep the Eloquent model in `app/Models/`, and use this flow: `Controller → Action → Repository interface → Eloquent implementation`. Define a typed `readonly` DTO for input, an `OrderRepository` contract, and an `EloquentOrderRepository`. Bind the contract to the implementation in `AppServiceProvider`, then inject the contract into ...Votes: 0GitHub stars: 549
- Laravel Clean ArchitectureUse a typed immutable DTO at the boundary between HTTP and the use case: ```php namespace App\Domains\Order\DTOs; readonly class CreateOrderData { public function __construct( public int $userId, public array $lines, ) {} } ``` Create the DTO from `$request->validated()`, then pass it to a focused Action. DTOs make the contract explicit and prevent raw arrays or mutable Eloquent models from becoming the cross-layer API. For PHP 8.2, prefer a `readonly class`; for PHP 8.1, use readonly propert...Votes: 0GitHub stars: 549
- Laravel Clean ArchitectureDefine a repository contract in the domain and provide an Eloquent adapter: ```php interface OrderRepository { public function save(OrderData $data): Order; } final class EloquentOrderRepository implements OrderRepository { public function save(OrderData $data): Order { return Order::create($data->toArray()); } } ``` Bind it in `AppServiceProvider` with `$this->app->bind(OrderRepository::class, EloquentOrderRepository::class)`, then inject `OrderRepository` into the Action. Keep the contract ...Votes: 0GitHub stars: 549
- Laravel Clean ArchitectureOrganize by business domain, for example: ```text app/ Domains/ Order/ Actions/ DTOs/ Contracts/ Models/ Providers/ ``` Keep `User`, `Order`, and `Payment` concerns together within their domains instead of creating global `Controllers`, `Services`, and `Repositories` buckets for every feature. Standard Eloquent models can remain in `app/Models/`, while domain Actions own use cases, DTOs carry typed data, and Contracts define persistence boundaries. Bind implementations in a provider. This mak...Votes: 0GitHub stars: 549
- Laravel Clean ArchitectureApply clean architecture as a small, explicit flow rather than mirroring every framework folder. Put domain use cases in `app/Domains/<Domain>/Actions`, typed immutable data in `DTOs`, and persistence abstractions in `Contracts`. Keep Eloquent implementations at the outer adapter boundary and bind them in `AppServiceProvider`. The request layer validates input, constructs a DTO, and calls one Action. The Action depends on interfaces and contains the use-case rule. The repository adapter trans...Votes: 0GitHub stars: 549
- Laravel Clean ArchitectureFor PHP 8.2, use a `readonly class` for immutable transfer data: ```php readonly class UserData { public function __construct( public string $name, public string $email, ) {} } ``` Construct it only from validated input and pass it between the controller, Action, and repository boundary. A readonly DTO prevents accidental mutation after validation and gives the use case typed fields instead of an unstructured array. If the project is on PHP 8.1, use readonly properties instead. In a Laravel d...Votes: 0GitHub stars: 549
- Laravel Database ExpertOptimize from evidence rather than guessing: 1. Enable `DB::enableQueryLog()` or use Debugbar and capture the slow SQL, bindings, count, and timing. 2. Add migrations for indexes matching the real join and filter columns. 3. Replace N+1 loops with eager aggregates such as `withCount()`/`withSum()`, `joinSub()`, `whereExists()`, or an `addSelect` subquery. 4. Cache stable, frequently read results with `Cache::remember()` and tags where the backend supports them. 5. Configure read/write connect...Votes: 0GitHub stars: 549
- Laravel Database ExpertUse Laravel's cache-aside API around a stable query: ```php $posts = Cache::remember('posts.all', now()->addMinutes(10), function () { return Post::query()->latest()->get(); }); ``` For Redis or another tag-capable store, group related entries: ```php $posts = Cache::tags(['posts'])->remember('posts.all', 600, fn () => Post::latest()->get()); ``` Invalidate the targeted group after a write with `Cache::tags(['posts'])->flush()`. Choose keys that include tenant or user identity where data is s...Votes: 0GitHub stars: 549
- Laravel Database ExpertConfigure separate `read` and `write` hosts in the connection definition, usually in `config/database.php`: ```php 'mysql' => [ 'driver' => 'mysql', 'read' => ['host' => [env('DB_READ_HOST')]], 'write' => ['host' => [env('DB_WRITE_HOST')]], 'sticky' => true, // shared database name, credentials, and options... ], ``` Laravel routes SELECT queries to the read connection and writes to the write connection; no application query rewrite should be needed. `sticky` can keep reads in the same reques...Votes: 0GitHub stars: 549
- Laravel Database ExpertUse cache tags to invalidate one related group without evicting unrelated application data: ```php $value = Cache::tags(['posts', 'user:1']) ->remember('user:1.posts', 600, fn () => User::findOrFail(1)->posts()->latest()->get()); ``` After a post write, flush the smallest relevant group: ```php Cache::tags(['posts'])->flush(); ``` Make keys tenant/user-specific where necessary, select a bounded TTL, and confirm the configured cache store supports tags (Redis and Memcached do; not every store ...Votes: 0GitHub stars: 549
- Laravel Database ExpertBuild the derived relation as a subquery and join it, or use an existence predicate when only membership matters: ```php $latest = Comment::query() ->select('post_id', DB::raw('MAX(created_at) AS latest_comment_at')) ->groupBy('post_id'); $posts = Post::query() ->joinSub($latest, 'latest_comments', function ($join) { $join->on('posts.id', '=', 'latest_comments.post_id'); }) ->select('posts.*', 'latest_comments.latest_comment_at') ->get(); ``` For filtering without selecting child rows: ```php...Votes: 0GitHub stars: 549
- Laravel Database ExpertAvoid loading a child relation once per parent. If the page needs one derived value per post, use an aggregate or correlated subquery in the parent query: ```php $posts = Post::query() ->addSelect(['latest_comment_at' => Comment::select('created_at') ->whereColumn('comments.post_id', 'posts.id') ->latest()->limit(1)]) ->withCount('comments') ->get(); ``` Use `withCount()`, `withSum()`, `joinSub()`, or `whereExists()` according to whether you need an aggregate, a selected value, or only existe...Votes: 0GitHub stars: 549
- Laravel EloquentPrevent N+1 by shaping the relationship query before iterating: ```php $posts = Post::query()->with('author')->latest()->get(); foreach ($posts as $post) { echo $post->author->name; } ``` For counts, use `withCount('comments')` rather than loading comments per row. In development, enable `Model::preventLazyLoading(! app()->isProduction())` in `AppServiceProvider::boot()` so accidental lazy loads throw `LazyLoadingViolationException`. Select only needed columns while retaining relationship key...Votes: 0GitHub stars: 549
- Laravel EloquentDeclare the relationship on the model and load it explicitly with `with()` at the query boundary: ```php class Post extends Model { protected $fillable = ['title', 'body', 'user_id']; public function author(): BelongsTo { return $this->belongsTo(User::class, 'user_id'); } } $posts = Post::with(['author', 'comments'])->latest()->get(); ``` Use `$with` only for relations required by nearly every read. For large results, constrain selected columns while preserving foreign keys and use pagination...Votes: 0GitHub stars: 549
- Laravel EloquentDefine a reusable local scope on the model: ```php use Illuminate\Database\Eloquent\Builder; public function scopeActive(Builder $query): Builder { return $query->where('active', true); } ``` Call it fluently with `User::active()->verified()->get()`. Scopes keep filters close to the model's query vocabulary and prevent controllers from duplicating conditions. Type the `Builder` argument and return value, keep the scope focused, and do not put broad business workflows in a model scope. If the ...Votes: 0GitHub stars: 549
- Laravel EloquentUse `chunk()` when processing many records in bounded batches: ```php User::query()->where('active', true)->chunk(500, function ($users): void { foreach ($users as $user) { // process one user } }); ``` Use `cursor()` or `lazy()` when a streaming iteration is more appropriate. Avoid `get()` for an unbounded table because it materializes every model in memory. Choose stable ordering and be careful when mutating rows during iteration; `chunkById()` is safer for many update workflows. Eager-load...Votes: 0GitHub stars: 549
- Laravel EloquentDefine an explicit `$fillable` allowlist and create models only from validated input: ```php class User extends Model { protected $fillable = ['name', 'email']; } $user = User::create($request->validated()); ``` `$fillable` prevents unexpected request fields from being assigned. Pair it with a Form Request and never pass `$request->all()` to `create()` or `update()`. Keep casts for dates, JSON, and domain types in `$casts` so values have consistent behavior. Treat mass-assignment protection a...Votes: 0GitHub stars: 549
- Laravel EloquentEnable strict lazy-loading prevention outside production: ```php use Illuminate\Database\Eloquent\Model; public function boot(): void { Model::preventLazyLoading(! app()->isProduction()); } ``` Now accidental relationship access without `with()` or `$with` throws `LazyLoadingViolationException` during local and test execution. Fix violations by eager-loading the relation at the query boundary, selecting required relationship keys, or using an aggregate such as `withCount()`. Keep the guard en...Votes: 0GitHub stars: 549
- Laravel SecurityAssuming a `Post` model with an `author_id` column and `User::isAdmin()`: 1. Generate the policy: ```bash php artisan make:policy PostPolicy --model=Post ``` 2. Implement `app/Policies/PostPolicy.php`: ```php <?php namespace App\Policies; use App\Models\Post; use App\Models\User; class PostPolicy { public function view(User $user, Post $post): bool { return $post->is_published || $user->id === $post->author_id; } public function update(User $user, Post $post): bool { return $user->id === $pos...Votes: 0GitHub stars: 549
- Laravel SecurityUse Gates for global permission checks, and Policies for model-specific authorization. ```php // app/Providers/AuthServiceProvider.php use App\Models\User; use Illuminate\Support\Facades\Gate; public function boot(): void { Gate::before(fn (User $user) => $user->isAdmin() ? true : null ); Gate::define('admin', fn (User $user): bool => $user->isAdmin() ); Gate::define('publish-posts', fn (User $user): bool => $user->can_publish ); } ``` Check a Gate in a controller: ```php use Illuminate\Suppo...Votes: 0GitHub stars: 549
- Laravel SecurityPrevent `env()` usage outside `config/*.php`: ```php // config/app.php 'key' => env('APP_KEY'), ``` Use configuration access in application code: ```php // Controller or service $key = config('app.key'); ``` Never call `env()` in controllers—or anywhere not in configuration files. Search the codebase for `env(`, move legitimate calls into configuration files, and replace application-level calls with `config()`. Validate the boundary with: ```bash php artisan config:cache ``` This ensures envi...Votes: 0GitHub stars: 549
- Laravel SecurityUse a Form Request and pass only `$request->validated()` to the model. ```bash php artisan make:request StorePostRequest ``` ```php // app/Http/Requests/StorePostRequest.php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class StorePostRequest extends FormRequest { public function authorize(): bool { return true; // Replace with Policy authorization if required. } public function rules(): array { return [ 'title' => ['required', 'string', 'max:255'], 'body' => ['requ...Votes: 0GitHub stars: 549
- Laravel SecurityAssuming the forms use Laravel’s `web` routes, CSRF protection is enabled by the `web` middleware and `CSRF middleware` by default. Add `@csrf` inside every Blade form: ```blade <form method="POST" action="{{ route('posts.store') }}"> @csrf <!-- fields --> <button type="submit">Save</button> </form> ``` For `PUT`, `PATCH`, or `DELETE` forms, include both directives: ```blade <form method="POST" action="{{ route('posts.destroy', $post) }}"> @csrf @method('DELETE') <button type="submit">Delete<...Votes: 0GitHub stars: 549
- Laravel SecurityAssume `User::isAdmin(): bool` exists and posts have an `user_id` owner. 1. Generate a policy: ```bash php artisan make:policy PostPolicy --model=Post ``` 2. Implement model authorization in `app/Policies/PostPolicy.php`: ```php <?php namespace App\Policies; use App\Models\Post; use App\Models\User; class PostPolicy { public function view(User $user, Post $post): bool { return $post->user_id === $user->id; } public function update(User $user, Post $post): bool { return $post->user_id === $use...Votes: 0GitHub stars: 549
- Laravel Sessions Middleware1. Install the Redis client: ```bash composer require predis/predis ``` 2. Configure `.env`: ```dotenv SESSION_DRIVER=redis SESSION_CONNECTION=default REDIS_CLIENT=predis REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 ``` Ensure `config/session.php` uses the configured driver: ```php 'driver' => env('SESSION_DRIVER', 'file'), 'connection' => env('SESSION_CONNECTION', 'default'), ``` Use `config('session.driver')` in application code—never access `env('SESSION_DRIVER')` directly. 3. ...Votes: 0GitHub stars: 549
- Laravel Sessions MiddlewareCreate the middleware: ```bash php artisan make:middleware EnsureTokenIsValid ``` `app/Http/Middleware/EnsureTokenIsValid.php`: ```php <?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class EnsureTokenIsValid { public function handle(Request $request, Closure $next): Response { abort_unless( hash_equals((string) config('app.internal_token'), (string) $request->header('X-Internal-Token')), 403 ); return $next($request...Votes: 0GitHub stars: 549
- Laravel Sessions MiddlewareCreate middleware: ```bash php artisan make:middleware SecurityHeaders ``` `app/Http/Middleware/SecurityHeaders.php`: ```php <?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class SecurityHeaders { public function handle(Request $request, Closure $next): Response { $response = $next($request); $response->headers->set( 'Strict-Transport-Security', 'max-age=31536000; includeSubDomains' ); $response->headers->set( 'Cont...Votes: 0GitHub stars: 549