autoworkflow 3.1.5 → 3.6.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.
Files changed (124) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +26 -0
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/post-edit.sh +190 -17
  12. package/.claude/hooks/pre-edit.sh +221 -0
  13. package/.claude/hooks/session-check.sh +90 -0
  14. package/.claude/settings.json +56 -6
  15. package/.claude/settings.local.json +5 -1
  16. package/.claude/skills/actix.md +337 -0
  17. package/.claude/skills/alembic.md +504 -0
  18. package/.claude/skills/angular.md +237 -0
  19. package/.claude/skills/api-design.md +187 -0
  20. package/.claude/skills/aspnet-core.md +377 -0
  21. package/.claude/skills/astro.md +245 -0
  22. package/.claude/skills/auth-clerk.md +327 -0
  23. package/.claude/skills/auth-firebase.md +367 -0
  24. package/.claude/skills/auth-nextauth.md +359 -0
  25. package/.claude/skills/auth-supabase.md +368 -0
  26. package/.claude/skills/axum.md +386 -0
  27. package/.claude/skills/blazor.md +456 -0
  28. package/.claude/skills/chi.md +348 -0
  29. package/.claude/skills/code-review.md +133 -0
  30. package/.claude/skills/csharp.md +296 -0
  31. package/.claude/skills/css-modules.md +325 -0
  32. package/.claude/skills/cypress.md +343 -0
  33. package/.claude/skills/debugging.md +133 -0
  34. package/.claude/skills/diesel.md +392 -0
  35. package/.claude/skills/django.md +301 -0
  36. package/.claude/skills/docker.md +319 -0
  37. package/.claude/skills/doctrine.md +473 -0
  38. package/.claude/skills/documentation.md +182 -0
  39. package/.claude/skills/dotnet.md +409 -0
  40. package/.claude/skills/drizzle.md +293 -0
  41. package/.claude/skills/echo.md +321 -0
  42. package/.claude/skills/eloquent.md +256 -0
  43. package/.claude/skills/emotion.md +426 -0
  44. package/.claude/skills/entity-framework.md +370 -0
  45. package/.claude/skills/express.md +316 -0
  46. package/.claude/skills/fastapi.md +329 -0
  47. package/.claude/skills/fastify.md +299 -0
  48. package/.claude/skills/fiber.md +315 -0
  49. package/.claude/skills/flask.md +322 -0
  50. package/.claude/skills/gin.md +342 -0
  51. package/.claude/skills/git.md +116 -0
  52. package/.claude/skills/github-actions.md +353 -0
  53. package/.claude/skills/go.md +377 -0
  54. package/.claude/skills/gorm.md +409 -0
  55. package/.claude/skills/graphql.md +478 -0
  56. package/.claude/skills/hibernate.md +379 -0
  57. package/.claude/skills/hono.md +306 -0
  58. package/.claude/skills/java.md +400 -0
  59. package/.claude/skills/jest.md +313 -0
  60. package/.claude/skills/jpa.md +282 -0
  61. package/.claude/skills/kotlin.md +347 -0
  62. package/.claude/skills/kubernetes.md +363 -0
  63. package/.claude/skills/laravel.md +414 -0
  64. package/.claude/skills/mcp-browser.md +320 -0
  65. package/.claude/skills/mcp-database.md +219 -0
  66. package/.claude/skills/mcp-fetch.md +241 -0
  67. package/.claude/skills/mcp-filesystem.md +204 -0
  68. package/.claude/skills/mcp-github.md +217 -0
  69. package/.claude/skills/mcp-memory.md +240 -0
  70. package/.claude/skills/mcp-search.md +218 -0
  71. package/.claude/skills/mcp-slack.md +262 -0
  72. package/.claude/skills/micronaut.md +388 -0
  73. package/.claude/skills/mongodb.md +319 -0
  74. package/.claude/skills/mongoose.md +355 -0
  75. package/.claude/skills/mysql.md +281 -0
  76. package/.claude/skills/nestjs.md +335 -0
  77. package/.claude/skills/nextjs-app-router.md +260 -0
  78. package/.claude/skills/nextjs-pages.md +172 -0
  79. package/.claude/skills/nuxt.md +202 -0
  80. package/.claude/skills/openapi.md +489 -0
  81. package/.claude/skills/performance.md +199 -0
  82. package/.claude/skills/php.md +398 -0
  83. package/.claude/skills/playwright.md +371 -0
  84. package/.claude/skills/postgresql.md +257 -0
  85. package/.claude/skills/prisma.md +293 -0
  86. package/.claude/skills/pydantic.md +304 -0
  87. package/.claude/skills/pytest.md +313 -0
  88. package/.claude/skills/python.md +272 -0
  89. package/.claude/skills/quarkus.md +377 -0
  90. package/.claude/skills/react.md +230 -0
  91. package/.claude/skills/redis.md +391 -0
  92. package/.claude/skills/refactoring.md +143 -0
  93. package/.claude/skills/remix.md +246 -0
  94. package/.claude/skills/rest-api.md +490 -0
  95. package/.claude/skills/rocket.md +366 -0
  96. package/.claude/skills/rust.md +341 -0
  97. package/.claude/skills/sass.md +380 -0
  98. package/.claude/skills/sea-orm.md +382 -0
  99. package/.claude/skills/security.md +167 -0
  100. package/.claude/skills/sequelize.md +395 -0
  101. package/.claude/skills/spring-boot.md +416 -0
  102. package/.claude/skills/sqlalchemy.md +269 -0
  103. package/.claude/skills/sqlx-rust.md +408 -0
  104. package/.claude/skills/state-jotai.md +346 -0
  105. package/.claude/skills/state-mobx.md +353 -0
  106. package/.claude/skills/state-pinia.md +431 -0
  107. package/.claude/skills/state-redux.md +337 -0
  108. package/.claude/skills/state-tanstack-query.md +434 -0
  109. package/.claude/skills/state-zustand.md +340 -0
  110. package/.claude/skills/styled-components.md +403 -0
  111. package/.claude/skills/svelte.md +238 -0
  112. package/.claude/skills/sveltekit.md +207 -0
  113. package/.claude/skills/symfony.md +437 -0
  114. package/.claude/skills/tailwind.md +279 -0
  115. package/.claude/skills/terraform.md +394 -0
  116. package/.claude/skills/testing-library.md +371 -0
  117. package/.claude/skills/trpc.md +426 -0
  118. package/.claude/skills/typeorm.md +368 -0
  119. package/.claude/skills/vitest.md +330 -0
  120. package/.claude/skills/vue.md +202 -0
  121. package/.claude/skills/warp.md +365 -0
  122. package/README.md +163 -52
  123. package/package.json +1 -1
  124. package/system/triggers.md +256 -17
@@ -0,0 +1,262 @@
1
+ # MCP Slack Skill
2
+
3
+ ## Server Configuration
4
+ \`\`\`json
5
+ // claude_desktop_config.json or .claude/settings.json
6
+ {
7
+ "mcpServers": {
8
+ "slack": {
9
+ "command": "npx",
10
+ "args": ["-y", "@modelcontextprotocol/server-slack"],
11
+ "env": {
12
+ "SLACK_BOT_TOKEN": "xoxb-xxxxxxxxxxxx",
13
+ "SLACK_TEAM_ID": "T0XXXXXXX"
14
+ }
15
+ }
16
+ }
17
+ }
18
+ \`\`\`
19
+
20
+ ## Authentication Setup
21
+ \`\`\`bash
22
+ # 1. Create a Slack App at https://api.slack.com/apps
23
+ # 2. Add Bot Token Scopes under OAuth & Permissions:
24
+ # - channels:history (read public channel messages)
25
+ # - channels:read (list public channels)
26
+ # - chat:write (post messages)
27
+ # - files:read (access files)
28
+ # - files:write (upload files)
29
+ # - groups:history (read private channel messages)
30
+ # - groups:read (list private channels)
31
+ # - im:history (read DMs)
32
+ # - im:read (list DMs)
33
+ # - users:read (list users)
34
+ # - reactions:read (access reactions)
35
+ # - reactions:write (add reactions)
36
+ # 3. Install app to workspace
37
+ # 4. Copy Bot User OAuth Token (xoxb-...)
38
+ \`\`\`
39
+
40
+ ## Available Tools
41
+
42
+ ### Channel Operations
43
+ \`\`\`
44
+ list_channels
45
+ - types?: 'public_channel' | 'private_channel' | 'mpim' | 'im'
46
+ - limit?: number (default 100)
47
+ - Returns: array of channels with id, name, topic
48
+
49
+ get_channel_info
50
+ - channel: string (channel ID)
51
+ - Returns: channel details
52
+
53
+ get_channel_history
54
+ - channel: string (channel ID)
55
+ - limit?: number (default 100)
56
+ - oldest?: string (timestamp)
57
+ - latest?: string (timestamp)
58
+ - Returns: array of messages
59
+
60
+ join_channel
61
+ - channel: string (channel ID)
62
+ - Returns: success status
63
+ \`\`\`
64
+
65
+ ### Message Operations
66
+ \`\`\`
67
+ post_message
68
+ - channel: string (channel ID or name)
69
+ - text: string (message content)
70
+ - thread_ts?: string (reply to thread)
71
+ - unfurl_links?: boolean
72
+ - unfurl_media?: boolean
73
+ - Returns: message timestamp
74
+
75
+ update_message
76
+ - channel: string
77
+ - ts: string (message timestamp)
78
+ - text: string (new content)
79
+ - Returns: updated message
80
+
81
+ delete_message
82
+ - channel: string
83
+ - ts: string (message timestamp)
84
+ - Returns: success status
85
+
86
+ reply_to_thread
87
+ - channel: string
88
+ - thread_ts: string (parent message timestamp)
89
+ - text: string
90
+ - Returns: message timestamp
91
+
92
+ get_thread_replies
93
+ - channel: string
94
+ - ts: string (thread parent timestamp)
95
+ - Returns: array of reply messages
96
+ \`\`\`
97
+
98
+ ### Reaction Operations
99
+ \`\`\`
100
+ add_reaction
101
+ - channel: string
102
+ - timestamp: string (message ts)
103
+ - name: string (emoji name without colons)
104
+
105
+ remove_reaction
106
+ - channel: string
107
+ - timestamp: string
108
+ - name: string
109
+
110
+ get_reactions
111
+ - channel: string
112
+ - timestamp: string
113
+ - Returns: array of reactions
114
+ \`\`\`
115
+
116
+ ### User Operations
117
+ \`\`\`
118
+ list_users
119
+ - limit?: number
120
+ - Returns: array of users with id, name, real_name, email
121
+
122
+ get_user_info
123
+ - user: string (user ID)
124
+ - Returns: user profile details
125
+
126
+ get_user_by_email
127
+ - email: string
128
+ - Returns: user information
129
+ \`\`\`
130
+
131
+ ### File Operations
132
+ \`\`\`
133
+ upload_file
134
+ - channels: string (comma-separated channel IDs)
135
+ - content?: string (file content)
136
+ - filename: string
137
+ - filetype?: string
138
+ - title?: string
139
+ - initial_comment?: string
140
+ - Returns: file info
141
+
142
+ list_files
143
+ - channel?: string
144
+ - user?: string
145
+ - types?: string (e.g., 'images', 'pdfs')
146
+ - Returns: array of files
147
+ \`\`\`
148
+
149
+ ## Message Formatting
150
+
151
+ ### Block Kit Basics
152
+ \`\`\`json
153
+ {
154
+ "blocks": [
155
+ {
156
+ "type": "header",
157
+ "text": {
158
+ "type": "plain_text",
159
+ "text": "Deployment Complete"
160
+ }
161
+ },
162
+ {
163
+ "type": "section",
164
+ "text": {
165
+ "type": "mrkdwn",
166
+ "text": "*Status:* :white_check_mark: Success\\n*Environment:* Production"
167
+ }
168
+ },
169
+ {
170
+ "type": "divider"
171
+ },
172
+ {
173
+ "type": "section",
174
+ "fields": [
175
+ { "type": "mrkdwn", "text": "*Version:* v2.3.1" },
176
+ { "type": "mrkdwn", "text": "*Duration:* 3m 42s" }
177
+ ]
178
+ },
179
+ {
180
+ "type": "actions",
181
+ "elements": [
182
+ {
183
+ "type": "button",
184
+ "text": { "type": "plain_text", "text": "View Logs" },
185
+ "url": "https://logs.example.com"
186
+ }
187
+ ]
188
+ }
189
+ ]
190
+ }
191
+ \`\`\`
192
+
193
+ ### Markdown Formatting
194
+ \`\`\`
195
+ *bold*
196
+ _italic_
197
+ ~strikethrough~
198
+ \`code\`
199
+ \`\`\`code block\`\`\`
200
+ > quote
201
+ <https://example.com|Link Text>
202
+ <@U12345> (mention user)
203
+ <#C12345> (mention channel)
204
+ <!here> <!channel> <!everyone> (special mentions)
205
+ :emoji_name: (emoji)
206
+ \`\`\`
207
+
208
+ ## Common Workflows
209
+
210
+ ### Post Build Notification
211
+ \`\`\`
212
+ 1. Use list_channels to find #deployments channel
213
+ 2. Use post_message with Block Kit:
214
+ - Header: "Build #123 Complete"
215
+ - Status, branch, commit info
216
+ - Link to build logs
217
+ - Actions: View Diff, Rollback
218
+ \`\`\`
219
+
220
+ ### Daily Standup Reminder
221
+ \`\`\`
222
+ 1. Get channel history from yesterday
223
+ 2. Summarize activity
224
+ 3. Post formatted standup prompt
225
+ 4. Add reaction for acknowledgment tracking
226
+ \`\`\`
227
+
228
+ ### Error Alert Thread
229
+ \`\`\`
230
+ 1. Post main alert message
231
+ 2. Store thread_ts from response
232
+ 3. Add error details as thread replies
233
+ 4. Add reactions for status tracking
234
+ - :eyes: (investigating)
235
+ - :white_check_mark: (resolved)
236
+ \`\`\`
237
+
238
+ ## Channel ID Lookup
239
+ \`\`\`bash
240
+ # Channel IDs start with C (public) or G (private)
241
+ # Find channel ID:
242
+ # 1. Right-click channel > View channel details
243
+ # 2. Copy channel ID from bottom of modal
244
+
245
+ # Or use list_channels tool to search by name
246
+ \`\`\`
247
+
248
+ ## ❌ DON'T
249
+ - Post sensitive data (tokens, passwords) in channels
250
+ - Spam channels with excessive notifications
251
+ - Use @channel/@here for non-urgent messages
252
+ - Store bot tokens in code
253
+ - Ignore rate limits (Tier 1: 1 req/sec)
254
+
255
+ ## ✅ DO
256
+ - Use threads to organize related messages
257
+ - Format messages with Block Kit for readability
258
+ - Use appropriate emoji reactions for status
259
+ - Batch notifications where possible
260
+ - Respect rate limits with backoff
261
+ - Use channel-specific tokens when needed
262
+ - Archive notifications after resolution
@@ -0,0 +1,388 @@
1
+ # Micronaut Skill
2
+
3
+ ## Project Setup
4
+ \`\`\`bash
5
+ # Create new project
6
+ mn create-app com.example.myapp --features=data-jdbc,postgres,security-jwt
7
+
8
+ # Run with hot reload
9
+ ./gradlew run --continuous
10
+
11
+ # Build native image
12
+ ./gradlew nativeCompile
13
+ \`\`\`
14
+
15
+ ## Controller
16
+ \`\`\`java
17
+ @Controller("/api/v1/users")
18
+ public class UserController {
19
+
20
+ private final UserService userService;
21
+
22
+ public UserController(UserService userService) {
23
+ this.userService = userService;
24
+ }
25
+
26
+ @Get
27
+ public Page<UserResponse> listUsers(
28
+ @QueryValue(defaultValue = "0") int page,
29
+ @QueryValue(defaultValue = "20") int size) {
30
+ return userService.findAll(Pageable.from(page, size));
31
+ }
32
+
33
+ @Get("/{id}")
34
+ public HttpResponse<UserResponse> getUser(@PathVariable String id) {
35
+ return userService.findById(id)
36
+ .map(HttpResponse::ok)
37
+ .orElse(HttpResponse.notFound());
38
+ }
39
+
40
+ @Post
41
+ public HttpResponse<UserResponse> createUser(@Valid @Body CreateUserRequest request) {
42
+ UserResponse user = userService.create(request);
43
+ return HttpResponse.created(user)
44
+ .header("Location", "/api/v1/users/" + user.id());
45
+ }
46
+
47
+ @Put("/{id}")
48
+ public HttpResponse<UserResponse> updateUser(
49
+ @PathVariable String id,
50
+ @Valid @Body UpdateUserRequest request) {
51
+ return HttpResponse.ok(userService.update(id, request));
52
+ }
53
+
54
+ @Delete("/{id}")
55
+ public HttpResponse<Void> deleteUser(@PathVariable String id) {
56
+ userService.delete(id);
57
+ return HttpResponse.noContent();
58
+ }
59
+ }
60
+ \`\`\`
61
+
62
+ ## Reactive Controller
63
+ \`\`\`java
64
+ @Controller("/api/v1/users")
65
+ public class UserController {
66
+
67
+ private final UserService userService;
68
+
69
+ public UserController(UserService userService) {
70
+ this.userService = userService;
71
+ }
72
+
73
+ @Get
74
+ public Flux<UserResponse> listUsers() {
75
+ return userService.findAllReactive();
76
+ }
77
+
78
+ @Get("/{id}")
79
+ public Mono<HttpResponse<UserResponse>> getUser(@PathVariable String id) {
80
+ return userService.findByIdReactive(id)
81
+ .map(HttpResponse::ok)
82
+ .defaultIfEmpty(HttpResponse.notFound());
83
+ }
84
+
85
+ @Post
86
+ public Mono<HttpResponse<UserResponse>> createUser(@Valid @Body CreateUserRequest request) {
87
+ return userService.createReactive(request)
88
+ .map(user -> HttpResponse.created(user));
89
+ }
90
+ }
91
+ \`\`\`
92
+
93
+ ## Data Access with Micronaut Data
94
+ \`\`\`java
95
+ // Entity
96
+ @MappedEntity("users")
97
+ public class User {
98
+
99
+ @Id
100
+ @GeneratedValue(GeneratedValue.Type.UUID)
101
+ private String id;
102
+
103
+ @Column("email")
104
+ private String email;
105
+
106
+ @Column("name")
107
+ private String name;
108
+
109
+ @Column("password_hash")
110
+ private String passwordHash;
111
+
112
+ @Column("is_active")
113
+ private boolean isActive = true;
114
+
115
+ @DateCreated
116
+ @Column("created_at")
117
+ private Instant createdAt;
118
+
119
+ @DateUpdated
120
+ @Column("updated_at")
121
+ private Instant updatedAt;
122
+
123
+ // Getters and setters...
124
+ }
125
+
126
+ // Repository
127
+ @JdbcRepository(dialect = Dialect.POSTGRES)
128
+ public interface UserRepository extends PageableRepository<User, String> {
129
+
130
+ Optional<User> findByEmail(String email);
131
+
132
+ List<User> findByIsActiveTrue();
133
+
134
+ @Query("SELECT * FROM users WHERE is_active = true ORDER BY created_at DESC")
135
+ List<User> findActiveUsers();
136
+
137
+ boolean existsByEmail(String email);
138
+
139
+ void updateName(@Id String id, String name);
140
+ }
141
+
142
+ // Reactive Repository
143
+ @R2dbcRepository(dialect = Dialect.POSTGRES)
144
+ public interface UserRepository extends ReactiveStreamsCrudRepository<User, String> {
145
+
146
+ Mono<User> findByEmail(String email);
147
+
148
+ Flux<User> findByIsActiveTrue();
149
+ }
150
+ \`\`\`
151
+
152
+ ## Service Layer
153
+ \`\`\`java
154
+ @Singleton
155
+ public class UserService {
156
+
157
+ private final UserRepository userRepository;
158
+ private final PasswordEncoder passwordEncoder;
159
+
160
+ public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
161
+ this.userRepository = userRepository;
162
+ this.passwordEncoder = passwordEncoder;
163
+ }
164
+
165
+ public Page<UserResponse> findAll(Pageable pageable) {
166
+ return userRepository.findAll(pageable)
167
+ .map(UserResponse::from);
168
+ }
169
+
170
+ public Optional<UserResponse> findById(String id) {
171
+ return userRepository.findById(id)
172
+ .map(UserResponse::from);
173
+ }
174
+
175
+ @Transactional
176
+ public UserResponse create(CreateUserRequest request) {
177
+ if (userRepository.existsByEmail(request.email())) {
178
+ throw new ConflictException("Email already exists");
179
+ }
180
+
181
+ User user = new User();
182
+ user.setEmail(request.email());
183
+ user.setName(request.name());
184
+ user.setPasswordHash(passwordEncoder.encode(request.password()));
185
+
186
+ return UserResponse.from(userRepository.save(user));
187
+ }
188
+
189
+ @Transactional
190
+ public UserResponse update(String id, UpdateUserRequest request) {
191
+ User user = userRepository.findById(id)
192
+ .orElseThrow(() -> new NotFoundException("User not found: " + id));
193
+
194
+ if (request.name() != null) {
195
+ user.setName(request.name());
196
+ }
197
+ if (request.email() != null) {
198
+ user.setEmail(request.email());
199
+ }
200
+
201
+ return UserResponse.from(userRepository.update(user));
202
+ }
203
+
204
+ @Transactional
205
+ public void delete(String id) {
206
+ if (!userRepository.existsById(id)) {
207
+ throw new NotFoundException("User not found: " + id);
208
+ }
209
+ userRepository.deleteById(id);
210
+ }
211
+ }
212
+ \`\`\`
213
+
214
+ ## Exception Handling
215
+ \`\`\`java
216
+ @Produces
217
+ @Singleton
218
+ public class GlobalExceptionHandler {
219
+
220
+ @Error(global = true)
221
+ public HttpResponse<ErrorResponse> handleNotFound(
222
+ HttpRequest<?> request,
223
+ NotFoundException exception) {
224
+ return HttpResponse.notFound()
225
+ .body(new ErrorResponse("NOT_FOUND", exception.getMessage()));
226
+ }
227
+
228
+ @Error(global = true)
229
+ public HttpResponse<ErrorResponse> handleConflict(
230
+ HttpRequest<?> request,
231
+ ConflictException exception) {
232
+ return HttpResponse.status(HttpStatus.CONFLICT)
233
+ .body(new ErrorResponse("CONFLICT", exception.getMessage()));
234
+ }
235
+
236
+ @Error(global = true)
237
+ public HttpResponse<ValidationErrorResponse> handleValidation(
238
+ HttpRequest<?> request,
239
+ ConstraintViolationException exception) {
240
+ Map<String, String> errors = exception.getConstraintViolations().stream()
241
+ .collect(Collectors.toMap(
242
+ v -> v.getPropertyPath().toString(),
243
+ ConstraintViolation::getMessage
244
+ ));
245
+ return HttpResponse.badRequest()
246
+ .body(new ValidationErrorResponse("VALIDATION_ERROR", errors));
247
+ }
248
+ }
249
+
250
+ public record ErrorResponse(String code, String message) {}
251
+ public record ValidationErrorResponse(String code, Map<String, String> errors) {}
252
+ \`\`\`
253
+
254
+ ## Configuration
255
+ \`\`\`yaml
256
+ # application.yml
257
+ micronaut:
258
+ application:
259
+ name: myapp
260
+ server:
261
+ port: 8080
262
+
263
+ datasources:
264
+ default:
265
+ url: \${DATABASE_URL}
266
+ username: \${DATABASE_USER}
267
+ password: \${DATABASE_PASSWORD}
268
+ dialect: POSTGRES
269
+
270
+ jpa:
271
+ default:
272
+ properties:
273
+ hibernate:
274
+ hbm2ddl:
275
+ auto: validate
276
+
277
+ micronaut:
278
+ security:
279
+ authentication: bearer
280
+ token:
281
+ jwt:
282
+ signatures:
283
+ secret:
284
+ generator:
285
+ secret: \${JWT_SECRET}
286
+ \`\`\`
287
+
288
+ ## Security
289
+ \`\`\`java
290
+ @Singleton
291
+ public class AuthenticationProviderUserPassword implements AuthenticationProvider<HttpRequest<?>> {
292
+
293
+ private final UserRepository userRepository;
294
+ private final PasswordEncoder passwordEncoder;
295
+
296
+ public AuthenticationProviderUserPassword(
297
+ UserRepository userRepository,
298
+ PasswordEncoder passwordEncoder) {
299
+ this.userRepository = userRepository;
300
+ this.passwordEncoder = passwordEncoder;
301
+ }
302
+
303
+ @Override
304
+ public Publisher<AuthenticationResponse> authenticate(
305
+ HttpRequest<?> httpRequest,
306
+ AuthenticationRequest<?, ?> authRequest) {
307
+ return Flux.just(authRequest)
308
+ .flatMap(req -> {
309
+ String email = (String) req.getIdentity();
310
+ String password = (String) req.getSecret();
311
+
312
+ return userRepository.findByEmail(email)
313
+ .filter(user -> passwordEncoder.matches(password, user.getPasswordHash()))
314
+ .map(user -> AuthenticationResponse.success(user.getId()))
315
+ .switchIfEmpty(Mono.just(AuthenticationResponse.failure()));
316
+ });
317
+ }
318
+ }
319
+
320
+ // Secured endpoint
321
+ @Controller("/api/v1/admin")
322
+ @Secured(SecurityRule.IS_AUTHENTICATED)
323
+ public class AdminController {
324
+
325
+ @Get("/users")
326
+ @Secured({"ADMIN"})
327
+ public List<UserResponse> listAllUsers() {
328
+ // ...
329
+ }
330
+ }
331
+ \`\`\`
332
+
333
+ ## Testing
334
+ \`\`\`java
335
+ @MicronautTest
336
+ class UserControllerTest {
337
+
338
+ @Inject
339
+ @Client("/")
340
+ HttpClient client;
341
+
342
+ @Inject
343
+ UserRepository userRepository;
344
+
345
+ @Test
346
+ void testGetUser() {
347
+ HttpResponse<UserResponse> response = client.toBlocking()
348
+ .exchange(HttpRequest.GET("/api/v1/users/1"), UserResponse.class);
349
+
350
+ assertEquals(HttpStatus.OK, response.getStatus());
351
+ assertNotNull(response.body());
352
+ }
353
+
354
+ @Test
355
+ void testCreateUser() {
356
+ var request = new CreateUserRequest("test@example.com", "Test", "password123");
357
+
358
+ HttpResponse<UserResponse> response = client.toBlocking()
359
+ .exchange(HttpRequest.POST("/api/v1/users", request), UserResponse.class);
360
+
361
+ assertEquals(HttpStatus.CREATED, response.getStatus());
362
+ assertTrue(response.header("Location").contains("/api/v1/users/"));
363
+ }
364
+
365
+ @Test
366
+ void testGetUserNotFound() {
367
+ HttpClientResponseException exception = assertThrows(
368
+ HttpClientResponseException.class,
369
+ () -> client.toBlocking().exchange(HttpRequest.GET("/api/v1/users/999"))
370
+ );
371
+
372
+ assertEquals(HttpStatus.NOT_FOUND, exception.getStatus());
373
+ }
374
+ }
375
+ \`\`\`
376
+
377
+ ## ✅ DO
378
+ - Use constructor injection (Micronaut creates at compile time)
379
+ - Use \`@Singleton\` for services (default scope)
380
+ - Use Micronaut Data for repositories
381
+ - Use \`@Error\` handlers for exception mapping
382
+ - Return \`HttpResponse<T>\` for explicit status control
383
+
384
+ ## ❌ DON'T
385
+ - Don't use field injection
386
+ - Don't return null - use Optional or throw exceptions
387
+ - Don't forget \`@Transactional\` for write operations
388
+ - Don't use reflection-based libraries (breaks native image)