mirror of
https://github.com/snipe/snipe-it.git
synced 2026-08-18 11:15:42 +00:00
Add rules
This commit is contained in:
11
.ai/rules/actions.md
Normal file
11
.ai/rules/actions.md
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
paths:
|
||||
- 'app/Actions/**'
|
||||
---
|
||||
|
||||
# Actions
|
||||
|
||||
## Actions expose a single static run() method
|
||||
An Action is a class in `app/Actions/<Entity>/` named `<Verb><Entity>Action`, with one `public static function run(...)` and no constructor. Call it statically: `DestroySupplierAction::run(supplier: $supplier)`.
|
||||
|
||||
Do not use `handle()`, `execute()`, `__invoke()`, or instantiate the class.
|
||||
18
.ai/rules/api.md
Normal file
18
.ai/rules/api.md
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
paths:
|
||||
- 'app/Http/Controllers/Api/**'
|
||||
---
|
||||
|
||||
# Api
|
||||
|
||||
## Wrap API responses in the standard envelope
|
||||
Every API response goes through the shared envelope:
|
||||
|
||||
`return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('...')));`
|
||||
|
||||
Use `'error'` with a `null` payload for failures, and a translation key for the message. There are no Eloquent API Resources in this project.
|
||||
|
||||
## Page API lists with offset and limit
|
||||
API list endpoints page with `offset`/`limit` request params, resolved through the container as `app('api_offset_value')` and `app('api_limit_value')`, then applied with `->skip($offset)->take($limit)->get()`.
|
||||
|
||||
Do not use `paginate()`, `simplePaginate()`, or `cursorPaginate()` on API endpoints.
|
||||
9
.ai/rules/app.md
Normal file
9
.ai/rules/app.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
paths:
|
||||
- 'app/**'
|
||||
---
|
||||
|
||||
# App
|
||||
|
||||
## Use trans(), never __()
|
||||
Translate with `trans('admin/hardware/message.some_key')` using short dotted keys from `resources/lang/<locale>/`. Never use `__()` — it appears nowhere in this codebase. Add a new key rather than hard-coding English.
|
||||
9
.ai/rules/controllers.md
Normal file
9
.ai/rules/controllers.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
paths:
|
||||
- 'app/Http/Controllers/**'
|
||||
---
|
||||
|
||||
# Controllers
|
||||
|
||||
## No DTOs or repository layer
|
||||
Controllers build Eloquent queries inline and pass models, collections, and arrays around. There are no DTO or repository classes — do not introduce them. Extract to an Action or a Presenter when a controller method gets heavy.
|
||||
18
.ai/rules/index.md
Normal file
18
.ai/rules/index.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Project Rules Index
|
||||
|
||||
Before planning or editing, find the row whose globs match the file's path and read that rule file.
|
||||
|
||||
| Applies to | Rule file |
|
||||
| --- | --- |
|
||||
| app/Actions/** | .ai/rules/actions.md |
|
||||
| app/Http/Controllers/Api/** | .ai/rules/api.md |
|
||||
| app/** | .ai/rules/app.md |
|
||||
| app/Http/Controllers/** | .ai/rules/controllers.md |
|
||||
| app/Livewire/** | .ai/rules/livewire.md |
|
||||
| database/migrations/** | .ai/rules/migrations.md |
|
||||
| app/Models/** | .ai/rules/models.md |
|
||||
| app/Presenters/** | .ai/rules/presenters.md |
|
||||
| app/Providers/** | .ai/rules/providers.md |
|
||||
| app/Http/Requests/** | .ai/rules/requests.md |
|
||||
| tests/** | .ai/rules/tests.md |
|
||||
| resources/views/** | .ai/rules/views.md |
|
||||
9
.ai/rules/livewire.md
Normal file
9
.ai/rules/livewire.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
paths:
|
||||
- 'app/Livewire/**'
|
||||
---
|
||||
|
||||
# Livewire
|
||||
|
||||
## Livewire components are class-based with a separate view
|
||||
A Livewire component is a class in `app/Livewire` plus a kebab-case Blade view in `resources/views/livewire`. Livewire 4 is installed, but this project uses none of its single-file, multi-file, or Volt formats — match the class-plus-view shape.
|
||||
9
.ai/rules/migrations.md
Normal file
9
.ai/rules/migrations.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
paths:
|
||||
- 'database/migrations/**'
|
||||
---
|
||||
|
||||
# Migrations
|
||||
|
||||
## No foreign-key constraints
|
||||
Relationship columns are plain `integer('other_id')` columns (nullable and indexed as needed). Do not add `foreignId()`, `foreignIdFor()`, `constrained()`, or `->foreign()->references()` — this schema has no FK constraints and referential integrity is enforced in application code.
|
||||
22
.ai/rules/models.md
Normal file
22
.ai/rules/models.md
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
paths:
|
||||
- 'app/Models/**'
|
||||
---
|
||||
|
||||
# Models
|
||||
|
||||
## Models validate themselves with watson/validating
|
||||
Models carry their own validation: `use Watson\Validating\ValidatingTrait` plus a `protected $rules` array. `$model->save()` returns false when validation fails and `$model->getErrors()` holds the messages.
|
||||
|
||||
This is a second layer on top of the Form Request, not a replacement for it.
|
||||
|
||||
## Declare a presenter on the model
|
||||
A model that renders in the UI sets `protected $presenter = \App\Presenters\<Entity>Presenter::class` and `use App\Presenters\Presentable`, exposing `$model->present()`.
|
||||
|
||||
## Share model behavior through opt-in traits
|
||||
Cross-cutting model behavior comes from traits in `app/Models/Traits`, opted into per model: `CompanyableTrait` (FMCS scoping), `Loggable` (action log), `Searchable` (API/datatable search), `Requestable`, `Acceptable`, `HasUploads`.
|
||||
|
||||
Add behavior as a trait rather than pushing it into a base class.
|
||||
|
||||
## Casts go in the $casts property
|
||||
Declare casts with `protected $casts = [...]`, not a `casts()` method, even though Laravel 12 supports the method form.
|
||||
11
.ai/rules/presenters.md
Normal file
11
.ai/rules/presenters.md
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
paths:
|
||||
- 'app/Presenters/**'
|
||||
---
|
||||
|
||||
# Presenters
|
||||
|
||||
## Presenters own display and datatable config
|
||||
Display formatting and Bootstrap-table column config belong in `app/Presenters/<Entity>Presenter.php`, reached from the model via `$model->present()`.
|
||||
|
||||
Keep this logic out of controllers, transformers, and Blade.
|
||||
14
.ai/rules/providers.md
Normal file
14
.ai/rules/providers.md
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
paths:
|
||||
- 'app/Providers/**'
|
||||
---
|
||||
|
||||
# Providers
|
||||
|
||||
## Named validation rules live in ValidationServiceProvider
|
||||
Add a new named validation rule as a `Validator::extend()` (or `extendImplicit()`) closure in `app/Providers/ValidationServiceProvider.php`, then reference it by its string name in `$rules`.
|
||||
|
||||
`app/Rules` is reserved for the encrypted-custom-field rule objects — do not add general-purpose rules there.
|
||||
|
||||
## Register observers in AppServiceProvider
|
||||
Wire an observer with `Model::observe(ModelObserver::class)` in `AppServiceProvider::boot()`. Do not use the `#[ObservedBy]` attribute on the model.
|
||||
16
.ai/rules/requests.md
Normal file
16
.ai/rules/requests.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
paths:
|
||||
- 'app/Http/Requests/**'
|
||||
---
|
||||
|
||||
# Requests
|
||||
|
||||
## Form Requests are the validation entry point
|
||||
Validate HTTP input with a Form Request class, not inline `$request->validate()` or `Validator::make()`.
|
||||
|
||||
Extend `App\Http\Requests\Request` and declare rules in the `protected $rules` property — the base class returns it from `rules()`. When the request handles file uploads, extend `ImageUploadRequest` instead and call `$request->handleImages($model)` in the controller.
|
||||
|
||||
## Always call parent::prepareForValidation()
|
||||
`ImageUploadRequest` inherits `prepareForValidation()` from the `ConvertsBase64ToFiles` trait, which turns base64 payloads into `UploadedFile` instances before rules run.
|
||||
|
||||
If a child request overrides `prepareForValidation()`, it MUST call `parent::prepareForValidation()` — usually first. Forgetting it silently breaks base64 image uploads with no validation error to point at. This has bitten us before.
|
||||
15
.ai/rules/tests.md
Normal file
15
.ai/rules/tests.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
paths:
|
||||
- 'tests/**'
|
||||
---
|
||||
|
||||
# Tests
|
||||
|
||||
## Database refresh comes from the base TestCase
|
||||
`Tests\TestCase` already applies `LazilyRefreshDatabase` and seeds settings via `InitializesSettings`. Do not add `RefreshDatabase`, `DatabaseTransactions`, or `DatabaseMigrations` to an individual test.
|
||||
|
||||
## Test methods are snake_case
|
||||
Name test methods in snake_case: `test_page_renders()`, `test_requires_permission()`. Never camelCase.
|
||||
|
||||
## Authenticate API tests with actingAsForApi()
|
||||
Use `$this->actingAsForApi($user)` in API tests and `$this->actingAs($user)` in UI tests.
|
||||
14
.ai/rules/views.md
Normal file
14
.ai/rules/views.md
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
paths:
|
||||
- 'resources/views/**'
|
||||
---
|
||||
|
||||
# Views
|
||||
|
||||
## Use trans(), never __()
|
||||
Translate with `trans('general.some_key')` using short dotted keys from `resources/lang/<locale>/`. Never use `__()` — it appears nowhere in this codebase. Add a new key rather than hard-coding English.
|
||||
|
||||
## Blade composition: layouts plus anonymous components
|
||||
Pages `@extends` a layout. Reusable markup is an anonymous Blade component: a file in `resources/views/components/` declaring `@props([...])`, used as `<x-name />`.
|
||||
|
||||
Do not create class-based components — there is no `app/View/Components` directory.
|
||||
Reference in New Issue
Block a user