create-ern-boilerplate 0.0.49 → 0.0.51
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/.claude/settings.local.json +9 -0
- package/package.json +1 -1
- package/templates/agent-generator/AI_GUIDE.md +16 -12
- package/templates/agent-generator/SERVER_GUIDE.md +82 -102
- package/templates/agent-generator/ai-context.json +2 -0
- package/templates/agent-generator/examples/services/mockApi/resource-advanced.mock.example.ts +132 -0
- package/templates/agent-generator/examples/services/mockApi/resource-basic.mock.example.ts +88 -0
- package/templates/agent-generator/examples/types/resource.types.example.ts +61 -0
- package/templates/agent-generator/package.json +1 -0
- package/templates/agent-generator/server/db.json +0 -80
- package/templates/agent-generator/src/services/mockApi/index.ts +0 -26
- package/templates/agent-generator/src/services/mockApi/categories.mock.ts +0 -75
- package/templates/agent-generator/src/services/mockApi/products.mock.ts +0 -125
- package/templates/agent-generator/src/types/product.types.ts +0 -49
package/package.json
CHANGED
|
@@ -131,15 +131,17 @@ Does your app need user accounts?
|
|
|
131
131
|
|
|
132
132
|
Quick reference:
|
|
133
133
|
```
|
|
134
|
-
app/(tabs)/
|
|
135
|
-
src/components/
|
|
136
|
-
src/hooks/
|
|
137
|
-
src/services/
|
|
138
|
-
src/services/mockApi/
|
|
139
|
-
src/store/
|
|
140
|
-
src/types/
|
|
141
|
-
server/db.json
|
|
142
|
-
examples/
|
|
134
|
+
app/(tabs)/ → Screens
|
|
135
|
+
src/components/ → Components
|
|
136
|
+
src/hooks/ → Custom hooks
|
|
137
|
+
src/services/ → API services
|
|
138
|
+
src/services/mockApi/ → Mock API implementations (auth, users only)
|
|
139
|
+
src/store/ → Zustand stores
|
|
140
|
+
src/types/ → TypeScript types
|
|
141
|
+
server/db.json → Mock data (users only by default)
|
|
142
|
+
examples/ → Code examples
|
|
143
|
+
examples/services/mockApi/ → Mock API pattern examples (basic & advanced CRUD)
|
|
144
|
+
examples/types/ → Type definition examples (generic resource types)
|
|
143
145
|
```
|
|
144
146
|
|
|
145
147
|
## ⚠️ Common Mistakes to Avoid
|
|
@@ -177,12 +179,14 @@ cat SERVER_GUIDE.md # Mock API guide
|
|
|
177
179
|
# Check examples
|
|
178
180
|
ls examples/screens/ # Screen patterns
|
|
179
181
|
ls examples/services/ # Service patterns
|
|
182
|
+
ls examples/services/mockApi/ # Mock API pattern examples
|
|
183
|
+
ls examples/types/ # Type definition examples
|
|
180
184
|
ls examples/apps/ # Complete app examples
|
|
181
185
|
|
|
182
186
|
# Check current structure
|
|
183
|
-
cat server/db.json # Mock data
|
|
184
|
-
ls src/types/ # Type definitions
|
|
185
|
-
ls src/services/mockApi/ #
|
|
187
|
+
cat server/db.json # Mock data (users only)
|
|
188
|
+
ls src/types/ # Type definitions (auth, user types)
|
|
189
|
+
ls src/services/mockApi/ # Active mock API files (auth, users)
|
|
186
190
|
```
|
|
187
191
|
|
|
188
192
|
## ✅ Success Checklist
|
|
@@ -43,22 +43,29 @@ The `server/db.json` file contains **minimal sample/starter data** for demonstra
|
|
|
43
43
|
```
|
|
44
44
|
templates/agent-generator/
|
|
45
45
|
├── server/
|
|
46
|
-
│ └── db.json # Mock database (
|
|
46
|
+
│ └── db.json # Mock database (users only by default)
|
|
47
47
|
│
|
|
48
48
|
├── src/
|
|
49
49
|
│ ├── types/ # TypeScript interfaces
|
|
50
50
|
│ │ ├── auth.types.ts # User & Auth types
|
|
51
|
-
│ │
|
|
52
|
-
│ │ └── product.types.ts # Product & Category types
|
|
51
|
+
│ │ └── user.types.ts # User data types
|
|
53
52
|
│ │
|
|
54
53
|
│ └── services/
|
|
55
54
|
│ ├── mockApi/ # Mock API implementations
|
|
56
55
|
│ │ ├── auth.mock.ts # Auth endpoints (login, register, etc)
|
|
57
56
|
│ │ ├── users.mock.ts # User CRUD endpoints
|
|
58
|
-
│ │
|
|
59
|
-
│ │ └── categories.mock.ts # Category CRUD endpoints
|
|
57
|
+
│ │ └── index.ts # Mock API router
|
|
60
58
|
│ │
|
|
61
59
|
│ └── api.ts # API client (switches between mock/real)
|
|
60
|
+
│
|
|
61
|
+
└── examples/ # Pattern examples for AI generation
|
|
62
|
+
├── types/
|
|
63
|
+
│ └── resource.types.example.ts # Example: Generic resource & tag types
|
|
64
|
+
│
|
|
65
|
+
└── services/
|
|
66
|
+
└── mockApi/
|
|
67
|
+
├── resource-advanced.mock.example.ts # Example: Advanced CRUD (filtering, sorting, pagination)
|
|
68
|
+
└── resource-basic.mock.example.ts # Example: Basic CRUD operations
|
|
62
69
|
```
|
|
63
70
|
|
|
64
71
|
## 📋 Understanding server/db.json
|
|
@@ -69,23 +76,26 @@ The `server/db.json` file is the **mock database** that contains sample/starter
|
|
|
69
76
|
|
|
70
77
|
**Current Sample Data:**
|
|
71
78
|
- **3 users** (1 admin, 1 active user, 1 inactive user) - demonstrates different roles and statuses
|
|
72
|
-
- **3 categories** (Electronics, Food, Clothing) - shows category structure
|
|
73
|
-
- **4 products** (includes 1 low stock item) - demonstrates normal and edge cases
|
|
74
79
|
|
|
75
80
|
**Format:**
|
|
76
81
|
```json
|
|
77
82
|
{
|
|
78
|
-
"users": [...]
|
|
79
|
-
"categories": [...], // Array of Category objects (sample: 3 items)
|
|
80
|
-
"products": [...] // Array of Product objects (sample: 4 items)
|
|
83
|
+
"users": [...] // Array of User objects (sample: 3 items)
|
|
81
84
|
}
|
|
82
85
|
```
|
|
83
86
|
|
|
84
|
-
**⚠️ This is minimal starter data!** When you add new features (e.g., News app, Todo app), you'll add new arrays
|
|
87
|
+
**⚠️ This is minimal starter data!** When you add new features (e.g., News app, Todo app, Product management), you'll add new arrays with data specific to your app.
|
|
88
|
+
|
|
89
|
+
**📚 Example Data Models:**
|
|
90
|
+
For reference, check `examples/types/` and `examples/services/mockApi/` which contain generic pattern implementations:
|
|
91
|
+
- **Resource** - Generic data model with title, content, status, tags
|
|
92
|
+
- **Tag** - Simple categorization/grouping system
|
|
93
|
+
|
|
94
|
+
These examples are intentionally generic and show you how to structure data models with filtering, sorting, and relationships. Adapt them for your specific use case (News → Article, Todo → Task, E-commerce → Product, etc.).
|
|
85
95
|
|
|
86
96
|
### Current Data Models
|
|
87
97
|
|
|
88
|
-
####
|
|
98
|
+
#### Users
|
|
89
99
|
|
|
90
100
|
Based on `User` interface from `src/types/auth.types.ts`:
|
|
91
101
|
|
|
@@ -124,69 +134,19 @@ interface User {
|
|
|
124
134
|
}
|
|
125
135
|
```
|
|
126
136
|
|
|
127
|
-
|
|
137
|
+
### Example Data Models (For Reference)
|
|
128
138
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
id: string; // Required: Unique identifier
|
|
134
|
-
name: string; // Required: Category name
|
|
135
|
-
slug: string; // Required: URL-friendly name
|
|
136
|
-
description: string; // Required: Category description
|
|
137
|
-
icon: string; // Required: Emoji or icon
|
|
138
|
-
color: string; // Required: Hex color code
|
|
139
|
-
}
|
|
140
|
-
```
|
|
139
|
+
For generic data model pattern examples, see:
|
|
140
|
+
- `examples/types/resource.types.example.ts` - Generic Resource and Tag type definitions
|
|
141
|
+
- `examples/services/mockApi/resource-advanced.mock.example.ts` - Advanced CRUD with filtering, sorting, pagination
|
|
142
|
+
- `examples/services/mockApi/resource-basic.mock.example.ts` - Basic CRUD operations
|
|
141
143
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
"description": "Electronic devices and accessories",
|
|
149
|
-
"icon": "📱",
|
|
150
|
-
"color": "#3b82f6"
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
#### 3. Products
|
|
155
|
-
|
|
156
|
-
Based on `Product` interface from `src/types/product.types.ts`:
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
interface Product {
|
|
160
|
-
id: string; // Required: Unique identifier
|
|
161
|
-
name: string; // Required: Product name
|
|
162
|
-
categoryId: string; // Required: Foreign key to categories
|
|
163
|
-
unit: string; // Required: Unit of measurement (pcs, kg, liter, etc)
|
|
164
|
-
stockSystem: number; // Required: Stock in system
|
|
165
|
-
stockPhysical: number; // Required: Actual physical stock
|
|
166
|
-
minStock: number; // Required: Minimum stock threshold
|
|
167
|
-
price: number; // Required: Product price
|
|
168
|
-
description: string; // Required: Product description
|
|
169
|
-
createdAt: string; // Required: ISO date string
|
|
170
|
-
updatedAt: string; // Required: ISO date string
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
**Example:**
|
|
175
|
-
```json
|
|
176
|
-
{
|
|
177
|
-
"id": "1",
|
|
178
|
-
"name": "iPhone 15 Pro",
|
|
179
|
-
"categoryId": "1",
|
|
180
|
-
"unit": "pcs",
|
|
181
|
-
"stockSystem": 50,
|
|
182
|
-
"stockPhysical": 48,
|
|
183
|
-
"minStock": 10,
|
|
184
|
-
"price": 15000000,
|
|
185
|
-
"description": "Latest iPhone with A17 Pro chip",
|
|
186
|
-
"createdAt": "2024-01-10T00:00:00.000Z",
|
|
187
|
-
"updatedAt": "2024-01-10T00:00:00.000Z"
|
|
188
|
-
}
|
|
189
|
-
```
|
|
144
|
+
These examples demonstrate:
|
|
145
|
+
- Complex filtering (search, tags, status)
|
|
146
|
+
- Multiple sort options (date, title)
|
|
147
|
+
- Pagination implementation
|
|
148
|
+
- Foreign key relationships (tagId)
|
|
149
|
+
- Generic patterns adaptable to any domain
|
|
190
150
|
|
|
191
151
|
## 🎯 How Mock API Works
|
|
192
152
|
|
|
@@ -195,58 +155,70 @@ interface Product {
|
|
|
195
155
|
Each mock file imports data from `server/db.json`:
|
|
196
156
|
|
|
197
157
|
```typescript
|
|
198
|
-
// src/services/mockApi/
|
|
158
|
+
// Active example: src/services/mockApi/users.mock.ts
|
|
199
159
|
import * as db from 'server/db.json';
|
|
200
160
|
|
|
201
|
-
let
|
|
161
|
+
let users: User[] = [...db.users]; // Clone the data
|
|
202
162
|
```
|
|
203
163
|
|
|
164
|
+
For reference, see `examples/services/mockApi/resource-advanced.mock.example.ts` and `resource-basic.mock.example.ts` which follow the same pattern.
|
|
165
|
+
|
|
204
166
|
### 2. CRUD Operations
|
|
205
167
|
|
|
206
|
-
Mock files provide functions that simulate API endpoints:
|
|
168
|
+
Mock files provide functions that simulate API endpoints. Here's the pattern (see `src/services/mockApi/users.mock.ts` or `examples/services/mockApi/resource-advanced.mock.example.ts`):
|
|
207
169
|
|
|
208
170
|
```typescript
|
|
209
|
-
// GET /
|
|
171
|
+
// GET /resource
|
|
210
172
|
export async function get<T = any>(url: string): Promise<T> {
|
|
211
|
-
if (url === '/
|
|
212
|
-
return
|
|
173
|
+
if (url === '/resource') {
|
|
174
|
+
return items as unknown as T;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Get single item by ID
|
|
178
|
+
const match = url.match(/\/resource\/(\w+)/);
|
|
179
|
+
if (match) {
|
|
180
|
+
const item = items.find(i => i.id === match[1]);
|
|
181
|
+
if (!item) throw new Error('Item not found');
|
|
182
|
+
return item as unknown as T;
|
|
213
183
|
}
|
|
214
|
-
// ...
|
|
215
184
|
}
|
|
216
185
|
|
|
217
|
-
// POST /
|
|
218
|
-
export async function post<T = any>(url: string, body:
|
|
219
|
-
const
|
|
186
|
+
// POST /resource
|
|
187
|
+
export async function post<T = any>(url: string, body: CreateData): Promise<T> {
|
|
188
|
+
const newItem = {
|
|
220
189
|
id: String(Date.now()),
|
|
221
190
|
...body,
|
|
222
191
|
createdAt: new Date().toISOString(),
|
|
223
192
|
updatedAt: new Date().toISOString(),
|
|
224
193
|
};
|
|
225
|
-
|
|
226
|
-
return
|
|
194
|
+
items.push(newItem);
|
|
195
|
+
return newItem as unknown as T;
|
|
227
196
|
}
|
|
228
197
|
|
|
229
|
-
// PUT /
|
|
230
|
-
export async function put<T = any>(url: string, body:
|
|
231
|
-
const match = url.match(/\/
|
|
198
|
+
// PUT /resource/:id
|
|
199
|
+
export async function put<T = any>(url: string, body: UpdateData): Promise<T> {
|
|
200
|
+
const match = url.match(/\/resource\/(\w+)/);
|
|
232
201
|
if (match) {
|
|
233
202
|
const id = match[1];
|
|
234
|
-
const index =
|
|
235
|
-
|
|
236
|
-
|
|
203
|
+
const index = items.findIndex(i => i.id === id);
|
|
204
|
+
if (index === -1) throw new Error('Item not found');
|
|
205
|
+
items[index] = { ...items[index], ...body, updatedAt: new Date().toISOString() };
|
|
206
|
+
return items[index] as unknown as T;
|
|
237
207
|
}
|
|
238
208
|
}
|
|
239
209
|
|
|
240
|
-
// DELETE /
|
|
210
|
+
// DELETE /resource/:id
|
|
241
211
|
export async function del<T = any>(url: string): Promise<T> {
|
|
242
|
-
const match = url.match(/\/
|
|
212
|
+
const match = url.match(/\/resource\/(\w+)/);
|
|
243
213
|
if (match) {
|
|
244
|
-
|
|
245
|
-
return { message: 'Deleted' } as unknown as T;
|
|
214
|
+
items = items.filter(i => i.id !== match[1]);
|
|
215
|
+
return { message: 'Deleted successfully' } as unknown as T;
|
|
246
216
|
}
|
|
247
217
|
}
|
|
248
218
|
```
|
|
249
219
|
|
|
220
|
+
**💡 Tip:** For complex filtering, sorting, and pagination examples, see `examples/services/mockApi/resource-advanced.mock.example.ts`
|
|
221
|
+
|
|
250
222
|
### 3. API Client Integration
|
|
251
223
|
|
|
252
224
|
The API client (`src/services/api.ts`) switches between mock and real API:
|
|
@@ -555,8 +527,12 @@ When generating code that needs data:
|
|
|
555
527
|
### 1. Check Existing Types First
|
|
556
528
|
|
|
557
529
|
```bash
|
|
558
|
-
# Read
|
|
559
|
-
cat src/types/
|
|
530
|
+
# Read active type definitions
|
|
531
|
+
cat src/types/auth.types.ts
|
|
532
|
+
cat src/types/user.types.ts
|
|
533
|
+
|
|
534
|
+
# Read example type definitions
|
|
535
|
+
cat examples/types/resource.types.example.ts
|
|
560
536
|
```
|
|
561
537
|
|
|
562
538
|
### 2. Check db.json Structure
|
|
@@ -569,7 +545,8 @@ cat server/db.json
|
|
|
569
545
|
### 3. Follow the Pattern
|
|
570
546
|
|
|
571
547
|
Copy existing mock API patterns:
|
|
572
|
-
- Read `src/services/mockApi/
|
|
548
|
+
- For basic CRUD: Read `src/services/mockApi/users.mock.ts` or `examples/services/mockApi/resource-basic.mock.example.ts`
|
|
549
|
+
- For advanced features: Read `examples/services/mockApi/resource-advanced.mock.example.ts`
|
|
573
550
|
- Copy the CRUD structure
|
|
574
551
|
- Adapt for your new data model
|
|
575
552
|
|
|
@@ -732,10 +709,13 @@ For db.json:
|
|
|
732
709
|
## 📞 Need Help?
|
|
733
710
|
|
|
734
711
|
Check these files for examples:
|
|
735
|
-
- `server/db.json` - Current dummy data
|
|
736
|
-
- `src/types/*.types.ts` -
|
|
737
|
-
- `src/services/mockApi/*.mock.ts` -
|
|
738
|
-
- `examples/
|
|
712
|
+
- `server/db.json` - Current dummy data (users only)
|
|
713
|
+
- `src/types/*.types.ts` - Active type definitions (auth, user)
|
|
714
|
+
- `src/services/mockApi/*.mock.ts` - Active mock API implementations (auth, users)
|
|
715
|
+
- `examples/types/resource.types.example.ts` - Generic resource & tag type definitions
|
|
716
|
+
- `examples/services/mockApi/resource-advanced.mock.example.ts` - Advanced CRUD pattern
|
|
717
|
+
- `examples/services/mockApi/resource-basic.mock.example.ts` - Basic CRUD pattern
|
|
718
|
+
- `examples/services/*.example.ts` - Service patterns
|
|
739
719
|
|
|
740
720
|
---
|
|
741
721
|
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"examples": "examples",
|
|
13
13
|
"examplesScreens": "examples/screens",
|
|
14
14
|
"examplesServices": "examples/services",
|
|
15
|
+
"examplesMockApi": "examples/services/mockApi",
|
|
16
|
+
"examplesTypes": "examples/types",
|
|
15
17
|
"examplesApps": "examples/apps",
|
|
16
18
|
"aiGuide": "AI_GUIDE.md",
|
|
17
19
|
"serverGuide": "SERVER_GUIDE.md",
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXAMPLE: Advanced Mock API Pattern
|
|
3
|
+
*
|
|
4
|
+
* This demonstrates a complete CRUD API with:
|
|
5
|
+
* - Filtering (search, tag, status)
|
|
6
|
+
* - Sorting (multiple options)
|
|
7
|
+
* - Pagination
|
|
8
|
+
*
|
|
9
|
+
* Adapt for your use case:
|
|
10
|
+
* - News app: resources → articles, tags → topics
|
|
11
|
+
* - Todo app: resources → tasks, tags → projects
|
|
12
|
+
* - E-commerce: resources → products, tags → categories
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { CreateResourceData, Resource, ResourceFilters, UpdateResourceData } from '@/types/resource.types.example';
|
|
16
|
+
import * as db from 'server/db.json';
|
|
17
|
+
|
|
18
|
+
// Initialize from db.json (adapt this to your data structure)
|
|
19
|
+
let resources: Resource[] = [...(db.resources || [])];
|
|
20
|
+
|
|
21
|
+
export async function get<T = any>(url: string, config?: { params?: ResourceFilters }): Promise<T> {
|
|
22
|
+
if (url === '/resources') {
|
|
23
|
+
const { params } = config || {};
|
|
24
|
+
let filtered = [...resources];
|
|
25
|
+
|
|
26
|
+
if (params) {
|
|
27
|
+
const { search, tagId, status, sortBy, limit = 10, page = 1 } = params;
|
|
28
|
+
|
|
29
|
+
// Filter by search (title and content)
|
|
30
|
+
if (search) {
|
|
31
|
+
const q = search.toLowerCase();
|
|
32
|
+
filtered = filtered.filter(
|
|
33
|
+
r => r.title.toLowerCase().includes(q) ||
|
|
34
|
+
r.content?.toLowerCase().includes(q)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Filter by tag
|
|
39
|
+
if (tagId) {
|
|
40
|
+
filtered = filtered.filter(r => r.tagId === tagId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Filter by status
|
|
44
|
+
if (status) {
|
|
45
|
+
filtered = filtered.filter(r => r.status === status);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Sort
|
|
49
|
+
if (sortBy === 'latest') {
|
|
50
|
+
filtered.sort((a, b) => +new Date(b.createdAt) - +new Date(a.createdAt));
|
|
51
|
+
} else if (sortBy === 'oldest') {
|
|
52
|
+
filtered.sort((a, b) => +new Date(a.createdAt) - +new Date(b.createdAt));
|
|
53
|
+
} else if (sortBy === 'title') {
|
|
54
|
+
filtered.sort((a, b) => a.title.localeCompare(b.title));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Pagination
|
|
58
|
+
const start = (page - 1) * limit;
|
|
59
|
+
return filtered.slice(start, start + limit) as unknown as T;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return resources as unknown as T;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Get single resource by ID
|
|
66
|
+
const match = url.match(/\/resources\/(\w+)/);
|
|
67
|
+
if (match) {
|
|
68
|
+
const resource = resources.find(r => r.id === match[1]);
|
|
69
|
+
if (!resource) throw new Error('Resource not found');
|
|
70
|
+
return resource as unknown as T;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw new Error(`Unknown GET /resources endpoint: ${url}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function post<T = any>(url: string, body?: CreateResourceData): Promise<T> {
|
|
77
|
+
if (url === '/resources') {
|
|
78
|
+
if (!body) throw new Error('Resource data is required');
|
|
79
|
+
|
|
80
|
+
const newResource: Resource = {
|
|
81
|
+
id: String(Date.now()),
|
|
82
|
+
title: body.title,
|
|
83
|
+
content: body.content,
|
|
84
|
+
tagId: body.tagId,
|
|
85
|
+
status: body.status || 'draft',
|
|
86
|
+
metadata: body.metadata,
|
|
87
|
+
createdAt: new Date().toISOString(),
|
|
88
|
+
updatedAt: new Date().toISOString(),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
resources.push(newResource);
|
|
92
|
+
return newResource as unknown as T;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw new Error(`Unknown POST /resources endpoint: ${url}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function put<T = any>(url: string, body: UpdateResourceData): Promise<T> {
|
|
99
|
+
const match = url.match(/\/resources\/(\w+)/);
|
|
100
|
+
if (match) {
|
|
101
|
+
const id = match[1];
|
|
102
|
+
const index = resources.findIndex(r => r.id === id);
|
|
103
|
+
if (index === -1) throw new Error('Resource not found');
|
|
104
|
+
|
|
105
|
+
resources[index] = {
|
|
106
|
+
...resources[index],
|
|
107
|
+
...body,
|
|
108
|
+
updatedAt: new Date().toISOString()
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return resources[index] as unknown as T;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw new Error(`Unknown PUT /resources endpoint: ${url}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function del<T = any>(url: string): Promise<T> {
|
|
118
|
+
const match = url.match(/\/resources\/(\w+)/);
|
|
119
|
+
if (match) {
|
|
120
|
+
const id = match[1];
|
|
121
|
+
const initialLength = resources.length;
|
|
122
|
+
resources = resources.filter(r => r.id !== id);
|
|
123
|
+
|
|
124
|
+
if (resources.length === initialLength) {
|
|
125
|
+
throw new Error('Resource not found');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { message: 'Resource deleted successfully' } as unknown as T;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
throw new Error(`Unknown DELETE /resources endpoint: ${url}`);
|
|
132
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXAMPLE: Basic Mock API Pattern
|
|
3
|
+
*
|
|
4
|
+
* This demonstrates a simple CRUD API without filtering/pagination.
|
|
5
|
+
* Use this pattern for simpler data models.
|
|
6
|
+
*
|
|
7
|
+
* Adapt for your use case:
|
|
8
|
+
* - News app: tags → topics
|
|
9
|
+
* - Todo app: tags → projects
|
|
10
|
+
* - E-commerce: tags → categories
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Tag } from '@/types/resource.types.example';
|
|
14
|
+
import * as db from 'server/db.json';
|
|
15
|
+
|
|
16
|
+
// Initialize from db.json (adapt this to your data structure)
|
|
17
|
+
let tags: Tag[] = [...(db.tags || [])];
|
|
18
|
+
|
|
19
|
+
export async function get<T = any>(url: string, config?: any): Promise<T> {
|
|
20
|
+
if (url === '/tags') {
|
|
21
|
+
return tags as unknown as T;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Get single tag by ID
|
|
25
|
+
const match = url.match(/\/tags\/(\w+)/);
|
|
26
|
+
if (match) {
|
|
27
|
+
const tag = tags.find(t => t.id === match[1]);
|
|
28
|
+
if (!tag) throw new Error('Tag not found');
|
|
29
|
+
return tag as unknown as T;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
throw new Error(`Unknown GET /tags endpoint: ${url}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function post<T = any>(url: string, body?: Partial<Tag>): Promise<T> {
|
|
36
|
+
if (url === '/tags') {
|
|
37
|
+
if (!body) throw new Error('Tag data is required');
|
|
38
|
+
|
|
39
|
+
const newTag: Tag = {
|
|
40
|
+
id: String(Date.now()),
|
|
41
|
+
name: body.name || '',
|
|
42
|
+
slug: body.slug || body.name?.toLowerCase().replace(/\s+/g, '-') || '',
|
|
43
|
+
description: body.description || '',
|
|
44
|
+
icon: body.icon || '🏷️',
|
|
45
|
+
color: body.color || '#6366f1',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
tags.push(newTag);
|
|
49
|
+
return newTag as unknown as T;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
throw new Error(`Unknown POST /tags endpoint: ${url}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function put<T = any>(url: string, body: Partial<Tag>): Promise<T> {
|
|
56
|
+
const match = url.match(/\/tags\/(\w+)/);
|
|
57
|
+
if (match) {
|
|
58
|
+
const id = match[1];
|
|
59
|
+
const index = tags.findIndex(t => t.id === id);
|
|
60
|
+
if (index === -1) throw new Error('Tag not found');
|
|
61
|
+
|
|
62
|
+
tags[index] = {
|
|
63
|
+
...tags[index],
|
|
64
|
+
...body
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return tags[index] as unknown as T;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
throw new Error(`Unknown PUT /tags endpoint: ${url}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function del<T = any>(url: string): Promise<T> {
|
|
74
|
+
const match = url.match(/\/tags\/(\w+)/);
|
|
75
|
+
if (match) {
|
|
76
|
+
const id = match[1];
|
|
77
|
+
const initialLength = tags.length;
|
|
78
|
+
tags = tags.filter(t => t.id !== id);
|
|
79
|
+
|
|
80
|
+
if (tags.length === initialLength) {
|
|
81
|
+
throw new Error('Tag not found');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { message: 'Tag deleted successfully' } as unknown as T;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw new Error(`Unknown DELETE /tags endpoint: ${url}`);
|
|
88
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXAMPLE: Generic Resource Types
|
|
3
|
+
*
|
|
4
|
+
* This file demonstrates type definitions for a generic resource model.
|
|
5
|
+
* Adapt these patterns for your specific use case:
|
|
6
|
+
* - News app: Resource → Article, Tag → Topic
|
|
7
|
+
* - Todo app: Resource → Task, Tag → Project
|
|
8
|
+
* - E-commerce: Resource → Product, Tag → Category
|
|
9
|
+
* - Blog: Resource → Post, Tag → Category
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Basic tag/category system
|
|
13
|
+
export interface Tag {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
description: string;
|
|
18
|
+
icon: string;
|
|
19
|
+
color: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Main resource with common fields
|
|
23
|
+
export interface Resource {
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
content: string;
|
|
27
|
+
tagId: string;
|
|
28
|
+
status: 'active' | 'inactive' | 'draft';
|
|
29
|
+
metadata?: string; // JSON string for flexible extra data
|
|
30
|
+
createdAt: string;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Filtering options (pagination, search, sorting)
|
|
35
|
+
export interface ResourceFilters {
|
|
36
|
+
search?: string;
|
|
37
|
+
tagId?: string;
|
|
38
|
+
status?: 'active' | 'inactive' | 'draft';
|
|
39
|
+
sortBy?: 'latest' | 'oldest' | 'title';
|
|
40
|
+
limit?: number;
|
|
41
|
+
page?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Resource with related tag data
|
|
45
|
+
export interface ResourceWithTag extends Resource {
|
|
46
|
+
tag?: Tag;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Create resource DTO
|
|
50
|
+
export interface CreateResourceData {
|
|
51
|
+
title: string;
|
|
52
|
+
content: string;
|
|
53
|
+
tagId: string;
|
|
54
|
+
status?: 'active' | 'inactive' | 'draft';
|
|
55
|
+
metadata?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Update resource DTO
|
|
59
|
+
export interface UpdateResourceData extends Partial<CreateResourceData> {
|
|
60
|
+
id: string;
|
|
61
|
+
}
|
|
@@ -36,85 +36,5 @@
|
|
|
36
36
|
"createdAt": "2024-02-01T08:00:00.000Z",
|
|
37
37
|
"updatedAt": "2024-02-01T08:00:00.000Z"
|
|
38
38
|
}
|
|
39
|
-
],
|
|
40
|
-
"categories": [
|
|
41
|
-
{
|
|
42
|
-
"id": "1",
|
|
43
|
-
"name": "Electronics",
|
|
44
|
-
"slug": "electronics",
|
|
45
|
-
"description": "Electronic devices and accessories",
|
|
46
|
-
"icon": "📱",
|
|
47
|
-
"color": "#3b82f6"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"id": "2",
|
|
51
|
-
"name": "Food & Beverage",
|
|
52
|
-
"slug": "food-beverage",
|
|
53
|
-
"description": "Food items and beverages",
|
|
54
|
-
"icon": "🍔",
|
|
55
|
-
"color": "#ef4444"
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"id": "3",
|
|
59
|
-
"name": "Clothing",
|
|
60
|
-
"slug": "clothing",
|
|
61
|
-
"description": "Clothing and fashion items",
|
|
62
|
-
"icon": "👕",
|
|
63
|
-
"color": "#8b5cf6"
|
|
64
|
-
}
|
|
65
|
-
],
|
|
66
|
-
"products": [
|
|
67
|
-
{
|
|
68
|
-
"id": "1",
|
|
69
|
-
"name": "Smartphone",
|
|
70
|
-
"categoryId": "1",
|
|
71
|
-
"unit": "pcs",
|
|
72
|
-
"stockSystem": 50,
|
|
73
|
-
"stockPhysical": 48,
|
|
74
|
-
"minStock": 10,
|
|
75
|
-
"price": 5000000,
|
|
76
|
-
"description": "Latest smartphone with advanced features",
|
|
77
|
-
"createdAt": "2024-01-10T00:00:00.000Z",
|
|
78
|
-
"updatedAt": "2024-01-10T00:00:00.000Z"
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"id": "2",
|
|
82
|
-
"name": "Wireless Earbuds",
|
|
83
|
-
"categoryId": "1",
|
|
84
|
-
"unit": "pcs",
|
|
85
|
-
"stockSystem": 8,
|
|
86
|
-
"stockPhysical": 8,
|
|
87
|
-
"minStock": 15,
|
|
88
|
-
"price": 500000,
|
|
89
|
-
"description": "Bluetooth earbuds with noise cancellation (Low Stock)",
|
|
90
|
-
"createdAt": "2024-01-15T00:00:00.000Z",
|
|
91
|
-
"updatedAt": "2024-01-15T00:00:00.000Z"
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
"id": "3",
|
|
95
|
-
"name": "Mineral Water",
|
|
96
|
-
"categoryId": "2",
|
|
97
|
-
"unit": "bottle",
|
|
98
|
-
"stockSystem": 200,
|
|
99
|
-
"stockPhysical": 195,
|
|
100
|
-
"minStock": 50,
|
|
101
|
-
"price": 5000,
|
|
102
|
-
"description": "Pure mineral water 600ml",
|
|
103
|
-
"createdAt": "2024-01-20T00:00:00.000Z",
|
|
104
|
-
"updatedAt": "2024-01-20T00:00:00.000Z"
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
"id": "4",
|
|
108
|
-
"name": "T-Shirt",
|
|
109
|
-
"categoryId": "3",
|
|
110
|
-
"unit": "pcs",
|
|
111
|
-
"stockSystem": 100,
|
|
112
|
-
"stockPhysical": 98,
|
|
113
|
-
"minStock": 20,
|
|
114
|
-
"price": 150000,
|
|
115
|
-
"description": "Cotton t-shirt, various colors",
|
|
116
|
-
"createdAt": "2024-01-25T00:00:00.000Z",
|
|
117
|
-
"updatedAt": "2024-01-25T00:00:00.000Z"
|
|
118
|
-
}
|
|
119
39
|
]
|
|
120
40
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import * as authHandlers from './auth.mock';
|
|
2
2
|
import * as userHandlers from './users.mock';
|
|
3
|
-
import * as productHandlers from './products.mock';
|
|
4
|
-
import * as categoryHandlers from './categories.mock';
|
|
5
3
|
|
|
6
4
|
const delay = (ms = 150) => new Promise(r => setTimeout(r, ms));
|
|
7
5
|
|
|
@@ -15,12 +13,6 @@ export const mockApi = {
|
|
|
15
13
|
if (url.startsWith('/users')) {
|
|
16
14
|
return userHandlers.get(url, config);
|
|
17
15
|
}
|
|
18
|
-
if (url.startsWith('/products')) {
|
|
19
|
-
return productHandlers.get(url, config);
|
|
20
|
-
}
|
|
21
|
-
if (url.startsWith('/categories')) {
|
|
22
|
-
return categoryHandlers.get(url, config);
|
|
23
|
-
}
|
|
24
16
|
|
|
25
17
|
throw new Error(`Unknown GET endpoint: ${url}`);
|
|
26
18
|
},
|
|
@@ -34,12 +26,6 @@ export const mockApi = {
|
|
|
34
26
|
if (url.startsWith('/users')) {
|
|
35
27
|
return userHandlers.post(url, body);
|
|
36
28
|
}
|
|
37
|
-
if (url.startsWith('/products')) {
|
|
38
|
-
return productHandlers.post(url, body);
|
|
39
|
-
}
|
|
40
|
-
if (url.startsWith('/categories')) {
|
|
41
|
-
return categoryHandlers.post(url, body);
|
|
42
|
-
}
|
|
43
29
|
|
|
44
30
|
|
|
45
31
|
throw new Error(`Unknown POST endpoint: ${url}`);
|
|
@@ -51,12 +37,6 @@ export const mockApi = {
|
|
|
51
37
|
if (url.startsWith('/users/')) {
|
|
52
38
|
return userHandlers.put(url, body);
|
|
53
39
|
}
|
|
54
|
-
if (url.startsWith('/products/')) {
|
|
55
|
-
return productHandlers.put(url, body);
|
|
56
|
-
}
|
|
57
|
-
if (url.startsWith('/categories/')) {
|
|
58
|
-
return categoryHandlers.put(url, body);
|
|
59
|
-
}
|
|
60
40
|
|
|
61
41
|
throw new Error(`Unknown PUT endpoint: ${url}`);
|
|
62
42
|
},
|
|
@@ -67,12 +47,6 @@ export const mockApi = {
|
|
|
67
47
|
if (url.startsWith('/users/')) {
|
|
68
48
|
return userHandlers.del(url);
|
|
69
49
|
}
|
|
70
|
-
if (url.startsWith('/products/')) {
|
|
71
|
-
return productHandlers.del(url);
|
|
72
|
-
}
|
|
73
|
-
if (url.startsWith('/categories/')) {
|
|
74
|
-
return categoryHandlers.del(url);
|
|
75
|
-
}
|
|
76
50
|
|
|
77
51
|
throw new Error(`Unknown DELETE endpoint: ${url}`);
|
|
78
52
|
},
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { Category } from '@/types/product.types';
|
|
2
|
-
import * as db from 'server/db.json';
|
|
3
|
-
|
|
4
|
-
let categories: Category[] = [...db.categories];
|
|
5
|
-
|
|
6
|
-
export async function get<T = any>(url: string, config?: any): Promise<T> {
|
|
7
|
-
if (url === '/categories') {
|
|
8
|
-
return categories as unknown as T;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Get single category
|
|
12
|
-
const match = url.match(/\/categories\/(\w+)/);
|
|
13
|
-
if (match) {
|
|
14
|
-
const category = categories.find(c => c.id === match[1]);
|
|
15
|
-
if (!category) throw new Error('Category not found');
|
|
16
|
-
return category as unknown as T;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
throw new Error(`Unknown GET /categories endpoint: ${url}`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function post<T = any>(url: string, body?: Partial<Category>): Promise<T> {
|
|
23
|
-
if (url === '/categories') {
|
|
24
|
-
if (!body) throw new Error('Category data is required');
|
|
25
|
-
|
|
26
|
-
const newCategory: Category = {
|
|
27
|
-
id: String(Date.now()),
|
|
28
|
-
name: body.name || '',
|
|
29
|
-
slug: body.slug || body.name?.toLowerCase().replace(/\s+/g, '-') || '',
|
|
30
|
-
description: body.description || '',
|
|
31
|
-
icon: body.icon || '📦',
|
|
32
|
-
color: body.color || '#6366f1',
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
categories.push(newCategory);
|
|
36
|
-
return newCategory as unknown as T;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
throw new Error(`Unknown POST /categories endpoint: ${url}`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export async function put<T = any>(url: string, body: Partial<Category>): Promise<T> {
|
|
43
|
-
const match = url.match(/\/categories\/(\w+)/);
|
|
44
|
-
if (match) {
|
|
45
|
-
const id = match[1];
|
|
46
|
-
const index = categories.findIndex(c => c.id === id);
|
|
47
|
-
if (index === -1) throw new Error('Category not found');
|
|
48
|
-
|
|
49
|
-
categories[index] = {
|
|
50
|
-
...categories[index],
|
|
51
|
-
...body
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
return categories[index] as unknown as T;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
throw new Error(`Unknown PUT /categories endpoint: ${url}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function del<T = any>(url: string): Promise<T> {
|
|
61
|
-
const match = url.match(/\/categories\/(\w+)/);
|
|
62
|
-
if (match) {
|
|
63
|
-
const id = match[1];
|
|
64
|
-
const initialLength = categories.length;
|
|
65
|
-
categories = categories.filter(c => c.id !== id);
|
|
66
|
-
|
|
67
|
-
if (categories.length === initialLength) {
|
|
68
|
-
throw new Error('Category not found');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return { message: 'Category deleted successfully' } as unknown as T;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
throw new Error(`Unknown DELETE /categories endpoint: ${url}`);
|
|
75
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
// products.mock.ts
|
|
2
|
-
import { CreateProductData, Product, ProductFilters, UpdateProductData } from '@/types/product.types';
|
|
3
|
-
import * as db from 'server/db.json';
|
|
4
|
-
|
|
5
|
-
let products: Product[] = [...db.products];
|
|
6
|
-
|
|
7
|
-
export async function get<T = any>(url: string, config?: { params?: ProductFilters }): Promise<T> {
|
|
8
|
-
if (url === '/products') {
|
|
9
|
-
const { params } = config || {};
|
|
10
|
-
let filtered = [...products];
|
|
11
|
-
|
|
12
|
-
if (params) {
|
|
13
|
-
const { search, categoryId, lowStock, sortBy, limit = 10, page = 1 } = params;
|
|
14
|
-
|
|
15
|
-
// Filter by search
|
|
16
|
-
if (search) {
|
|
17
|
-
const q = search.toLowerCase();
|
|
18
|
-
filtered = filtered.filter(
|
|
19
|
-
p => p.name.toLowerCase().includes(q) ||
|
|
20
|
-
p.description?.toLowerCase().includes(q)
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Filter by category
|
|
25
|
-
if (categoryId) {
|
|
26
|
-
filtered = filtered.filter(p => p.categoryId === categoryId);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Filter by low stock
|
|
30
|
-
if (lowStock) {
|
|
31
|
-
filtered = filtered.filter(p => p.stockSystem <= p.minStock);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Sort
|
|
35
|
-
if (sortBy === 'latest') {
|
|
36
|
-
filtered.sort((a, b) => +new Date(b.createdAt) - +new Date(a.createdAt));
|
|
37
|
-
} else if (sortBy === 'oldest') {
|
|
38
|
-
filtered.sort((a, b) => +new Date(a.createdAt) - +new Date(b.createdAt));
|
|
39
|
-
} else if (sortBy === 'name') {
|
|
40
|
-
filtered.sort((a, b) => a.name.localeCompare(b.name));
|
|
41
|
-
} else if (sortBy === 'price-asc') {
|
|
42
|
-
filtered.sort((a, b) => a.price - b.price);
|
|
43
|
-
} else if (sortBy === 'price-desc') {
|
|
44
|
-
filtered.sort((a, b) => b.price - a.price);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Pagination
|
|
48
|
-
const start = (page - 1) * limit;
|
|
49
|
-
return filtered.slice(start, start + limit) as unknown as T;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return products as unknown as T;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Get single product
|
|
56
|
-
const match = url.match(/\/products\/(\w+)/);
|
|
57
|
-
if (match) {
|
|
58
|
-
const product = products.find(p => p.id === match[1]);
|
|
59
|
-
if (!product) throw new Error('Product not found');
|
|
60
|
-
return product as unknown as T;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
throw new Error(`Unknown GET /products endpoint: ${url}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export async function post<T = any>(url: string, body?: CreateProductData): Promise<T> {
|
|
67
|
-
if (url === '/products') {
|
|
68
|
-
if (!body) throw new Error('Product data is required');
|
|
69
|
-
|
|
70
|
-
const newProduct: Product = {
|
|
71
|
-
id: String(Date.now()),
|
|
72
|
-
name: body.name,
|
|
73
|
-
categoryId: body.categoryId,
|
|
74
|
-
unit: body.unit,
|
|
75
|
-
stockSystem: body.stockSystem,
|
|
76
|
-
stockPhysical: body.stockSystem, // Initial physical stock same as system
|
|
77
|
-
minStock: body.minStock,
|
|
78
|
-
price: body.price,
|
|
79
|
-
description: body.description || '',
|
|
80
|
-
createdAt: new Date().toISOString(),
|
|
81
|
-
updatedAt: new Date().toISOString(),
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
products.push(newProduct);
|
|
85
|
-
return newProduct as unknown as T;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
throw new Error(`Unknown POST /products endpoint: ${url}`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export async function put<T = any>(url: string, body: UpdateProductData): Promise<T> {
|
|
92
|
-
const match = url.match(/\/products\/(\w+)/);
|
|
93
|
-
if (match) {
|
|
94
|
-
const id = match[1];
|
|
95
|
-
const index = products.findIndex(p => p.id === id);
|
|
96
|
-
if (index === -1) throw new Error('Product not found');
|
|
97
|
-
|
|
98
|
-
products[index] = {
|
|
99
|
-
...products[index],
|
|
100
|
-
...body,
|
|
101
|
-
updatedAt: new Date().toISOString()
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
return products[index] as unknown as T;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
throw new Error(`Unknown PUT /products endpoint: ${url}`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export async function del<T = any>(url: string): Promise<T> {
|
|
111
|
-
const match = url.match(/\/products\/(\w+)/);
|
|
112
|
-
if (match) {
|
|
113
|
-
const id = match[1];
|
|
114
|
-
const initialLength = products.length;
|
|
115
|
-
products = products.filter(p => p.id !== id);
|
|
116
|
-
|
|
117
|
-
if (products.length === initialLength) {
|
|
118
|
-
throw new Error('Product not found');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return { message: 'Product deleted successfully' } as unknown as T;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
throw new Error(`Unknown DELETE /products endpoint: ${url}`);
|
|
125
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
export interface Category {
|
|
2
|
-
id: string;
|
|
3
|
-
name: string;
|
|
4
|
-
slug: string;
|
|
5
|
-
description: string;
|
|
6
|
-
icon: string;
|
|
7
|
-
color: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface Product {
|
|
11
|
-
id: string;
|
|
12
|
-
name: string;
|
|
13
|
-
categoryId: string;
|
|
14
|
-
unit: string;
|
|
15
|
-
stockSystem: number;
|
|
16
|
-
stockPhysical: number;
|
|
17
|
-
minStock: number;
|
|
18
|
-
price: number;
|
|
19
|
-
description: string;
|
|
20
|
-
createdAt: string;
|
|
21
|
-
updatedAt: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface ProductFilters {
|
|
25
|
-
search?: string;
|
|
26
|
-
categoryId?: string;
|
|
27
|
-
lowStock?: boolean;
|
|
28
|
-
sortBy?: 'latest' | 'oldest' | 'name' | 'price-asc' | 'price-desc';
|
|
29
|
-
limit?: number;
|
|
30
|
-
page?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface ProductWithCategory extends Product {
|
|
34
|
-
category?: Category;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface CreateProductData {
|
|
38
|
-
name: string;
|
|
39
|
-
categoryId: string;
|
|
40
|
-
unit: string;
|
|
41
|
-
stockSystem: number;
|
|
42
|
-
minStock: number;
|
|
43
|
-
price: number;
|
|
44
|
-
description?: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface UpdateProductData extends Partial<CreateProductData> {
|
|
48
|
-
id: string;
|
|
49
|
-
}
|