moicle 1.0.0
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 +201 -0
- package/assets/agents/developers/flutter-mobile-dev.md +69 -0
- package/assets/agents/developers/go-backend-dev.md +57 -0
- package/assets/agents/developers/laravel-backend-dev.md +123 -0
- package/assets/agents/developers/react-frontend-dev.md +69 -0
- package/assets/agents/developers/remix-fullstack-dev.md +69 -0
- package/assets/agents/utilities/api-designer.md +76 -0
- package/assets/agents/utilities/clean-architect.md +83 -0
- package/assets/agents/utilities/code-reviewer.md +76 -0
- package/assets/agents/utilities/db-designer.md +68 -0
- package/assets/agents/utilities/devops.md +71 -0
- package/assets/agents/utilities/docs-writer.md +75 -0
- package/assets/agents/utilities/perf-optimizer.md +87 -0
- package/assets/agents/utilities/refactor.md +173 -0
- package/assets/agents/utilities/security-audit.md +203 -0
- package/assets/agents/utilities/test-writer.md +139 -0
- package/assets/architecture/clean-architecture.md +143 -0
- package/assets/architecture/flutter-mobile.md +304 -0
- package/assets/architecture/go-backend.md +217 -0
- package/assets/architecture/laravel-backend.md +303 -0
- package/assets/architecture/monorepo.md +162 -0
- package/assets/architecture/react-frontend.md +268 -0
- package/assets/architecture/remix-fullstack.md +272 -0
- package/assets/commands/bootstrap.md +98 -0
- package/assets/commands/brainstorm.md +440 -0
- package/assets/skills/feature-workflow/SKILL.md +298 -0
- package/assets/skills/hotfix-workflow/SKILL.md +368 -0
- package/assets/templates/flutter/CLAUDE.md +454 -0
- package/assets/templates/go-gin/CLAUDE.md +244 -0
- package/assets/templates/monorepo/CLAUDE.md +362 -0
- package/assets/templates/react-vite/CLAUDE.md +304 -0
- package/assets/templates/remix/CLAUDE.md +304 -0
- package/bin/cli.js +76 -0
- package/dist/commands/disable.d.ts +3 -0
- package/dist/commands/disable.d.ts.map +1 -0
- package/dist/commands/disable.js +188 -0
- package/dist/commands/disable.js.map +1 -0
- package/dist/commands/enable.d.ts +3 -0
- package/dist/commands/enable.d.ts.map +1 -0
- package/dist/commands/enable.js +191 -0
- package/dist/commands/enable.js.map +1 -0
- package/dist/commands/install.d.ts +3 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +290 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +75 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/postinstall.d.ts +2 -0
- package/dist/commands/postinstall.d.ts.map +1 -0
- package/dist/commands/postinstall.js +25 -0
- package/dist/commands/postinstall.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +118 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/uninstall.d.ts +3 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +178 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/config.d.ts +13 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +95 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/symlink.d.ts +24 -0
- package/dist/utils/symlink.d.ts.map +1 -0
- package/dist/utils/symlink.js +313 -0
- package/dist/utils/symlink.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# Flutter Mobile Structure
|
|
2
|
+
|
|
3
|
+
> Simple Feature-based architecture with Riverpod/Provider
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
{project}/
|
|
9
|
+
├── lib/
|
|
10
|
+
│ ├── core/
|
|
11
|
+
│ │ ├── config/
|
|
12
|
+
│ │ │ ├── app_config.dart
|
|
13
|
+
│ │ │ ├── routes.dart
|
|
14
|
+
│ │ │ └── theme.dart
|
|
15
|
+
│ │ ├── services/
|
|
16
|
+
│ │ │ ├── api_client.dart
|
|
17
|
+
│ │ │ └── storage_service.dart
|
|
18
|
+
│ │ ├── utils/
|
|
19
|
+
│ │ │ ├── extensions.dart
|
|
20
|
+
│ │ │ └── validators.dart
|
|
21
|
+
│ │ └── widgets/ # Core widgets
|
|
22
|
+
│ ├── features/
|
|
23
|
+
│ │ └── {feature}/
|
|
24
|
+
│ │ ├── models/ # Data models
|
|
25
|
+
│ │ ├── providers/ # State management
|
|
26
|
+
│ │ ├── services/ # API calls
|
|
27
|
+
│ │ ├── screens/ # Screen widgets
|
|
28
|
+
│ │ └── widgets/ # Feature widgets
|
|
29
|
+
│ ├── shared/
|
|
30
|
+
│ │ ├── models/ # Shared models
|
|
31
|
+
│ │ └── widgets/ # Shared widgets
|
|
32
|
+
│ ├── app.dart
|
|
33
|
+
│ └── main.dart
|
|
34
|
+
├── test/
|
|
35
|
+
├── assets/
|
|
36
|
+
│ ├── images/
|
|
37
|
+
│ └── fonts/
|
|
38
|
+
├── .claude/
|
|
39
|
+
├── CLAUDE.md
|
|
40
|
+
├── pubspec.yaml
|
|
41
|
+
└── analysis_options.yaml
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Architecture Pattern
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Screen → Provider → Service → API
|
|
48
|
+
↓ ↓
|
|
49
|
+
Widget Model
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Simple flow:**
|
|
53
|
+
1. Screen uses Provider for state
|
|
54
|
+
2. Provider manages state and calls Service
|
|
55
|
+
3. Service makes API calls
|
|
56
|
+
4. Model defines data structure
|
|
57
|
+
|
|
58
|
+
## Feature Structure
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
features/users/
|
|
62
|
+
├── models/
|
|
63
|
+
│ └── user.dart # User model
|
|
64
|
+
├── providers/
|
|
65
|
+
│ └── user_provider.dart # State management
|
|
66
|
+
├── services/
|
|
67
|
+
│ └── user_service.dart # API calls
|
|
68
|
+
├── screens/
|
|
69
|
+
│ ├── user_list_screen.dart
|
|
70
|
+
│ └── user_detail_screen.dart
|
|
71
|
+
└── widgets/
|
|
72
|
+
├── user_card.dart
|
|
73
|
+
└── user_form.dart
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Key Files
|
|
77
|
+
|
|
78
|
+
### models/user.dart
|
|
79
|
+
```dart
|
|
80
|
+
class User {
|
|
81
|
+
final String id;
|
|
82
|
+
final String name;
|
|
83
|
+
final String email;
|
|
84
|
+
final DateTime createdAt;
|
|
85
|
+
|
|
86
|
+
User({
|
|
87
|
+
required this.id,
|
|
88
|
+
required this.name,
|
|
89
|
+
required this.email,
|
|
90
|
+
required this.createdAt,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
factory User.fromJson(Map<String, dynamic> json) {
|
|
94
|
+
return User(
|
|
95
|
+
id: json['id'],
|
|
96
|
+
name: json['name'],
|
|
97
|
+
email: json['email'],
|
|
98
|
+
createdAt: DateTime.parse(json['created_at']),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Map<String, dynamic> toJson() {
|
|
103
|
+
return {
|
|
104
|
+
'id': id,
|
|
105
|
+
'name': name,
|
|
106
|
+
'email': email,
|
|
107
|
+
'created_at': createdAt.toIso8601String(),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### services/user_service.dart
|
|
114
|
+
```dart
|
|
115
|
+
import '../models/user.dart';
|
|
116
|
+
import '../../core/services/api_client.dart';
|
|
117
|
+
|
|
118
|
+
class UserService {
|
|
119
|
+
final ApiClient _api;
|
|
120
|
+
|
|
121
|
+
UserService(this._api);
|
|
122
|
+
|
|
123
|
+
Future<List<User>> getUsers() async {
|
|
124
|
+
final response = await _api.get('/users');
|
|
125
|
+
return (response['data'] as List)
|
|
126
|
+
.map((json) => User.fromJson(json))
|
|
127
|
+
.toList();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
Future<User> getUserById(String id) async {
|
|
131
|
+
final response = await _api.get('/users/$id');
|
|
132
|
+
return User.fromJson(response['data']);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
Future<User> createUser(Map<String, dynamic> data) async {
|
|
136
|
+
final response = await _api.post('/users', data);
|
|
137
|
+
return User.fromJson(response['data']);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Future<void> deleteUser(String id) async {
|
|
141
|
+
await _api.delete('/users/$id');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### providers/user_provider.dart (Riverpod)
|
|
147
|
+
```dart
|
|
148
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
149
|
+
import '../models/user.dart';
|
|
150
|
+
import '../services/user_service.dart';
|
|
151
|
+
|
|
152
|
+
final userServiceProvider = Provider((ref) => UserService(ref.read(apiClientProvider)));
|
|
153
|
+
|
|
154
|
+
final usersProvider = FutureProvider<List<User>>((ref) async {
|
|
155
|
+
final service = ref.read(userServiceProvider);
|
|
156
|
+
return service.getUsers();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
final userProvider = FutureProvider.family<User, String>((ref, id) async {
|
|
160
|
+
final service = ref.read(userServiceProvider);
|
|
161
|
+
return service.getUserById(id);
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### providers/user_provider.dart (Provider alternative)
|
|
166
|
+
```dart
|
|
167
|
+
import 'package:flutter/material.dart';
|
|
168
|
+
import '../models/user.dart';
|
|
169
|
+
import '../services/user_service.dart';
|
|
170
|
+
|
|
171
|
+
class UserProvider extends ChangeNotifier {
|
|
172
|
+
final UserService _service;
|
|
173
|
+
|
|
174
|
+
List<User> _users = [];
|
|
175
|
+
bool _isLoading = false;
|
|
176
|
+
String? _error;
|
|
177
|
+
|
|
178
|
+
List<User> get users => _users;
|
|
179
|
+
bool get isLoading => _isLoading;
|
|
180
|
+
String? get error => _error;
|
|
181
|
+
|
|
182
|
+
UserProvider(this._service);
|
|
183
|
+
|
|
184
|
+
Future<void> fetchUsers() async {
|
|
185
|
+
_isLoading = true;
|
|
186
|
+
_error = null;
|
|
187
|
+
notifyListeners();
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
_users = await _service.getUsers();
|
|
191
|
+
} catch (e) {
|
|
192
|
+
_error = e.toString();
|
|
193
|
+
} finally {
|
|
194
|
+
_isLoading = false;
|
|
195
|
+
notifyListeners();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### screens/user_list_screen.dart
|
|
202
|
+
```dart
|
|
203
|
+
import 'package:flutter/material.dart';
|
|
204
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
205
|
+
import '../providers/user_provider.dart';
|
|
206
|
+
import '../widgets/user_card.dart';
|
|
207
|
+
|
|
208
|
+
class UserListScreen extends ConsumerWidget {
|
|
209
|
+
const UserListScreen({super.key});
|
|
210
|
+
|
|
211
|
+
@override
|
|
212
|
+
Widget build(BuildContext context, WidgetRef ref) {
|
|
213
|
+
final usersAsync = ref.watch(usersProvider);
|
|
214
|
+
|
|
215
|
+
return Scaffold(
|
|
216
|
+
appBar: AppBar(title: const Text('Users')),
|
|
217
|
+
body: usersAsync.when(
|
|
218
|
+
loading: () => const Center(child: CircularProgressIndicator()),
|
|
219
|
+
error: (error, stack) => Center(child: Text('Error: $error')),
|
|
220
|
+
data: (users) => ListView.builder(
|
|
221
|
+
itemCount: users.length,
|
|
222
|
+
itemBuilder: (context, index) => UserCard(user: users[index]),
|
|
223
|
+
),
|
|
224
|
+
),
|
|
225
|
+
floatingActionButton: FloatingActionButton(
|
|
226
|
+
onPressed: () => Navigator.pushNamed(context, '/users/new'),
|
|
227
|
+
child: const Icon(Icons.add),
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### core/services/api_client.dart
|
|
235
|
+
```dart
|
|
236
|
+
import 'dart:convert';
|
|
237
|
+
import 'package:http/http.dart' as http;
|
|
238
|
+
|
|
239
|
+
class ApiClient {
|
|
240
|
+
final String baseUrl;
|
|
241
|
+
String? _token;
|
|
242
|
+
|
|
243
|
+
ApiClient(this.baseUrl);
|
|
244
|
+
|
|
245
|
+
void setToken(String token) => _token = token;
|
|
246
|
+
|
|
247
|
+
Map<String, String> get _headers => {
|
|
248
|
+
'Content-Type': 'application/json',
|
|
249
|
+
if (_token != null) 'Authorization': 'Bearer $_token',
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
Future<Map<String, dynamic>> get(String path) async {
|
|
253
|
+
final response = await http.get(
|
|
254
|
+
Uri.parse('$baseUrl$path'),
|
|
255
|
+
headers: _headers,
|
|
256
|
+
);
|
|
257
|
+
return jsonDecode(response.body);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
Future<Map<String, dynamic>> post(String path, Map<String, dynamic> data) async {
|
|
261
|
+
final response = await http.post(
|
|
262
|
+
Uri.parse('$baseUrl$path'),
|
|
263
|
+
headers: _headers,
|
|
264
|
+
body: jsonEncode(data),
|
|
265
|
+
);
|
|
266
|
+
return jsonDecode(response.body);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
Future<void> delete(String path) async {
|
|
270
|
+
await http.delete(Uri.parse('$baseUrl$path'), headers: _headers);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Conventions
|
|
276
|
+
|
|
277
|
+
| Item | Convention | Example |
|
|
278
|
+
|------|------------|---------|
|
|
279
|
+
| File | snake_case | `user_service.dart` |
|
|
280
|
+
| Class | PascalCase | `UserService` |
|
|
281
|
+
| Variable | camelCase | `userName` |
|
|
282
|
+
| Provider | xxxProvider | `usersProvider` |
|
|
283
|
+
| Screen | XxxScreen | `UserListScreen` |
|
|
284
|
+
|
|
285
|
+
## State Management Options
|
|
286
|
+
|
|
287
|
+
| Option | Use Case |
|
|
288
|
+
|--------|----------|
|
|
289
|
+
| Riverpod | Recommended, compile-safe |
|
|
290
|
+
| Provider | Simple, familiar |
|
|
291
|
+
| BLoC | Complex state, events |
|
|
292
|
+
| GetX | Rapid development |
|
|
293
|
+
|
|
294
|
+
## When to Add More Structure
|
|
295
|
+
|
|
296
|
+
**Current pattern is enough for:**
|
|
297
|
+
- Small to medium apps
|
|
298
|
+
- CRUD operations
|
|
299
|
+
- Standard mobile apps
|
|
300
|
+
|
|
301
|
+
**Consider adding layers when:**
|
|
302
|
+
- Offline-first requirements
|
|
303
|
+
- Complex business rules
|
|
304
|
+
- Multiple data sources
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Go Backend Structure
|
|
2
|
+
|
|
3
|
+
> Simple Handler + Service pattern with GORM
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
{project}/
|
|
9
|
+
├── cmd/
|
|
10
|
+
│ └── api/
|
|
11
|
+
│ └── main.go # Entry point
|
|
12
|
+
├── internal/
|
|
13
|
+
│ ├── config/
|
|
14
|
+
│ │ └── config.go # App configuration
|
|
15
|
+
│ ├── models/ # GORM models
|
|
16
|
+
│ │ ├── user.go
|
|
17
|
+
│ │ └── story.go
|
|
18
|
+
│ ├── handlers/ # HTTP handlers
|
|
19
|
+
│ │ ├── user_handler.go
|
|
20
|
+
│ │ └── story_handler.go
|
|
21
|
+
│ ├── services/ # Business logic
|
|
22
|
+
│ │ ├── user_service.go
|
|
23
|
+
│ │ └── story_service.go
|
|
24
|
+
│ ├── middleware/ # HTTP middleware
|
|
25
|
+
│ │ ├── auth.go
|
|
26
|
+
│ │ └── cors.go
|
|
27
|
+
│ └── routes/ # Route definitions
|
|
28
|
+
│ └── routes.go
|
|
29
|
+
├── pkg/ # Shared packages
|
|
30
|
+
│ ├── database/ # DB connection
|
|
31
|
+
│ ├── logger/
|
|
32
|
+
│ └── validator/
|
|
33
|
+
├── migrations/ # SQL migrations
|
|
34
|
+
├── .claude/
|
|
35
|
+
├── CLAUDE.md
|
|
36
|
+
├── go.mod
|
|
37
|
+
├── Makefile
|
|
38
|
+
├── Dockerfile
|
|
39
|
+
└── README.md
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Architecture Pattern
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Handler → Service → Model (GORM)
|
|
46
|
+
↓ ↓
|
|
47
|
+
Request Database
|
|
48
|
+
Binding Query
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Simple flow:**
|
|
52
|
+
1. Handler receives HTTP request
|
|
53
|
+
2. Handler calls Service
|
|
54
|
+
3. Service contains business logic
|
|
55
|
+
4. Service uses GORM Models directly
|
|
56
|
+
5. Handler returns response
|
|
57
|
+
|
|
58
|
+
## Key Files
|
|
59
|
+
|
|
60
|
+
### internal/models/user.go
|
|
61
|
+
```go
|
|
62
|
+
package models
|
|
63
|
+
|
|
64
|
+
import "gorm.io/gorm"
|
|
65
|
+
|
|
66
|
+
type User struct {
|
|
67
|
+
gorm.Model
|
|
68
|
+
Name string `json:"name"`
|
|
69
|
+
Email string `json:"email" gorm:"uniqueIndex"`
|
|
70
|
+
Password string `json:"-"`
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### internal/services/user_service.go
|
|
75
|
+
```go
|
|
76
|
+
package services
|
|
77
|
+
|
|
78
|
+
import (
|
|
79
|
+
"myapp/internal/models"
|
|
80
|
+
"gorm.io/gorm"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
type UserService struct {
|
|
84
|
+
db *gorm.DB
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func NewUserService(db *gorm.DB) *UserService {
|
|
88
|
+
return &UserService{db: db}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func (s *UserService) GetByID(id uint) (*models.User, error) {
|
|
92
|
+
var user models.User
|
|
93
|
+
if err := s.db.First(&user, id).Error; err != nil {
|
|
94
|
+
return nil, err
|
|
95
|
+
}
|
|
96
|
+
return &user, nil
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func (s *UserService) GetAll() ([]models.User, error) {
|
|
100
|
+
var users []models.User
|
|
101
|
+
if err := s.db.Find(&users).Error; err != nil {
|
|
102
|
+
return nil, err
|
|
103
|
+
}
|
|
104
|
+
return users, nil
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func (s *UserService) Create(user *models.User) error {
|
|
108
|
+
return s.db.Create(user).Error
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### internal/handlers/user_handler.go
|
|
113
|
+
```go
|
|
114
|
+
package handlers
|
|
115
|
+
|
|
116
|
+
import (
|
|
117
|
+
"net/http"
|
|
118
|
+
"myapp/internal/services"
|
|
119
|
+
"github.com/gin-gonic/gin"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
type UserHandler struct {
|
|
123
|
+
userService *services.UserService
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func NewUserHandler(userService *services.UserService) *UserHandler {
|
|
127
|
+
return &UserHandler{userService: userService}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func (h *UserHandler) GetAll(c *gin.Context) {
|
|
131
|
+
users, err := h.userService.GetAll()
|
|
132
|
+
if err != nil {
|
|
133
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
c.JSON(http.StatusOK, gin.H{"data": users})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
func (h *UserHandler) GetByID(c *gin.Context) {
|
|
140
|
+
id := c.Param("id")
|
|
141
|
+
user, err := h.userService.GetByID(id)
|
|
142
|
+
if err != nil {
|
|
143
|
+
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
c.JSON(http.StatusOK, gin.H{"data": user})
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### cmd/api/main.go
|
|
151
|
+
```go
|
|
152
|
+
package main
|
|
153
|
+
|
|
154
|
+
import (
|
|
155
|
+
"myapp/internal/config"
|
|
156
|
+
"myapp/internal/handlers"
|
|
157
|
+
"myapp/internal/services"
|
|
158
|
+
"myapp/pkg/database"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
func main() {
|
|
162
|
+
cfg := config.Load()
|
|
163
|
+
db := database.Connect(cfg.DB)
|
|
164
|
+
|
|
165
|
+
// Wire dependencies
|
|
166
|
+
userService := services.NewUserService(db)
|
|
167
|
+
userHandler := handlers.NewUserHandler(userService)
|
|
168
|
+
|
|
169
|
+
// Setup routes
|
|
170
|
+
r := gin.Default()
|
|
171
|
+
r.GET("/users", userHandler.GetAll)
|
|
172
|
+
r.GET("/users/:id", userHandler.GetByID)
|
|
173
|
+
|
|
174
|
+
r.Run(cfg.Port)
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Conventions
|
|
179
|
+
|
|
180
|
+
| Item | Convention | Example |
|
|
181
|
+
|------|------------|---------|
|
|
182
|
+
| Package | lowercase, short | `user`, `auth` |
|
|
183
|
+
| Struct | PascalCase | `UserService` |
|
|
184
|
+
| File | snake_case | `user_handler.go` |
|
|
185
|
+
| Handler func | PascalCase | `GetByID`, `Create` |
|
|
186
|
+
| Service func | PascalCase | `GetAll`, `FindByEmail` |
|
|
187
|
+
|
|
188
|
+
## Makefile
|
|
189
|
+
|
|
190
|
+
```makefile
|
|
191
|
+
.PHONY: run build test
|
|
192
|
+
|
|
193
|
+
run:
|
|
194
|
+
go run cmd/api/main.go
|
|
195
|
+
|
|
196
|
+
build:
|
|
197
|
+
go build -o bin/api cmd/api/main.go
|
|
198
|
+
|
|
199
|
+
test:
|
|
200
|
+
go test -v ./...
|
|
201
|
+
|
|
202
|
+
migrate:
|
|
203
|
+
go run cmd/migrate/main.go
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## When to Add More Structure
|
|
207
|
+
|
|
208
|
+
**Current pattern is enough for:**
|
|
209
|
+
- Small to medium APIs
|
|
210
|
+
- CRUD operations
|
|
211
|
+
- Simple business logic
|
|
212
|
+
|
|
213
|
+
**Consider adding layers when:**
|
|
214
|
+
- Multiple data sources (DB + external APIs)
|
|
215
|
+
- Complex business rules
|
|
216
|
+
- Need to swap database
|
|
217
|
+
- Large team with clear boundaries
|