@famgia/omnify-ai-guides 2.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -0
- package/dist/chunk-RCTEXK7C.js +549 -0
- package/dist/chunk-RCTEXK7C.js.map +1 -0
- package/dist/config/rules.yaml +524 -0
- package/dist/index.cjs +587 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +55 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge/agents/architect.md.stub +150 -0
- package/dist/knowledge/agents/developer.md.stub +190 -0
- package/dist/knowledge/agents/reviewer.md.stub +134 -0
- package/dist/knowledge/agents/tester.md.stub +196 -0
- package/dist/knowledge/checklists/backend.md.stub +112 -0
- package/dist/knowledge/checklists/react.md.stub +108 -0
- package/dist/knowledge/claude-rules/laravel-controllers.md.stub +57 -0
- package/dist/knowledge/claude-rules/laravel-migrations.md.stub +47 -0
- package/dist/knowledge/claude-rules/laravel-tests.md.stub +52 -0
- package/dist/knowledge/claude-rules/naming.md.stub +369 -0
- package/dist/knowledge/claude-rules/performance.md.stub +256 -0
- package/dist/knowledge/claude-rules/php-standards.md.stub +305 -0
- package/dist/knowledge/claude-rules/react-components.md.stub +67 -0
- package/dist/knowledge/claude-rules/schema-yaml.md.stub +83 -0
- package/dist/knowledge/claude-rules/security.md.stub +164 -0
- package/dist/knowledge/cursor-rules/antd-deprecations.mdc.stub +62 -0
- package/dist/knowledge/cursor-rules/basemodel-readonly.mdc.stub +66 -0
- package/dist/knowledge/cursor-rules/baserequest-readonly.mdc.stub +74 -0
- package/dist/knowledge/cursor-rules/baseresource-readonly.mdc.stub +78 -0
- package/dist/knowledge/cursor-rules/laravel-controller.mdc.stub +421 -0
- package/dist/knowledge/cursor-rules/laravel-request.mdc.stub +112 -0
- package/dist/knowledge/cursor-rules/laravel-resource.mdc.stub +73 -0
- package/dist/knowledge/cursor-rules/laravel-review.mdc.stub +69 -0
- package/dist/knowledge/cursor-rules/laravel-testing.mdc.stub +138 -0
- package/dist/knowledge/cursor-rules/laravel.mdc.stub +138 -0
- package/dist/knowledge/cursor-rules/migrations-workflow.mdc.stub +224 -0
- package/dist/knowledge/cursor-rules/model-editable.mdc.stub +120 -0
- package/dist/knowledge/cursor-rules/omnify-migrations.mdc.stub +109 -0
- package/dist/knowledge/cursor-rules/omnify-schema.mdc.stub +358 -0
- package/dist/knowledge/cursor-rules/omnify.mdc.stub +58 -0
- package/dist/knowledge/cursor-rules/react-design.mdc.stub +693 -0
- package/dist/knowledge/cursor-rules/react-form.mdc.stub +292 -0
- package/dist/knowledge/cursor-rules/react-services.mdc.stub +304 -0
- package/dist/knowledge/cursor-rules/react.mdc.stub +336 -0
- package/dist/knowledge/cursor-rules/request-editable.mdc.stub +111 -0
- package/dist/knowledge/cursor-rules/resource-editable.mdc.stub +125 -0
- package/dist/knowledge/cursor-rules/schema-create.mdc.stub +440 -0
- package/dist/knowledge/cursor-rules/validation-rules.mdc.stub +181 -0
- package/dist/knowledge/laravel/README.md.stub +59 -0
- package/dist/knowledge/laravel/architecture.md.stub +424 -0
- package/dist/knowledge/laravel/authentication.md.stub +588 -0
- package/dist/knowledge/laravel/controller.md.stub +484 -0
- package/dist/knowledge/laravel/datetime.md.stub +334 -0
- package/dist/knowledge/laravel/migrations-team.md.stub +376 -0
- package/dist/knowledge/laravel/openapi.md.stub +449 -0
- package/dist/knowledge/laravel/request.md.stub +450 -0
- package/dist/knowledge/laravel/resource.md.stub +516 -0
- package/dist/knowledge/laravel/service.md.stub +503 -0
- package/dist/knowledge/laravel/testing.md.stub +1504 -0
- package/dist/knowledge/omnify/antdesign-guide.md.stub +401 -0
- package/dist/knowledge/omnify/config-guide.md.stub +405 -0
- package/dist/knowledge/omnify/japan-guide.md.stub +186 -0
- package/dist/knowledge/omnify/laravel-guide.md.stub +61 -0
- package/dist/knowledge/omnify/partial-schema-guide.md.stub +353 -0
- package/dist/knowledge/omnify/react-form-guide.md.stub +225 -0
- package/dist/knowledge/omnify/schema-guide.md.stub +144 -0
- package/dist/knowledge/omnify/typescript-guide.md.stub +337 -0
- package/dist/knowledge/react/README.md.stub +221 -0
- package/dist/knowledge/react/antd-guide.md +528 -0
- package/dist/knowledge/react/antd-guide.md.stub +528 -0
- package/dist/knowledge/react/checklist.md.stub +108 -0
- package/dist/knowledge/react/datetime-guide.md.stub +137 -0
- package/dist/knowledge/react/design-philosophy.md.stub +363 -0
- package/dist/knowledge/react/i18n-guide.md.stub +211 -0
- package/dist/knowledge/react/laravel-integration.md.stub +181 -0
- package/dist/knowledge/react/service-pattern.md.stub +180 -0
- package/dist/knowledge/react/tanstack-query.md.stub +339 -0
- package/dist/knowledge/react/types-guide.md +669 -0
- package/dist/knowledge/react/types-guide.md.stub +669 -0
- package/dist/knowledge/workflows/bug-fix.md.stub +201 -0
- package/dist/knowledge/workflows/code-review.md.stub +164 -0
- package/dist/knowledge/workflows/new-feature.md.stub +327 -0
- package/dist/plugin-M95GyBll.d.cts +191 -0
- package/dist/plugin-M95GyBll.d.ts +191 -0
- package/dist/plugin.cjs +573 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +2 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.js +15 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "React + Ant Design + TanStack Query rules for Omnify projects: component patterns, form validation with Zod, i18n, service layer, and generated types. Apply when creating React components, forms, or API integrations."
|
|
3
|
+
globs: ["{{TYPESCRIPT_BASE}}/**/*.{ts,tsx}"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# React + Ant Design Rules
|
|
8
|
+
|
|
9
|
+
> **Related Rules:**
|
|
10
|
+
> - **Form Development:** `.cursor/rules/omnify/react-form.mdc`
|
|
11
|
+
> - **Design System:** `.cursor/rules/omnify/react-design.mdc`
|
|
12
|
+
> - **Services:** `.cursor/rules/omnify/react-services.mdc`
|
|
13
|
+
> - **Schema Creation:** `.cursor/rules/omnify/schema-create.mdc` (mention `@schema-create` in chat)
|
|
14
|
+
|
|
15
|
+
## Guide Documentation
|
|
16
|
+
|
|
17
|
+
- Read `.claude/omnify/guides/react/` for patterns
|
|
18
|
+
|
|
19
|
+
## Critical Rules
|
|
20
|
+
|
|
21
|
+
1. **Use Ant Design** - Don't recreate existing components
|
|
22
|
+
2. **Use Omnify types** - Import from `@omnify/schemas`, don't duplicate
|
|
23
|
+
3. **Use i18n** - Use `useTranslations()` for UI text
|
|
24
|
+
4. **Service Layer** - API calls in `services/`, not components
|
|
25
|
+
5. **TanStack Query** - For all server state, no `useState` + `useEffect`
|
|
26
|
+
|
|
27
|
+
## Quick Patterns
|
|
28
|
+
|
|
29
|
+
### Service
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
export const userService = {
|
|
33
|
+
list: (params?: UserListParams) => api.get("/api/users", { params }).then(r => r.data),
|
|
34
|
+
create: (input: UserCreate) => api.post("/api/users", input).then(r => r.data.data),
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Query
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const { data, isLoading } = useQuery({
|
|
42
|
+
queryKey: queryKeys.users.list(filters),
|
|
43
|
+
queryFn: () => userService.list(filters),
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Mutation
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const mutation = useMutation({
|
|
51
|
+
mutationFn: userService.create,
|
|
52
|
+
onSuccess: () => {
|
|
53
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
|
|
54
|
+
message.success(t("messages.created"));
|
|
55
|
+
},
|
|
56
|
+
onError: (error) => form.setFields(getFormErrors(error)),
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Types Rule
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// ✅ Use Omnify-generated types
|
|
64
|
+
import type { User, UserCreate } from "@omnify/schemas";
|
|
65
|
+
|
|
66
|
+
// ✅ Only define query params locally
|
|
67
|
+
export interface UserListParams { ... }
|
|
68
|
+
|
|
69
|
+
// ❌ Don't redefine Omnify types
|
|
70
|
+
export interface UserCreateInput { ... } // WRONG
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## ⚠️ Enum Usage Rules (CRITICAL)
|
|
74
|
+
|
|
75
|
+
**ALWAYS use generated Enums** - NEVER inline union types or type extractions!
|
|
76
|
+
|
|
77
|
+
### ❌ BAD Patterns (FORBIDDEN)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// ❌ Complex type extraction - FORBIDDEN!
|
|
81
|
+
newFilter.approval_status = status as NonNullable<ListParams["filter"]>["approval_status"];
|
|
82
|
+
|
|
83
|
+
// ❌ Hardcoded union type - NOT DRY!
|
|
84
|
+
newFilter.approval_status = status as "pending" | "approved" | "rejected";
|
|
85
|
+
|
|
86
|
+
// ❌ Using string type - NO TYPE SAFETY!
|
|
87
|
+
const [status, setStatus] = useState<string>("");
|
|
88
|
+
|
|
89
|
+
// ❌ Hardcoded comparisons
|
|
90
|
+
if (status === "pending") { ... }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### ✅ GOOD Patterns (REQUIRED)
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// ✅ Import Enum from generated file
|
|
97
|
+
import {
|
|
98
|
+
PostStatus,
|
|
99
|
+
PostStatusValues,
|
|
100
|
+
isPostStatus,
|
|
101
|
+
getPostStatusLabel
|
|
102
|
+
} from "@omnify/enum/PostStatus";
|
|
103
|
+
|
|
104
|
+
// ✅ Type assertions with Enum
|
|
105
|
+
newFilter.approval_status = status as PostStatus;
|
|
106
|
+
|
|
107
|
+
// ✅ State with Enum type
|
|
108
|
+
const [status, setStatus] = useState<PostStatus | "">("");
|
|
109
|
+
// OR with default value:
|
|
110
|
+
const [status, setStatus] = useState<PostStatus>(PostStatus.Pending);
|
|
111
|
+
|
|
112
|
+
// ✅ Enum comparisons
|
|
113
|
+
if (status === PostStatus.Pending) { ... }
|
|
114
|
+
|
|
115
|
+
// ✅ Iterate with Values array
|
|
116
|
+
const options = PostStatusValues.map(value => ({
|
|
117
|
+
value,
|
|
118
|
+
label: getPostStatusLabel(value, locale)
|
|
119
|
+
}));
|
|
120
|
+
|
|
121
|
+
// ✅ Type guard for validation
|
|
122
|
+
if (isPostStatus(unknownValue)) {
|
|
123
|
+
// unknownValue is PostStatus here
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Generated Enum Files Structure
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
@omnify/enum/
|
|
131
|
+
├── PostStatus.ts
|
|
132
|
+
│ ├── PostStatus (enum)
|
|
133
|
+
│ ├── PostStatusValues (array of all values)
|
|
134
|
+
│ ├── isPostStatus() (type guard)
|
|
135
|
+
│ └── getPostStatusLabel() (i18n label)
|
|
136
|
+
├── OrderStatus.ts
|
|
137
|
+
└── ...
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Select/Filter Options Pattern
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { PostStatus, PostStatusValues, getPostStatusLabel } from "@omnify/enum/PostStatus";
|
|
144
|
+
|
|
145
|
+
// ✅ Build options from Enum
|
|
146
|
+
const statusOptions = PostStatusValues.map(value => ({
|
|
147
|
+
value,
|
|
148
|
+
label: getPostStatusLabel(value, locale)
|
|
149
|
+
}));
|
|
150
|
+
|
|
151
|
+
<Select options={statusOptions} />
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Form Pattern (Omnify)
|
|
155
|
+
|
|
156
|
+
> **Detailed Guide:** See `react-form.mdc` for complete form development guide.
|
|
157
|
+
|
|
158
|
+
**Quick Reference:**
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// 1. Imports
|
|
162
|
+
import { zodRule, setZodLocale } from '@/omnify/lib';
|
|
163
|
+
import { OmnifyForm } from '@/omnify/components';
|
|
164
|
+
import { customerSchemas, customerI18n, getCustomerFieldLabel } from '@/omnify/schemas';
|
|
165
|
+
|
|
166
|
+
// 2. Helper functions (MUST define in every form)
|
|
167
|
+
const LOCALE = 'ja';
|
|
168
|
+
const label = (key: string) => getCustomerFieldLabel(key, LOCALE);
|
|
169
|
+
const rule = (key: keyof typeof customerSchemas) => zodRule(customerSchemas[key], label(key));
|
|
170
|
+
|
|
171
|
+
// 3. Form.Item pattern
|
|
172
|
+
<Form.Item name="email" label={label('email')} rules={[rule('email')]}>
|
|
173
|
+
<Input />
|
|
174
|
+
</Form.Item>
|
|
175
|
+
|
|
176
|
+
// 4. Japanese compound fields
|
|
177
|
+
<OmnifyForm.JapaneseName schemas={customerSchemas} i18n={customerI18n} prefix="name" />
|
|
178
|
+
<OmnifyForm.JapaneseAddress form={form} schemas={customerSchemas} i18n={customerI18n} prefix="address" />
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## File Location
|
|
182
|
+
|
|
183
|
+
| What | Where |
|
|
184
|
+
| ----------------------- | ------------------------------------------- |
|
|
185
|
+
| Component (1 feature) | `features/{feature}/` |
|
|
186
|
+
| Component (2+ features) | `components/common/` |
|
|
187
|
+
| Service (API) | `services/` (ALWAYS) |
|
|
188
|
+
| Hook (1 feature) | `features/{feature}/` |
|
|
189
|
+
| Hook (2+ features) | `hooks/` |
|
|
190
|
+
| Zod Schema | `schemas/{model}.ts` |
|
|
191
|
+
| Validation utils | `lib/form-validation.ts`, `lib/zod-i18n.ts` |
|
|
192
|
+
|
|
193
|
+
## Ant Design v6 Deprecated Props
|
|
194
|
+
|
|
195
|
+
| Deprecated | Use Instead |
|
|
196
|
+
| -------------------------- | ----------------------- |
|
|
197
|
+
| `visible` | `open` |
|
|
198
|
+
| `direction` (Space) | `orientation` |
|
|
199
|
+
| `dropdownMatchSelectWidth` | `popupMatchSelectWidth` |
|
|
200
|
+
|
|
201
|
+
## Ant Design Static Method Warning
|
|
202
|
+
|
|
203
|
+
⚠️ **Warning:** `Static function can not consume context like dynamic theme`
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// ❌ Wrong - Static import has no context
|
|
207
|
+
import { message } from "antd";
|
|
208
|
+
message.success("Done"); // Warning!
|
|
209
|
+
|
|
210
|
+
// ✅ Correct - Use App.useApp() hook
|
|
211
|
+
import { App } from "antd";
|
|
212
|
+
|
|
213
|
+
function MyComponent() {
|
|
214
|
+
const { message, notification, modal } = App.useApp();
|
|
215
|
+
message.success("Done"); // ✅ No warning
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Note:**
|
|
220
|
+
- `App` component is already wrapped in `AntdThemeProvider`
|
|
221
|
+
- Just use `App.useApp()` in your component/hook
|
|
222
|
+
- Applies to: `message`, `notification`, `modal`
|
|
223
|
+
|
|
224
|
+
## Common Mistakes
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// ❌ Wrong
|
|
228
|
+
useEffect(() => { fetchData() }, []); // Use useQuery
|
|
229
|
+
const [users, setUsers] = useState([]); // Use TanStack for server state
|
|
230
|
+
<Button>Save</Button> // Use i18n
|
|
231
|
+
|
|
232
|
+
// ✅ Correct
|
|
233
|
+
const { data } = useQuery({ queryKey, queryFn });
|
|
234
|
+
<Button>{t("common.save")}</Button>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Form Validation Mistakes
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// ❌ Wrong - Hardcoded message in schema
|
|
241
|
+
z.string().min(1, "Name is required")
|
|
242
|
+
|
|
243
|
+
// ❌ Wrong - Define schema in component
|
|
244
|
+
const schema = z.object({ name: z.string() });
|
|
245
|
+
|
|
246
|
+
// ❌ Wrong - No field label comment
|
|
247
|
+
<Form.Item name="name" label={label("name")}>
|
|
248
|
+
|
|
249
|
+
// ✅ Correct - Schema in schemas/, messages in i18n/
|
|
250
|
+
import { userSchemas } from "@/schemas/user";
|
|
251
|
+
{/* Name */}
|
|
252
|
+
<Form.Item name="name" label={label("name")} rules={[zodRule(userSchemas.name, label("name"))]}>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Form.useForm() Warning
|
|
256
|
+
|
|
257
|
+
⚠️ **Warning:** `Instance created by useForm is not connected to any Form element`
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
// ❌ Wrong - Export hook creates unused form instance
|
|
261
|
+
export function useUserForm() {
|
|
262
|
+
return Form.useForm(); // Instance not connected to any Form!
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ❌ Wrong - Create form instance but don't pass to Form
|
|
266
|
+
const [form] = Form.useForm();
|
|
267
|
+
return <Form>...</Form> // Missing form={form}
|
|
268
|
+
|
|
269
|
+
// ✅ Correct - Form instance must connect to Form
|
|
270
|
+
const [form] = Form.useForm();
|
|
271
|
+
return <Form form={form}>...</Form>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Rule:** Each `Form.useForm()` must have exactly one corresponding `<Form form={form}>`.
|
|
275
|
+
|
|
276
|
+
### Backend Validation Errors (422)
|
|
277
|
+
|
|
278
|
+
⚠️ **IMPORTANT:** Form must display errors from backend!
|
|
279
|
+
|
|
280
|
+
**Form component MUST receive `form` prop from parent:**
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// Form component
|
|
284
|
+
interface UserFormProps {
|
|
285
|
+
form: FormInstance; // ← REQUIRED
|
|
286
|
+
onSubmit: (values: UserCreate) => void;
|
|
287
|
+
// ...
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function UserForm({ form, onSubmit, ... }: UserFormProps) {
|
|
291
|
+
return (
|
|
292
|
+
<Form form={form} onFinish={onSubmit}>
|
|
293
|
+
...
|
|
294
|
+
</Form>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Page creates form and handles backend errors:**
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// Page component
|
|
303
|
+
export default function NewUserPage() {
|
|
304
|
+
const [form] = Form.useForm(); // ← Create in parent
|
|
305
|
+
|
|
306
|
+
const mutation = useMutation({
|
|
307
|
+
mutationFn: userService.create,
|
|
308
|
+
onSuccess: () => { ... },
|
|
309
|
+
onError: (error) => {
|
|
310
|
+
form.setFields(getFormErrors(error)); // ← Set errors from backend
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return (
|
|
315
|
+
<UserForm
|
|
316
|
+
form={form} // ← Pass to form component
|
|
317
|
+
onSubmit={(values) => mutation.mutate(values)}
|
|
318
|
+
/>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Helper `getFormErrors`** (available in `lib/api.ts`):
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import { getFormErrors } from "@/lib/api";
|
|
327
|
+
|
|
328
|
+
// Converts Laravel 422 response to Ant Design format:
|
|
329
|
+
// { errors: { email: ["Email already exists"] } }
|
|
330
|
+
// → [{ name: "email", errors: ["Email already exists"] }]
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**Rules:**
|
|
334
|
+
1. Form component does NOT create `Form.useForm()` - receives from parent
|
|
335
|
+
2. Page creates form instance and passes down
|
|
336
|
+
3. `onError` calls `form.setFields(getFormErrors(error))`
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "User-editable form requests. MUST read corresponding BaseRequest before editing to avoid duplicating auto-generated validation rules."
|
|
3
|
+
globs: ["{{LARAVEL_BASE}}/Http/Requests/*.php"]
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Request Editing Rules
|
|
8
|
+
|
|
9
|
+
These are **user-editable** request files that extend auto-generated BaseRequests.
|
|
10
|
+
|
|
11
|
+
## ⚠️ BEFORE EDITING - MANDATORY STEPS
|
|
12
|
+
|
|
13
|
+
### 1. Read the BaseRequest First
|
|
14
|
+
|
|
15
|
+
Before editing `PostStoreRequest.php`, you **MUST** read `OmnifyBase/PostStoreRequestBase.php` to understand:
|
|
16
|
+
- What validation rules already exist
|
|
17
|
+
- What messages are defined
|
|
18
|
+
- What attributes are set
|
|
19
|
+
|
|
20
|
+
### 2. Check for Existing Rules
|
|
21
|
+
|
|
22
|
+
If a rule exists in BaseRequest, **DO NOT** duplicate it here.
|
|
23
|
+
|
|
24
|
+
## ❌ NEVER DUPLICATE
|
|
25
|
+
|
|
26
|
+
```php
|
|
27
|
+
// These are ALREADY in BaseRequest - NEVER recreate!
|
|
28
|
+
'title' => ['required', 'string', 'max:255'], // ❌ From schema
|
|
29
|
+
'email' => ['required', 'email'], // ❌ From schema
|
|
30
|
+
'status' => ['required', 'in:draft,published'], // ❌ From enum
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## ✅ ONLY ADD Custom Validation
|
|
34
|
+
|
|
35
|
+
```php
|
|
36
|
+
class PostStoreRequest extends PostStoreRequestBase
|
|
37
|
+
{
|
|
38
|
+
/**
|
|
39
|
+
* Merge custom rules with base rules
|
|
40
|
+
*/
|
|
41
|
+
public function rules(): array
|
|
42
|
+
{
|
|
43
|
+
return array_merge(parent::rules(), [
|
|
44
|
+
// ✅ Unique constraint (database-specific)
|
|
45
|
+
'slug' => ['unique:posts,slug'],
|
|
46
|
+
|
|
47
|
+
// ✅ Complex business rules
|
|
48
|
+
'published_at' => ['required_if:status,published'],
|
|
49
|
+
|
|
50
|
+
// ✅ Custom format validation
|
|
51
|
+
'featured_image' => ['url', 'regex:/\.(jpg|png|webp)$/i'],
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Add custom validation logic
|
|
57
|
+
*/
|
|
58
|
+
public function withValidator($validator): void
|
|
59
|
+
{
|
|
60
|
+
$validator->after(function ($validator) {
|
|
61
|
+
// ✅ Cross-field validation
|
|
62
|
+
if ($this->status === 'published' && empty($this->content)) {
|
|
63
|
+
$validator->errors()->add('content', 'Content is required for published posts.');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Custom authorization logic
|
|
70
|
+
*/
|
|
71
|
+
public function authorize(): bool
|
|
72
|
+
{
|
|
73
|
+
// ✅ Add authorization check
|
|
74
|
+
return $this->user()->can('create', Post::class);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Prepare data before validation
|
|
79
|
+
*/
|
|
80
|
+
protected function prepareForValidation(): void
|
|
81
|
+
{
|
|
82
|
+
// ✅ Data transformation
|
|
83
|
+
$this->merge([
|
|
84
|
+
'slug' => Str::slug($this->title),
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## For Update Requests
|
|
91
|
+
|
|
92
|
+
```php
|
|
93
|
+
class PostUpdateRequest extends PostUpdateRequestBase
|
|
94
|
+
{
|
|
95
|
+
public function rules(): array
|
|
96
|
+
{
|
|
97
|
+
return array_merge(parent::rules(), [
|
|
98
|
+
// ✅ Ignore current record for unique check
|
|
99
|
+
'slug' => ['unique:posts,slug,' . $this->route('post')],
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Quick Checklist Before Saving
|
|
106
|
+
|
|
107
|
+
- [ ] Read `OmnifyBase/{Request}Base.php` first
|
|
108
|
+
- [ ] Not duplicating validation rules from BaseRequest
|
|
109
|
+
- [ ] Using `array_merge(parent::rules(), [...])` pattern
|
|
110
|
+
- [ ] Only adding custom/complex validation
|
|
111
|
+
- [ ] Using `parent::authorize()` if BaseRequest has authorization
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "User-editable API resources. MUST read corresponding BaseResource before editing to avoid duplicating auto-generated field mappings."
|
|
3
|
+
globs: ["{{LARAVEL_BASE}}/Http/Resources/*.php"]
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Resource Editing Rules
|
|
8
|
+
|
|
9
|
+
These are **user-editable** resource files that extend auto-generated BaseResources.
|
|
10
|
+
|
|
11
|
+
## ⚠️ BEFORE EDITING - MANDATORY STEPS
|
|
12
|
+
|
|
13
|
+
### 1. Read the BaseResource First
|
|
14
|
+
|
|
15
|
+
Before editing `PostResource.php`, you **MUST** read `OmnifyBase/PostResourceBase.php` to understand:
|
|
16
|
+
- What fields are already mapped
|
|
17
|
+
- What relations are included
|
|
18
|
+
- How timestamps are formatted
|
|
19
|
+
|
|
20
|
+
### 2. Check for Existing Fields
|
|
21
|
+
|
|
22
|
+
If a field exists in BaseResource, **DO NOT** duplicate it here.
|
|
23
|
+
|
|
24
|
+
## ❌ NEVER DUPLICATE
|
|
25
|
+
|
|
26
|
+
```php
|
|
27
|
+
// These are ALREADY in BaseResource - NEVER recreate!
|
|
28
|
+
'id' => $this->id, // ❌ From schema
|
|
29
|
+
'title' => $this->title, // ❌ From schema
|
|
30
|
+
'author' => UserResource::make(...), // ❌ From association
|
|
31
|
+
'created_at' => $this->created_at, // ❌ Auto-included
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## ✅ ONLY ADD Custom Fields
|
|
35
|
+
|
|
36
|
+
```php
|
|
37
|
+
class PostResource extends PostResourceBase
|
|
38
|
+
{
|
|
39
|
+
public function toArray($request): array
|
|
40
|
+
{
|
|
41
|
+
return array_merge(parent::toArray($request), [
|
|
42
|
+
// ✅ Computed fields
|
|
43
|
+
'read_time' => $this->getReadTimeInMinutes(),
|
|
44
|
+
'word_count' => str_word_count($this->content ?? ''),
|
|
45
|
+
|
|
46
|
+
// ✅ Derived boolean flags
|
|
47
|
+
'is_published' => $this->status === 'published',
|
|
48
|
+
'is_featured' => !empty($this->featured_image),
|
|
49
|
+
|
|
50
|
+
// ✅ Formatted data
|
|
51
|
+
'excerpt_html' => Str::markdown($this->excerpt ?? ''),
|
|
52
|
+
|
|
53
|
+
// ✅ Conditional fields
|
|
54
|
+
'edit_url' => $this->when(
|
|
55
|
+
$request->user()?->can('update', $this->resource),
|
|
56
|
+
route('posts.edit', $this->id)
|
|
57
|
+
),
|
|
58
|
+
|
|
59
|
+
// ✅ Aggregated data
|
|
60
|
+
'comments_count' => $this->whenCounted('comments'),
|
|
61
|
+
|
|
62
|
+
// ✅ Custom relations not in schema
|
|
63
|
+
'related_posts' => PostResource::collection(
|
|
64
|
+
$this->whenLoaded('relatedPosts')
|
|
65
|
+
),
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Common Patterns
|
|
72
|
+
|
|
73
|
+
### Conditional Fields Based on User
|
|
74
|
+
|
|
75
|
+
```php
|
|
76
|
+
return array_merge(parent::toArray($request), [
|
|
77
|
+
// Only show to admins
|
|
78
|
+
'internal_notes' => $this->when(
|
|
79
|
+
$request->user()?->isAdmin(),
|
|
80
|
+
$this->internal_notes
|
|
81
|
+
),
|
|
82
|
+
]);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Nested Resource Customization
|
|
86
|
+
|
|
87
|
+
```php
|
|
88
|
+
return array_merge(parent::toArray($request), [
|
|
89
|
+
// Override relation resource
|
|
90
|
+
'author' => $this->whenLoaded('author', function () {
|
|
91
|
+
return [
|
|
92
|
+
'id' => $this->author->id,
|
|
93
|
+
'name' => $this->author->name,
|
|
94
|
+
// Custom: only include essential fields
|
|
95
|
+
];
|
|
96
|
+
}),
|
|
97
|
+
]);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Collection Resources
|
|
101
|
+
|
|
102
|
+
```php
|
|
103
|
+
class PostCollection extends ResourceCollection
|
|
104
|
+
{
|
|
105
|
+
public function toArray($request): array
|
|
106
|
+
{
|
|
107
|
+
return [
|
|
108
|
+
'data' => $this->collection,
|
|
109
|
+
// ✅ Add metadata
|
|
110
|
+
'meta' => [
|
|
111
|
+
'total_published' => $this->collection->where('status', 'published')->count(),
|
|
112
|
+
],
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Quick Checklist Before Saving
|
|
119
|
+
|
|
120
|
+
- [ ] Read `OmnifyBase/{Resource}Base.php` first
|
|
121
|
+
- [ ] Not duplicating fields from BaseResource
|
|
122
|
+
- [ ] Using `array_merge(parent::toArray($request), [...])` pattern
|
|
123
|
+
- [ ] Only adding computed/custom fields
|
|
124
|
+
- [ ] Using `$this->when()` for conditional fields
|
|
125
|
+
- [ ] Using `$this->whenLoaded()` for optional relations
|