autoworkflow 3.1.4 → 3.5.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 (123) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +174 -11
  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/pre-edit.sh +129 -0
  12. package/.claude/hooks/session-check.sh +79 -0
  13. package/.claude/settings.json +40 -6
  14. package/.claude/settings.local.json +3 -1
  15. package/.claude/skills/actix.md +337 -0
  16. package/.claude/skills/alembic.md +504 -0
  17. package/.claude/skills/angular.md +237 -0
  18. package/.claude/skills/api-design.md +187 -0
  19. package/.claude/skills/aspnet-core.md +377 -0
  20. package/.claude/skills/astro.md +245 -0
  21. package/.claude/skills/auth-clerk.md +327 -0
  22. package/.claude/skills/auth-firebase.md +367 -0
  23. package/.claude/skills/auth-nextauth.md +359 -0
  24. package/.claude/skills/auth-supabase.md +368 -0
  25. package/.claude/skills/axum.md +386 -0
  26. package/.claude/skills/blazor.md +456 -0
  27. package/.claude/skills/chi.md +348 -0
  28. package/.claude/skills/code-review.md +133 -0
  29. package/.claude/skills/csharp.md +296 -0
  30. package/.claude/skills/css-modules.md +325 -0
  31. package/.claude/skills/cypress.md +343 -0
  32. package/.claude/skills/debugging.md +133 -0
  33. package/.claude/skills/diesel.md +392 -0
  34. package/.claude/skills/django.md +301 -0
  35. package/.claude/skills/docker.md +319 -0
  36. package/.claude/skills/doctrine.md +473 -0
  37. package/.claude/skills/documentation.md +182 -0
  38. package/.claude/skills/dotnet.md +409 -0
  39. package/.claude/skills/drizzle.md +293 -0
  40. package/.claude/skills/echo.md +321 -0
  41. package/.claude/skills/eloquent.md +256 -0
  42. package/.claude/skills/emotion.md +426 -0
  43. package/.claude/skills/entity-framework.md +370 -0
  44. package/.claude/skills/express.md +316 -0
  45. package/.claude/skills/fastapi.md +329 -0
  46. package/.claude/skills/fastify.md +299 -0
  47. package/.claude/skills/fiber.md +315 -0
  48. package/.claude/skills/flask.md +322 -0
  49. package/.claude/skills/gin.md +342 -0
  50. package/.claude/skills/git.md +116 -0
  51. package/.claude/skills/github-actions.md +353 -0
  52. package/.claude/skills/go.md +377 -0
  53. package/.claude/skills/gorm.md +409 -0
  54. package/.claude/skills/graphql.md +478 -0
  55. package/.claude/skills/hibernate.md +379 -0
  56. package/.claude/skills/hono.md +306 -0
  57. package/.claude/skills/java.md +400 -0
  58. package/.claude/skills/jest.md +313 -0
  59. package/.claude/skills/jpa.md +282 -0
  60. package/.claude/skills/kotlin.md +347 -0
  61. package/.claude/skills/kubernetes.md +363 -0
  62. package/.claude/skills/laravel.md +414 -0
  63. package/.claude/skills/mcp-browser.md +320 -0
  64. package/.claude/skills/mcp-database.md +219 -0
  65. package/.claude/skills/mcp-fetch.md +241 -0
  66. package/.claude/skills/mcp-filesystem.md +204 -0
  67. package/.claude/skills/mcp-github.md +217 -0
  68. package/.claude/skills/mcp-memory.md +240 -0
  69. package/.claude/skills/mcp-search.md +218 -0
  70. package/.claude/skills/mcp-slack.md +262 -0
  71. package/.claude/skills/micronaut.md +388 -0
  72. package/.claude/skills/mongodb.md +319 -0
  73. package/.claude/skills/mongoose.md +355 -0
  74. package/.claude/skills/mysql.md +281 -0
  75. package/.claude/skills/nestjs.md +335 -0
  76. package/.claude/skills/nextjs-app-router.md +260 -0
  77. package/.claude/skills/nextjs-pages.md +172 -0
  78. package/.claude/skills/nuxt.md +202 -0
  79. package/.claude/skills/openapi.md +489 -0
  80. package/.claude/skills/performance.md +199 -0
  81. package/.claude/skills/php.md +398 -0
  82. package/.claude/skills/playwright.md +371 -0
  83. package/.claude/skills/postgresql.md +257 -0
  84. package/.claude/skills/prisma.md +293 -0
  85. package/.claude/skills/pydantic.md +304 -0
  86. package/.claude/skills/pytest.md +313 -0
  87. package/.claude/skills/python.md +272 -0
  88. package/.claude/skills/quarkus.md +377 -0
  89. package/.claude/skills/react.md +230 -0
  90. package/.claude/skills/redis.md +391 -0
  91. package/.claude/skills/refactoring.md +143 -0
  92. package/.claude/skills/remix.md +246 -0
  93. package/.claude/skills/rest-api.md +490 -0
  94. package/.claude/skills/rocket.md +366 -0
  95. package/.claude/skills/rust.md +341 -0
  96. package/.claude/skills/sass.md +380 -0
  97. package/.claude/skills/sea-orm.md +382 -0
  98. package/.claude/skills/security.md +167 -0
  99. package/.claude/skills/sequelize.md +395 -0
  100. package/.claude/skills/spring-boot.md +416 -0
  101. package/.claude/skills/sqlalchemy.md +269 -0
  102. package/.claude/skills/sqlx-rust.md +408 -0
  103. package/.claude/skills/state-jotai.md +346 -0
  104. package/.claude/skills/state-mobx.md +353 -0
  105. package/.claude/skills/state-pinia.md +431 -0
  106. package/.claude/skills/state-redux.md +337 -0
  107. package/.claude/skills/state-tanstack-query.md +434 -0
  108. package/.claude/skills/state-zustand.md +340 -0
  109. package/.claude/skills/styled-components.md +403 -0
  110. package/.claude/skills/svelte.md +238 -0
  111. package/.claude/skills/sveltekit.md +207 -0
  112. package/.claude/skills/symfony.md +437 -0
  113. package/.claude/skills/tailwind.md +279 -0
  114. package/.claude/skills/terraform.md +394 -0
  115. package/.claude/skills/testing-library.md +371 -0
  116. package/.claude/skills/trpc.md +426 -0
  117. package/.claude/skills/typeorm.md +368 -0
  118. package/.claude/skills/vitest.md +330 -0
  119. package/.claude/skills/vue.md +202 -0
  120. package/.claude/skills/warp.md +365 -0
  121. package/README.md +135 -52
  122. package/package.json +1 -1
  123. package/system/triggers.md +152 -11
@@ -0,0 +1,315 @@
1
+ # Fiber Framework Skill
2
+
3
+ ## Application Setup
4
+ \`\`\`go
5
+ package main
6
+
7
+ import (
8
+ "log"
9
+ "os"
10
+ "time"
11
+
12
+ "github.com/gofiber/fiber/v2"
13
+ "github.com/gofiber/fiber/v2/middleware/cors"
14
+ "github.com/gofiber/fiber/v2/middleware/limiter"
15
+ "github.com/gofiber/fiber/v2/middleware/logger"
16
+ "github.com/gofiber/fiber/v2/middleware/recover"
17
+ )
18
+
19
+ func main() {
20
+ app := fiber.New(fiber.Config{
21
+ ErrorHandler: customErrorHandler,
22
+ DisableStartupMessage: os.Getenv("ENV") == "production",
23
+ ReadTimeout: 10 * time.Second,
24
+ WriteTimeout: 10 * time.Second,
25
+ })
26
+
27
+ // Global middleware
28
+ app.Use(recover.New())
29
+ app.Use(logger.New())
30
+ app.Use(cors.New(cors.Config{
31
+ AllowOrigins: "*",
32
+ AllowMethods: "GET,POST,PUT,DELETE,OPTIONS",
33
+ AllowHeaders: "Authorization,Content-Type",
34
+ }))
35
+
36
+ // Health check
37
+ app.Get("/health", func(c *fiber.Ctx) error {
38
+ return c.JSON(fiber.Map{"status": "ok"})
39
+ })
40
+
41
+ setupRoutes(app)
42
+
43
+ log.Fatal(app.Listen(":3000"))
44
+ }
45
+ \`\`\`
46
+
47
+ ## Route Groups
48
+ \`\`\`go
49
+ func setupRoutes(app *fiber.App) {
50
+ api := app.Group("/api/v1")
51
+
52
+ // Public routes
53
+ auth := api.Group("/auth")
54
+ auth.Post("/login", loginHandler)
55
+ auth.Post("/register", registerHandler)
56
+
57
+ // Protected routes
58
+ protected := api.Group("", AuthMiddleware())
59
+
60
+ // Users
61
+ users := protected.Group("/users")
62
+ users.Get("/", listUsersHandler)
63
+ users.Get("/:id", getUserHandler)
64
+ users.Put("/:id", updateUserHandler)
65
+ users.Delete("/:id", deleteUserHandler)
66
+
67
+ // Posts with rate limiting
68
+ posts := protected.Group("/posts", limiter.New(limiter.Config{
69
+ Max: 10,
70
+ Expiration: time.Minute,
71
+ }))
72
+ posts.Get("/", listPostsHandler)
73
+ posts.Post("/", createPostHandler)
74
+ }
75
+ \`\`\`
76
+
77
+ ## Request Parsing and Validation
78
+ \`\`\`go
79
+ type CreateUserRequest struct {
80
+ Email string \`json:"email" validate:"required,email"\`
81
+ Name string \`json:"name" validate:"required,min=2,max=100"\`
82
+ Password string \`json:"password" validate:"required,min=8"\`
83
+ }
84
+
85
+ type QueryParams struct {
86
+ Page int \`query:"page"\`
87
+ PerPage int \`query:"per_page"\`
88
+ Sort string \`query:"sort"\`
89
+ }
90
+
91
+ func createUserHandler(c *fiber.Ctx) error {
92
+ var req CreateUserRequest
93
+ if err := c.BodyParser(&req); err != nil {
94
+ return fiber.NewError(fiber.StatusBadRequest, "invalid request body")
95
+ }
96
+
97
+ // Validate using go-playground/validator
98
+ if err := validate.Struct(&req); err != nil {
99
+ return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
100
+ "error": "validation_error",
101
+ "details": formatValidationErrors(err),
102
+ })
103
+ }
104
+
105
+ user, err := userService.Create(c.Context(), req)
106
+ if err != nil {
107
+ return err // Handled by custom error handler
108
+ }
109
+
110
+ return c.Status(fiber.StatusCreated).JSON(user)
111
+ }
112
+
113
+ func listUsersHandler(c *fiber.Ctx) error {
114
+ var query QueryParams
115
+ if err := c.QueryParser(&query); err != nil {
116
+ return fiber.NewError(fiber.StatusBadRequest, "invalid query parameters")
117
+ }
118
+
119
+ // Defaults
120
+ if query.Page == 0 {
121
+ query.Page = 1
122
+ }
123
+ if query.PerPage == 0 {
124
+ query.PerPage = 20
125
+ }
126
+
127
+ users, total, err := userService.List(c.Context(), query)
128
+ if err != nil {
129
+ return err
130
+ }
131
+
132
+ return c.JSON(fiber.Map{
133
+ "data": users,
134
+ "total": total,
135
+ "page": query.Page,
136
+ "per_page": query.PerPage,
137
+ })
138
+ }
139
+
140
+ func getUserHandler(c *fiber.Ctx) error {
141
+ id := c.Params("id")
142
+
143
+ user, err := userService.GetByID(c.Context(), id)
144
+ if err != nil {
145
+ return err
146
+ }
147
+
148
+ return c.JSON(user)
149
+ }
150
+ \`\`\`
151
+
152
+ ## Custom Middleware
153
+ \`\`\`go
154
+ // Auth Middleware
155
+ func AuthMiddleware() fiber.Handler {
156
+ return func(c *fiber.Ctx) error {
157
+ authHeader := c.Get("Authorization")
158
+ if authHeader == "" {
159
+ return fiber.NewError(fiber.StatusUnauthorized, "missing authorization header")
160
+ }
161
+
162
+ tokenString := strings.TrimPrefix(authHeader, "Bearer ")
163
+ claims, err := validateToken(tokenString)
164
+ if err != nil {
165
+ return fiber.NewError(fiber.StatusUnauthorized, "invalid token")
166
+ }
167
+
168
+ // Store in Locals
169
+ c.Locals("userID", claims.UserID)
170
+ c.Locals("userRole", claims.Role)
171
+
172
+ return c.Next()
173
+ }
174
+ }
175
+
176
+ // Request ID Middleware
177
+ func RequestIDMiddleware() fiber.Handler {
178
+ return func(c *fiber.Ctx) error {
179
+ requestID := c.Get("X-Request-ID")
180
+ if requestID == "" {
181
+ requestID = uuid.New().String()
182
+ }
183
+ c.Locals("requestID", requestID)
184
+ c.Set("X-Request-ID", requestID)
185
+ return c.Next()
186
+ }
187
+ }
188
+
189
+ // Get user from context
190
+ func getUserFromContext(c *fiber.Ctx) string {
191
+ userID, ok := c.Locals("userID").(string)
192
+ if !ok {
193
+ return ""
194
+ }
195
+ return userID
196
+ }
197
+ \`\`\`
198
+
199
+ ## Error Handling
200
+ \`\`\`go
201
+ // Custom error handler
202
+ func customErrorHandler(c *fiber.Ctx, err error) error {
203
+ // Default to 500
204
+ code := fiber.StatusInternalServerError
205
+ message := "internal server error"
206
+
207
+ // Check for fiber.Error
208
+ var e *fiber.Error
209
+ if errors.As(err, &e) {
210
+ code = e.Code
211
+ message = e.Message
212
+ }
213
+
214
+ // Check for custom errors
215
+ switch {
216
+ case errors.Is(err, ErrNotFound):
217
+ code = fiber.StatusNotFound
218
+ message = "resource not found"
219
+ case errors.Is(err, ErrUnauthorized):
220
+ code = fiber.StatusUnauthorized
221
+ message = "unauthorized"
222
+ case errors.Is(err, ErrForbidden):
223
+ code = fiber.StatusForbidden
224
+ message = "forbidden"
225
+ case errors.Is(err, ErrConflict):
226
+ code = fiber.StatusConflict
227
+ message = "resource already exists"
228
+ }
229
+
230
+ // Log internal errors
231
+ if code == fiber.StatusInternalServerError {
232
+ log.Printf("internal error: %v", err)
233
+ }
234
+
235
+ return c.Status(code).JSON(fiber.Map{
236
+ "error": message,
237
+ })
238
+ }
239
+ \`\`\`
240
+
241
+ ## Testing
242
+ \`\`\`go
243
+ package handler_test
244
+
245
+ import (
246
+ "bytes"
247
+ "encoding/json"
248
+ "io"
249
+ "net/http/httptest"
250
+ "testing"
251
+
252
+ "github.com/gofiber/fiber/v2"
253
+ "github.com/stretchr/testify/assert"
254
+ )
255
+
256
+ func setupTestApp() *fiber.App {
257
+ app := fiber.New(fiber.Config{
258
+ ErrorHandler: customErrorHandler,
259
+ })
260
+ setupRoutes(app)
261
+ return app
262
+ }
263
+
264
+ func TestCreateUser(t *testing.T) {
265
+ app := setupTestApp()
266
+
267
+ tests := []struct {
268
+ name string
269
+ body map[string]interface{}
270
+ wantStatus int
271
+ }{
272
+ {
273
+ name: "valid user",
274
+ body: map[string]interface{}{
275
+ "email": "test@example.com",
276
+ "name": "Test User",
277
+ "password": "password123",
278
+ },
279
+ wantStatus: fiber.StatusCreated,
280
+ },
281
+ {
282
+ name: "missing email",
283
+ body: map[string]interface{}{
284
+ "name": "Test User",
285
+ "password": "password123",
286
+ },
287
+ wantStatus: fiber.StatusBadRequest,
288
+ },
289
+ }
290
+
291
+ for _, tt := range tests {
292
+ t.Run(tt.name, func(t *testing.T) {
293
+ body, _ := json.Marshal(tt.body)
294
+ req := httptest.NewRequest("POST", "/api/v1/users", bytes.NewBuffer(body))
295
+ req.Header.Set("Content-Type", "application/json")
296
+
297
+ resp, _ := app.Test(req)
298
+ assert.Equal(t, tt.wantStatus, resp.StatusCode)
299
+ })
300
+ }
301
+ }
302
+ \`\`\`
303
+
304
+ ## ✅ DO
305
+ - Use \`fiber.Config\` for app configuration
306
+ - Use \`c.Context()\` for passing context to services
307
+ - Return errors from handlers (let error handler process them)
308
+ - Use \`c.Locals()\` for request-scoped values
309
+ - Use built-in middleware (logger, recover, cors, limiter)
310
+
311
+ ## ❌ DON'T
312
+ - Don't ignore errors from \`BodyParser\` or \`QueryParser\`
313
+ - Don't use \`c.Context()\` after handler returns (it's pooled)
314
+ - Don't modify response after calling \`c.Next()\` in middleware
315
+ - Don't store Fiber context in goroutines (copy needed values first)
@@ -0,0 +1,322 @@
1
+ # Flask Skill
2
+
3
+ ## Project Structure
4
+ \`\`\`
5
+ app/
6
+ ├── __init__.py # Application factory
7
+ ├── config.py # Configuration
8
+ ├── extensions.py # Flask extensions
9
+ ├── models/
10
+ │ └── user.py
11
+ ├── routes/
12
+ │ ├── __init__.py
13
+ │ ├── auth.py
14
+ │ └── users.py
15
+ ├── services/
16
+ │ └── user.py
17
+ └── templates/
18
+ └── base.html
19
+ \`\`\`
20
+
21
+ ## Application Factory
22
+ \`\`\`python
23
+ # __init__.py
24
+ from flask import Flask
25
+ from app.extensions import db, migrate, jwt
26
+ from app.config import config
27
+
28
+ def create_app(config_name='development'):
29
+ app = Flask(__name__)
30
+ app.config.from_object(config[config_name])
31
+
32
+ # Initialize extensions
33
+ db.init_app(app)
34
+ migrate.init_app(app, db)
35
+ jwt.init_app(app)
36
+
37
+ # Register blueprints
38
+ from app.routes.auth import auth_bp
39
+ from app.routes.users import users_bp
40
+
41
+ app.register_blueprint(auth_bp, url_prefix='/auth')
42
+ app.register_blueprint(users_bp, url_prefix='/api/users')
43
+
44
+ # Register error handlers
45
+ register_error_handlers(app)
46
+
47
+ return app
48
+
49
+ def register_error_handlers(app):
50
+ @app.errorhandler(404)
51
+ def not_found(error):
52
+ return {'error': 'Not found'}, 404
53
+
54
+ @app.errorhandler(500)
55
+ def internal_error(error):
56
+ db.session.rollback()
57
+ return {'error': 'Internal server error'}, 500
58
+
59
+ # extensions.py
60
+ from flask_sqlalchemy import SQLAlchemy
61
+ from flask_migrate import Migrate
62
+ from flask_jwt_extended import JWTManager
63
+
64
+ db = SQLAlchemy()
65
+ migrate = Migrate()
66
+ jwt = JWTManager()
67
+ \`\`\`
68
+
69
+ ## Configuration
70
+ \`\`\`python
71
+ # config.py
72
+ import os
73
+
74
+ class Config:
75
+ SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret')
76
+ SQLALCHEMY_TRACK_MODIFICATIONS = False
77
+ JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY', 'jwt-secret')
78
+
79
+ class DevelopmentConfig(Config):
80
+ DEBUG = True
81
+ SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'
82
+
83
+ class ProductionConfig(Config):
84
+ SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
85
+
86
+ class TestingConfig(Config):
87
+ TESTING = True
88
+ SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
89
+
90
+ config = {
91
+ 'development': DevelopmentConfig,
92
+ 'production': ProductionConfig,
93
+ 'testing': TestingConfig,
94
+ }
95
+ \`\`\`
96
+
97
+ ## Blueprints & Routes
98
+ \`\`\`python
99
+ # routes/users.py
100
+ from flask import Blueprint, request, jsonify
101
+ from flask_jwt_extended import jwt_required, get_jwt_identity
102
+ from app.models.user import User
103
+ from app.extensions import db
104
+ from app.schemas import UserSchema, UserCreateSchema
105
+
106
+ users_bp = Blueprint('users', __name__)
107
+
108
+ @users_bp.route('/', methods=['GET'])
109
+ def get_users():
110
+ page = request.args.get('page', 1, type=int)
111
+ per_page = request.args.get('per_page', 10, type=int)
112
+
113
+ pagination = User.query.paginate(page=page, per_page=per_page)
114
+
115
+ return jsonify({
116
+ 'users': UserSchema(many=True).dump(pagination.items),
117
+ 'total': pagination.total,
118
+ 'pages': pagination.pages,
119
+ 'page': page,
120
+ })
121
+
122
+ @users_bp.route('/<int:user_id>', methods=['GET'])
123
+ def get_user(user_id):
124
+ user = User.query.get_or_404(user_id)
125
+ return jsonify(UserSchema().dump(user))
126
+
127
+ @users_bp.route('/', methods=['POST'])
128
+ def create_user():
129
+ schema = UserCreateSchema()
130
+ data = schema.load(request.json)
131
+
132
+ user = User(**data)
133
+ db.session.add(user)
134
+ db.session.commit()
135
+
136
+ return jsonify(UserSchema().dump(user)), 201
137
+
138
+ @users_bp.route('/<int:user_id>', methods=['PUT'])
139
+ @jwt_required()
140
+ def update_user(user_id):
141
+ current_user_id = get_jwt_identity()
142
+ if current_user_id != user_id:
143
+ return jsonify({'error': 'Forbidden'}), 403
144
+
145
+ user = User.query.get_or_404(user_id)
146
+ data = request.json
147
+
148
+ for key, value in data.items():
149
+ if hasattr(user, key):
150
+ setattr(user, key, value)
151
+
152
+ db.session.commit()
153
+ return jsonify(UserSchema().dump(user))
154
+
155
+ @users_bp.route('/<int:user_id>', methods=['DELETE'])
156
+ @jwt_required()
157
+ def delete_user(user_id):
158
+ user = User.query.get_or_404(user_id)
159
+ db.session.delete(user)
160
+ db.session.commit()
161
+ return '', 204
162
+ \`\`\`
163
+
164
+ ## Authentication
165
+ \`\`\`python
166
+ # routes/auth.py
167
+ from flask import Blueprint, request, jsonify
168
+ from flask_jwt_extended import (
169
+ create_access_token,
170
+ create_refresh_token,
171
+ jwt_required,
172
+ get_jwt_identity,
173
+ )
174
+ from werkzeug.security import check_password_hash
175
+ from app.models.user import User
176
+
177
+ auth_bp = Blueprint('auth', __name__)
178
+
179
+ @auth_bp.route('/login', methods=['POST'])
180
+ def login():
181
+ email = request.json.get('email')
182
+ password = request.json.get('password')
183
+
184
+ user = User.query.filter_by(email=email).first()
185
+
186
+ if not user or not check_password_hash(user.password_hash, password):
187
+ return jsonify({'error': 'Invalid credentials'}), 401
188
+
189
+ access_token = create_access_token(identity=user.id)
190
+ refresh_token = create_refresh_token(identity=user.id)
191
+
192
+ return jsonify({
193
+ 'access_token': access_token,
194
+ 'refresh_token': refresh_token,
195
+ })
196
+
197
+ @auth_bp.route('/refresh', methods=['POST'])
198
+ @jwt_required(refresh=True)
199
+ def refresh():
200
+ identity = get_jwt_identity()
201
+ access_token = create_access_token(identity=identity)
202
+ return jsonify({'access_token': access_token})
203
+
204
+ @auth_bp.route('/me', methods=['GET'])
205
+ @jwt_required()
206
+ def me():
207
+ user_id = get_jwt_identity()
208
+ user = User.query.get(user_id)
209
+ return jsonify(UserSchema().dump(user))
210
+ \`\`\`
211
+
212
+ ## Error Handling
213
+ \`\`\`python
214
+ from flask import jsonify
215
+ from marshmallow import ValidationError
216
+
217
+ def register_error_handlers(app):
218
+ @app.errorhandler(ValidationError)
219
+ def handle_validation_error(error):
220
+ return jsonify({'error': 'Validation failed', 'details': error.messages}), 400
221
+
222
+ @app.errorhandler(404)
223
+ def not_found(error):
224
+ return jsonify({'error': 'Resource not found'}), 404
225
+
226
+ @app.errorhandler(401)
227
+ def unauthorized(error):
228
+ return jsonify({'error': 'Unauthorized'}), 401
229
+
230
+ @app.errorhandler(Exception)
231
+ def handle_exception(error):
232
+ app.logger.error(f'Unhandled exception: {error}')
233
+ return jsonify({'error': 'Internal server error'}), 500
234
+ \`\`\`
235
+
236
+ ## Request Validation (Marshmallow)
237
+ \`\`\`python
238
+ # schemas.py
239
+ from marshmallow import Schema, fields, validate, post_load
240
+
241
+ class UserSchema(Schema):
242
+ id = fields.Int(dump_only=True)
243
+ email = fields.Email(required=True)
244
+ name = fields.Str(required=True, validate=validate.Length(min=1, max=100))
245
+ created_at = fields.DateTime(dump_only=True)
246
+
247
+ class UserCreateSchema(Schema):
248
+ email = fields.Email(required=True)
249
+ name = fields.Str(required=True)
250
+ password = fields.Str(required=True, load_only=True, validate=validate.Length(min=8))
251
+
252
+ @post_load
253
+ def hash_password(self, data, **kwargs):
254
+ from werkzeug.security import generate_password_hash
255
+ data['password_hash'] = generate_password_hash(data.pop('password'))
256
+ return data
257
+ \`\`\`
258
+
259
+ ## Testing
260
+ \`\`\`python
261
+ # tests/conftest.py
262
+ import pytest
263
+ from app import create_app
264
+ from app.extensions import db
265
+
266
+ @pytest.fixture
267
+ def app():
268
+ app = create_app('testing')
269
+ with app.app_context():
270
+ db.create_all()
271
+ yield app
272
+ db.drop_all()
273
+
274
+ @pytest.fixture
275
+ def client(app):
276
+ return app.test_client()
277
+
278
+ @pytest.fixture
279
+ def auth_headers(client):
280
+ # Create user and login
281
+ client.post('/auth/register', json={
282
+ 'email': 'test@example.com',
283
+ 'password': 'password123',
284
+ 'name': 'Test User',
285
+ })
286
+ response = client.post('/auth/login', json={
287
+ 'email': 'test@example.com',
288
+ 'password': 'password123',
289
+ })
290
+ token = response.json['access_token']
291
+ return {'Authorization': f'Bearer {token}'}
292
+
293
+ # tests/test_users.py
294
+ def test_get_users(client):
295
+ response = client.get('/api/users/')
296
+ assert response.status_code == 200
297
+ assert 'users' in response.json
298
+
299
+ def test_create_user(client):
300
+ response = client.post('/api/users/', json={
301
+ 'email': 'new@example.com',
302
+ 'name': 'New User',
303
+ 'password': 'password123',
304
+ })
305
+ assert response.status_code == 201
306
+ assert response.json['email'] == 'new@example.com'
307
+ \`\`\`
308
+
309
+ ## ❌ DON'T
310
+ - Use app.run() in production (use gunicorn)
311
+ - Store secrets in code
312
+ - Forget to use application factory
313
+ - Skip request validation
314
+
315
+ ## ✅ DO
316
+ - Use blueprints for organization
317
+ - Use Flask-SQLAlchemy for ORM
318
+ - Use Flask-Migrate for migrations
319
+ - Use Flask-JWT-Extended for auth
320
+ - Use Marshmallow for validation
321
+ - Use application factory pattern
322
+ - Handle errors globally