3
0
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:
Marcus Moore
2026-08-05 14:07:47 -07:00
parent 18f5e44adc
commit 3a845d5fb0
13 changed files with 175 additions and 0 deletions

11
.ai/rules/actions.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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.