claude-code-templates 1.16.0 → 1.17.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 (101) hide show
  1. package/README.md +7 -7
  2. package/bin/create-claude-config.js +17 -8
  3. package/package.json +2 -3
  4. package/src/analytics/core/AgentAnalyzer.js +17 -3
  5. package/src/analytics/core/ProcessDetector.js +23 -7
  6. package/src/analytics/core/StateCalculator.js +102 -33
  7. package/src/analytics/data/DataCache.js +7 -7
  8. package/src/analytics-web/chats_mobile.html +2590 -0
  9. package/src/analytics-web/components/App.js +10 -10
  10. package/src/analytics-web/components/SessionTimer.js +1 -1
  11. package/src/analytics-web/components/Sidebar.js +5 -14
  12. package/src/analytics-web/index.html +932 -78
  13. package/src/analytics.js +263 -5
  14. package/src/chats-mobile.js +682 -0
  15. package/src/claude-api-proxy.js +460 -0
  16. package/src/file-operations.js +239 -36
  17. package/src/health-check.js +310 -0
  18. package/src/index.js +1252 -56
  19. package/src/tracking-service.js +31 -34
  20. package/components/agents/api-security-audit.md +0 -92
  21. package/components/agents/database-optimization.md +0 -94
  22. package/components/agents/react-performance-optimization.md +0 -64
  23. package/components/commands/check-file.md +0 -53
  24. package/components/commands/generate-tests.md +0 -68
  25. package/components/mcps/deepgraph-nextjs.json +0 -12
  26. package/components/mcps/deepgraph-react.json +0 -12
  27. package/components/mcps/deepgraph-typescript.json +0 -12
  28. package/components/mcps/deepgraph-vue.json +0 -12
  29. package/components/mcps/filesystem-access.json +0 -12
  30. package/components/mcps/github-integration.json +0 -11
  31. package/components/mcps/memory-integration.json +0 -8
  32. package/components/mcps/mysql-integration.json +0 -11
  33. package/components/mcps/postgresql-integration.json +0 -11
  34. package/components/mcps/web-fetch.json +0 -8
  35. package/src/analytics-web/components/AgentsPage.js +0 -4761
  36. package/templates/common/.claude/commands/git-workflow.md +0 -239
  37. package/templates/common/.claude/commands/project-setup.md +0 -316
  38. package/templates/common/.mcp.json +0 -41
  39. package/templates/common/CLAUDE.md +0 -109
  40. package/templates/common/README.md +0 -96
  41. package/templates/go/.mcp.json +0 -78
  42. package/templates/go/README.md +0 -25
  43. package/templates/javascript-typescript/.claude/commands/api-endpoint.md +0 -51
  44. package/templates/javascript-typescript/.claude/commands/debug.md +0 -52
  45. package/templates/javascript-typescript/.claude/commands/lint.md +0 -48
  46. package/templates/javascript-typescript/.claude/commands/npm-scripts.md +0 -48
  47. package/templates/javascript-typescript/.claude/commands/refactor.md +0 -55
  48. package/templates/javascript-typescript/.claude/commands/test.md +0 -61
  49. package/templates/javascript-typescript/.claude/commands/typescript-migrate.md +0 -51
  50. package/templates/javascript-typescript/.claude/settings.json +0 -142
  51. package/templates/javascript-typescript/.mcp.json +0 -80
  52. package/templates/javascript-typescript/CLAUDE.md +0 -185
  53. package/templates/javascript-typescript/README.md +0 -259
  54. package/templates/javascript-typescript/examples/angular-app/.claude/commands/components.md +0 -63
  55. package/templates/javascript-typescript/examples/angular-app/.claude/commands/services.md +0 -62
  56. package/templates/javascript-typescript/examples/node-api/.claude/commands/api-endpoint.md +0 -46
  57. package/templates/javascript-typescript/examples/node-api/.claude/commands/database.md +0 -56
  58. package/templates/javascript-typescript/examples/node-api/.claude/commands/middleware.md +0 -61
  59. package/templates/javascript-typescript/examples/node-api/.claude/commands/route.md +0 -57
  60. package/templates/javascript-typescript/examples/node-api/CLAUDE.md +0 -102
  61. package/templates/javascript-typescript/examples/react-app/.claude/commands/component.md +0 -29
  62. package/templates/javascript-typescript/examples/react-app/.claude/commands/hooks.md +0 -44
  63. package/templates/javascript-typescript/examples/react-app/.claude/commands/state-management.md +0 -45
  64. package/templates/javascript-typescript/examples/react-app/CLAUDE.md +0 -81
  65. package/templates/javascript-typescript/examples/react-app/agents/react-performance-optimization.md +0 -530
  66. package/templates/javascript-typescript/examples/react-app/agents/react-state-management.md +0 -295
  67. package/templates/javascript-typescript/examples/vue-app/.claude/commands/components.md +0 -46
  68. package/templates/javascript-typescript/examples/vue-app/.claude/commands/composables.md +0 -51
  69. package/templates/python/.claude/commands/lint.md +0 -111
  70. package/templates/python/.claude/commands/test.md +0 -73
  71. package/templates/python/.claude/settings.json +0 -153
  72. package/templates/python/.mcp.json +0 -78
  73. package/templates/python/CLAUDE.md +0 -276
  74. package/templates/python/examples/django-app/.claude/commands/admin.md +0 -264
  75. package/templates/python/examples/django-app/.claude/commands/django-model.md +0 -124
  76. package/templates/python/examples/django-app/.claude/commands/views.md +0 -222
  77. package/templates/python/examples/django-app/CLAUDE.md +0 -313
  78. package/templates/python/examples/django-app/agents/django-api-security.md +0 -642
  79. package/templates/python/examples/django-app/agents/django-database-optimization.md +0 -752
  80. package/templates/python/examples/fastapi-app/.claude/commands/api-endpoints.md +0 -513
  81. package/templates/python/examples/fastapi-app/.claude/commands/auth.md +0 -775
  82. package/templates/python/examples/fastapi-app/.claude/commands/database.md +0 -657
  83. package/templates/python/examples/fastapi-app/.claude/commands/deployment.md +0 -160
  84. package/templates/python/examples/fastapi-app/.claude/commands/testing.md +0 -927
  85. package/templates/python/examples/fastapi-app/CLAUDE.md +0 -229
  86. package/templates/python/examples/flask-app/.claude/commands/app-factory.md +0 -384
  87. package/templates/python/examples/flask-app/.claude/commands/blueprint.md +0 -243
  88. package/templates/python/examples/flask-app/.claude/commands/database.md +0 -410
  89. package/templates/python/examples/flask-app/.claude/commands/deployment.md +0 -620
  90. package/templates/python/examples/flask-app/.claude/commands/flask-route.md +0 -217
  91. package/templates/python/examples/flask-app/.claude/commands/testing.md +0 -559
  92. package/templates/python/examples/flask-app/CLAUDE.md +0 -391
  93. package/templates/ruby/.claude/commands/model.md +0 -360
  94. package/templates/ruby/.claude/commands/test.md +0 -480
  95. package/templates/ruby/.claude/settings.json +0 -146
  96. package/templates/ruby/.mcp.json +0 -83
  97. package/templates/ruby/CLAUDE.md +0 -284
  98. package/templates/ruby/examples/rails-app/.claude/commands/authentication.md +0 -490
  99. package/templates/ruby/examples/rails-app/CLAUDE.md +0 -376
  100. package/templates/rust/.mcp.json +0 -78
  101. package/templates/rust/README.md +0 -26
@@ -1,657 +0,0 @@
1
- # FastAPI Database Integration
2
-
3
- Complete database setup with SQLAlchemy, Alembic, and async support for FastAPI.
4
-
5
- ## Usage
6
-
7
- ```bash
8
- # Initialize Alembic
9
- alembic init alembic
10
-
11
- # Create migration
12
- alembic revision --autogenerate -m "Initial migration"
13
-
14
- # Apply migrations
15
- alembic upgrade head
16
-
17
- # Downgrade migration
18
- alembic downgrade -1
19
- ```
20
-
21
- ## Database Configuration
22
-
23
- ```python
24
- # app/core/config.py
25
- from pydantic import BaseSettings, PostgresDsn, validator
26
- from typing import Optional, Dict, Any
27
- import os
28
-
29
- class Settings(BaseSettings):
30
- """Application settings."""
31
-
32
- # Database
33
- POSTGRES_SERVER: str = "localhost"
34
- POSTGRES_USER: str = "postgres"
35
- POSTGRES_PASSWORD: str = "password"
36
- POSTGRES_DB: str = "fastapi_app"
37
- POSTGRES_PORT: str = "5432"
38
- DATABASE_URL: Optional[PostgresDsn] = None
39
-
40
- @validator("DATABASE_URL", pre=True)
41
- def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
42
- if isinstance(v, str):
43
- return v
44
- return PostgresDsn.build(
45
- scheme="postgresql+asyncpg",
46
- user=values.get("POSTGRES_USER"),
47
- password=values.get("POSTGRES_PASSWORD"),
48
- host=values.get("POSTGRES_SERVER"),
49
- port=values.get("POSTGRES_PORT"),
50
- path=f"/{values.get('POSTGRES_DB') or ''}",
51
- )
52
-
53
- # Redis
54
- REDIS_URL: str = "redis://localhost:6379/0"
55
-
56
- # Database settings
57
- DATABASE_POOL_SIZE: int = 10
58
- DATABASE_MAX_OVERFLOW: int = 20
59
- DATABASE_POOL_RECYCLE: int = 3600
60
-
61
- class Config:
62
- env_file = ".env"
63
- case_sensitive = True
64
-
65
- settings = Settings()
66
- ```
67
-
68
- ## Database Setup
69
-
70
- ```python
71
- # app/db/database.py
72
- from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
73
- from sqlalchemy.ext.declarative import declarative_base
74
- from sqlalchemy.orm import sessionmaker
75
- from app.core.config import settings
76
-
77
- # Create async engine
78
- engine = create_async_engine(
79
- str(settings.DATABASE_URL),
80
- pool_size=settings.DATABASE_POOL_SIZE,
81
- max_overflow=settings.DATABASE_MAX_OVERFLOW,
82
- pool_recycle=settings.DATABASE_POOL_RECYCLE,
83
- pool_pre_ping=True,
84
- echo=False # Set to True for SQL debugging
85
- )
86
-
87
- # Create async session factory
88
- AsyncSessionLocal = sessionmaker(
89
- engine, class_=AsyncSession, expire_on_commit=False
90
- )
91
-
92
- # Base class for models
93
- Base = declarative_base()
94
-
95
- async def get_db() -> AsyncSession:
96
- """Dependency to get database session."""
97
- async with AsyncSessionLocal() as session:
98
- try:
99
- yield session
100
- finally:
101
- await session.close()
102
-
103
- async def create_tables():
104
- """Create database tables."""
105
- async with engine.begin() as conn:
106
- await conn.run_sync(Base.metadata.create_all)
107
-
108
- async def drop_tables():
109
- """Drop database tables."""
110
- async with engine.begin() as conn:
111
- await conn.run_sync(Base.metadata.drop_all)
112
- ```
113
-
114
- ## Base Model
115
-
116
- ```python
117
- # app/models/base.py
118
- from sqlalchemy import Column, Integer, DateTime, func
119
- from sqlalchemy.ext.declarative import declared_attr
120
- from app.db.database import Base
121
- from datetime import datetime
122
- from typing import Any
123
-
124
- class TimestampMixin:
125
- """Mixin for timestamp fields."""
126
- created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
127
- updated_at = Column(
128
- DateTime(timezone=True),
129
- server_default=func.now(),
130
- onupdate=func.now(),
131
- nullable=False
132
- )
133
-
134
- class BaseModel(Base, TimestampMixin):
135
- """Base model with common functionality."""
136
- __abstract__ = True
137
-
138
- id = Column(Integer, primary_key=True, index=True)
139
-
140
- @declared_attr
141
- def __tablename__(cls) -> str:
142
- return cls.__name__.lower()
143
-
144
- def dict(self, exclude: set = None) -> dict[str, Any]:
145
- """Convert model to dictionary."""
146
- exclude = exclude or set()
147
- return {
148
- column.name: getattr(self, column.name)
149
- for column in self.__table__.columns
150
- if column.name not in exclude
151
- }
152
-
153
- def __repr__(self) -> str:
154
- return f"<{self.__class__.__name__}(id={self.id})>"
155
- ```
156
-
157
- ## Example Models
158
-
159
- ```python
160
- # app/models/user.py
161
- from sqlalchemy import Column, String, Boolean, Text, Index
162
- from sqlalchemy.orm import relationship
163
- from app.models.base import BaseModel
164
- from passlib.context import CryptContext
165
-
166
- pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
167
-
168
- class User(BaseModel):
169
- """User model."""
170
- __tablename__ = "users"
171
-
172
- username = Column(String(50), unique=True, index=True, nullable=False)
173
- email = Column(String(100), unique=True, index=True, nullable=False)
174
- hashed_password = Column(String(255), nullable=False)
175
- first_name = Column(String(50), nullable=False)
176
- last_name = Column(String(50), nullable=False)
177
- is_active = Column(Boolean, default=True, nullable=False)
178
- is_superuser = Column(Boolean, default=False, nullable=False)
179
- bio = Column(Text)
180
-
181
- # Relationships
182
- posts = relationship("Post", back_populates="author", cascade="all, delete-orphan")
183
-
184
- # Indexes
185
- __table_args__ = (
186
- Index('idx_user_email_active', email, is_active),
187
- Index('idx_user_username_active', username, is_active),
188
- )
189
-
190
- def verify_password(self, password: str) -> bool:
191
- """Verify password against hash."""
192
- return pwd_context.verify(password, self.hashed_password)
193
-
194
- @staticmethod
195
- def get_password_hash(password: str) -> str:
196
- """Generate password hash."""
197
- return pwd_context.hash(password)
198
-
199
- def set_password(self, password: str) -> None:
200
- """Set user password."""
201
- self.hashed_password = self.get_password_hash(password)
202
-
203
- @property
204
- def full_name(self) -> str:
205
- """Get user's full name."""
206
- return f"{self.first_name} {self.last_name}"
207
-
208
- def dict(self, exclude: set = None) -> dict:
209
- """Convert to dict excluding sensitive data."""
210
- exclude = exclude or set()
211
- exclude.add('hashed_password')
212
- return super().dict(exclude=exclude)
213
-
214
- # app/models/post.py
215
- from sqlalchemy import Column, String, Text, ForeignKey, Boolean, Index
216
- from sqlalchemy.orm import relationship
217
- from app.models.base import BaseModel
218
-
219
- class Post(BaseModel):
220
- """Blog post model."""
221
- __tablename__ = "posts"
222
-
223
- title = Column(String(200), nullable=False, index=True)
224
- content = Column(Text, nullable=False)
225
- slug = Column(String(200), unique=True, nullable=False, index=True)
226
- is_published = Column(Boolean, default=False, nullable=False)
227
-
228
- # Foreign keys
229
- author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
230
-
231
- # Relationships
232
- author = relationship("User", back_populates="posts")
233
-
234
- # Indexes
235
- __table_args__ = (
236
- Index('idx_post_published_created', is_published, 'created_at'),
237
- Index('idx_post_author_published', author_id, is_published),
238
- )
239
- ```
240
-
241
- ## Repository Pattern
242
-
243
- ```python
244
- # app/repositories/base.py
245
- from typing import Generic, TypeVar, Type, Optional, List, Dict, Any
246
- from sqlalchemy.ext.asyncio import AsyncSession
247
- from sqlalchemy import select, update, delete, func
248
- from sqlalchemy.orm import selectinload
249
- from app.models.base import BaseModel
250
-
251
- ModelType = TypeVar("ModelType", bound=BaseModel)
252
-
253
- class BaseRepository(Generic[ModelType]):
254
- """Base repository with common CRUD operations."""
255
-
256
- def __init__(self, model: Type[ModelType], db: AsyncSession):
257
- self.model = model
258
- self.db = db
259
-
260
- async def get(self, id: int) -> Optional[ModelType]:
261
- """Get model by ID."""
262
- result = await self.db.execute(
263
- select(self.model).where(self.model.id == id)
264
- )
265
- return result.scalar_one_or_none()
266
-
267
- async def get_multi(
268
- self,
269
- skip: int = 0,
270
- limit: int = 100,
271
- filters: Dict[str, Any] = None
272
- ) -> List[ModelType]:
273
- """Get multiple models with pagination."""
274
- query = select(self.model)
275
-
276
- if filters:
277
- for field, value in filters.items():
278
- if hasattr(self.model, field):
279
- query = query.where(getattr(self.model, field) == value)
280
-
281
- query = query.offset(skip).limit(limit)
282
- result = await self.db.execute(query)
283
- return result.scalars().all()
284
-
285
- async def create(self, obj_in: Dict[str, Any]) -> ModelType:
286
- """Create new model."""
287
- db_obj = self.model(**obj_in)
288
- self.db.add(db_obj)
289
- await self.db.commit()
290
- await self.db.refresh(db_obj)
291
- return db_obj
292
-
293
- async def update(
294
- self,
295
- id: int,
296
- obj_in: Dict[str, Any]
297
- ) -> Optional[ModelType]:
298
- """Update model by ID."""
299
- await self.db.execute(
300
- update(self.model)
301
- .where(self.model.id == id)
302
- .values(**obj_in)
303
- )
304
- await self.db.commit()
305
- return await self.get(id)
306
-
307
- async def delete(self, id: int) -> bool:
308
- """Delete model by ID."""
309
- result = await self.db.execute(
310
- delete(self.model).where(self.model.id == id)
311
- )
312
- await self.db.commit()
313
- return result.rowcount > 0
314
-
315
- async def count(self, filters: Dict[str, Any] = None) -> int:
316
- """Count models with optional filters."""
317
- query = select(func.count(self.model.id))
318
-
319
- if filters:
320
- for field, value in filters.items():
321
- if hasattr(self.model, field):
322
- query = query.where(getattr(self.model, field) == value)
323
-
324
- result = await self.db.execute(query)
325
- return result.scalar()
326
-
327
- # app/repositories/user.py
328
- from typing import Optional
329
- from sqlalchemy import select
330
- from app.models.user import User
331
- from app.repositories.base import BaseRepository
332
-
333
- class UserRepository(BaseRepository[User]):
334
- """User repository with custom methods."""
335
-
336
- async def get_by_email(self, email: str) -> Optional[User]:
337
- """Get user by email."""
338
- result = await self.db.execute(
339
- select(User).where(User.email == email)
340
- )
341
- return result.scalar_one_or_none()
342
-
343
- async def get_by_username(self, username: str) -> Optional[User]:
344
- """Get user by username."""
345
- result = await self.db.execute(
346
- select(User).where(User.username == username)
347
- )
348
- return result.scalar_one_or_none()
349
-
350
- async def get_active_users(self, skip: int = 0, limit: int = 100):
351
- """Get active users."""
352
- return await self.get_multi(
353
- skip=skip,
354
- limit=limit,
355
- filters={'is_active': True}
356
- )
357
- ```
358
-
359
- ## Alembic Configuration
360
-
361
- ```python
362
- # alembic/env.py
363
- from logging.config import fileConfig
364
- from sqlalchemy import engine_from_config, pool
365
- from sqlalchemy.ext.asyncio import AsyncEngine
366
- from alembic import context
367
- import asyncio
368
-
369
- # Import your models
370
- from app.models.base import Base
371
- from app.models.user import User
372
- from app.models.post import Post
373
- from app.core.config import settings
374
-
375
- # Alembic Config object
376
- config = context.config
377
-
378
- # Override database URL
379
- config.set_main_option("sqlalchemy.url", str(settings.DATABASE_URL))
380
-
381
- # Interpret the config file for logging
382
- if config.config_file_name is not None:
383
- fileConfig(config.config_file_name)
384
-
385
- # Add your model's MetaData object here
386
- target_metadata = Base.metadata
387
-
388
- def do_run_migrations(connection):
389
- context.configure(
390
- connection=connection,
391
- target_metadata=target_metadata,
392
- compare_type=True,
393
- compare_server_default=True,
394
- )
395
-
396
- with context.begin_transaction():
397
- context.run_migrations()
398
-
399
- async def run_migrations_online():
400
- """Run migrations in 'online' mode."""
401
- configuration = config.get_section(config.config_ini_section)
402
- configuration["sqlalchemy.url"] = str(settings.DATABASE_URL)
403
-
404
- connectable = AsyncEngine(
405
- engine_from_config(
406
- configuration,
407
- prefix="sqlalchemy.",
408
- poolclass=pool.NullPool,
409
- )
410
- )
411
-
412
- async with connectable.connect() as connection:
413
- await connection.run_sync(do_run_migrations)
414
-
415
- await connectable.dispose()
416
-
417
- if context.is_offline_mode():
418
- run_migrations_offline()
419
- else:
420
- asyncio.run(run_migrations_online())
421
- ```
422
-
423
- ## Database Utilities
424
-
425
- ```python
426
- # app/db/utils.py
427
- from sqlalchemy.ext.asyncio import AsyncSession
428
- from sqlalchemy import text
429
- from app.db.database import engine, AsyncSessionLocal
430
- from app.core.config import settings
431
- import asyncio
432
-
433
- async def check_database_connection() -> bool:
434
- """Check if database is accessible."""
435
- try:
436
- async with AsyncSessionLocal() as session:
437
- await session.execute(text("SELECT 1"))
438
- return True
439
- except Exception:
440
- return False
441
-
442
- async def create_database_if_not_exists():
443
- """Create database if it doesn't exist."""
444
- # This is PostgreSQL specific
445
- import asyncpg
446
- from urllib.parse import urlparse
447
-
448
- url = urlparse(str(settings.DATABASE_URL))
449
-
450
- try:
451
- # Connect to postgres database to create our database
452
- conn = await asyncpg.connect(
453
- host=url.hostname,
454
- port=url.port,
455
- user=url.username,
456
- password=url.password,
457
- database='postgres'
458
- )
459
-
460
- # Check if database exists
461
- exists = await conn.fetchval(
462
- "SELECT 1 FROM pg_database WHERE datname = $1",
463
- url.path[1:] # Remove leading slash
464
- )
465
-
466
- if not exists:
467
- await conn.execute(f'CREATE DATABASE "{url.path[1:]}"')
468
- print(f"Database {url.path[1:]} created.")
469
-
470
- await conn.close()
471
-
472
- except Exception as e:
473
- print(f"Error creating database: {e}")
474
-
475
- async def execute_raw_sql(sql: str, params: dict = None) -> list:
476
- """Execute raw SQL query."""
477
- async with AsyncSessionLocal() as session:
478
- result = await session.execute(text(sql), params or {})
479
- return result.fetchall()
480
-
481
- async def get_table_info(table_name: str) -> dict:
482
- """Get information about a table."""
483
- sql = """
484
- SELECT
485
- column_name,
486
- data_type,
487
- is_nullable,
488
- column_default
489
- FROM information_schema.columns
490
- WHERE table_name = :table_name
491
- ORDER BY ordinal_position;
492
- """
493
-
494
- result = await execute_raw_sql(sql, {'table_name': table_name})
495
- return [
496
- {
497
- 'column_name': row[0],
498
- 'data_type': row[1],
499
- 'is_nullable': row[2],
500
- 'column_default': row[3]
501
- }
502
- for row in result
503
- ]
504
- ```
505
-
506
- ## Database Initialization
507
-
508
- ```python
509
- # app/db/init_db.py
510
- from sqlalchemy.ext.asyncio import AsyncSession
511
- from app.db.database import get_db, create_tables
512
- from app.models.user import User
513
- from app.repositories.user import UserRepository
514
- from app.core.config import settings
515
- import asyncio
516
-
517
- async def init_db() -> None:
518
- """Initialize database with tables and default data."""
519
- # Create tables
520
- await create_tables()
521
- print("Database tables created.")
522
-
523
- # Create default superuser
524
- async with AsyncSessionLocal() as session:
525
- user_repo = UserRepository(User, session)
526
-
527
- # Check if superuser exists
528
- existing_user = await user_repo.get_by_email("admin@example.com")
529
-
530
- if not existing_user:
531
- superuser_data = {
532
- "username": "admin",
533
- "email": "admin@example.com",
534
- "first_name": "Admin",
535
- "last_name": "User",
536
- "is_superuser": True,
537
- "is_active": True
538
- }
539
-
540
- superuser = User(**superuser_data)
541
- superuser.set_password("admin123")
542
-
543
- session.add(superuser)
544
- await session.commit()
545
- print("Superuser created.")
546
- else:
547
- print("Superuser already exists.")
548
-
549
- if __name__ == "__main__":
550
- asyncio.run(init_db())
551
- ```
552
-
553
- ## Testing Database
554
-
555
- ```python
556
- # tests/conftest.py
557
- import pytest
558
- import asyncio
559
- from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
560
- from sqlalchemy.orm import sessionmaker
561
- from app.db.database import Base, get_db
562
- from app.main import app
563
- import pytest_asyncio
564
-
565
- # Test database URL
566
- TEST_DATABASE_URL = "sqlite+aiosqlite:///./test.db"
567
-
568
- @pytest.fixture(scope="session")
569
- def event_loop():
570
- """Create event loop for async tests."""
571
- loop = asyncio.get_event_loop_policy().new_event_loop()
572
- yield loop
573
- loop.close()
574
-
575
- @pytest_asyncio.fixture(scope="session")
576
- async def test_engine():
577
- """Create test database engine."""
578
- engine = create_async_engine(TEST_DATABASE_URL, echo=False)
579
-
580
- # Create tables
581
- async with engine.begin() as conn:
582
- await conn.run_sync(Base.metadata.create_all)
583
-
584
- yield engine
585
-
586
- # Drop tables
587
- async with engine.begin() as conn:
588
- await conn.run_sync(Base.metadata.drop_all)
589
-
590
- await engine.dispose()
591
-
592
- @pytest_asyncio.fixture
593
- async def test_session(test_engine):
594
- """Create test database session."""
595
- TestSessionLocal = sessionmaker(
596
- test_engine, class_=AsyncSession, expire_on_commit=False
597
- )
598
-
599
- async with TestSessionLocal() as session:
600
- yield session
601
-
602
- @pytest.fixture
603
- def override_get_db(test_session):
604
- """Override database dependency."""
605
- async def _override_get_db():
606
- yield test_session
607
-
608
- app.dependency_overrides[get_db] = _override_get_db
609
- yield
610
- app.dependency_overrides = {}
611
- ```
612
-
613
- ## Database Health Check
614
-
615
- ```python
616
- # app/api/health.py
617
- from fastapi import APIRouter, Depends, HTTPException
618
- from sqlalchemy.ext.asyncio import AsyncSession
619
- from sqlalchemy import text
620
- from app.db.database import get_db
621
- import time
622
-
623
- router = APIRouter()
624
-
625
- @router.get("/health")
626
- async def health_check(db: AsyncSession = Depends(get_db)):
627
- """Health check endpoint."""
628
- checks = {
629
- "status": "healthy",
630
- "timestamp": time.time(),
631
- "database": await check_database_health(db),
632
- }
633
-
634
- # Determine overall status
635
- if checks["database"]["status"] != "ok":
636
- checks["status"] = "unhealthy"
637
- raise HTTPException(status_code=503, detail=checks)
638
-
639
- return checks
640
-
641
- async def check_database_health(db: AsyncSession) -> dict:
642
- """Check database connection."""
643
- try:
644
- start_time = time.time()
645
- await db.execute(text("SELECT 1"))
646
- response_time = (time.time() - start_time) * 1000 # milliseconds
647
-
648
- return {
649
- "status": "ok",
650
- "response_time_ms": round(response_time, 2)
651
- }
652
- except Exception as e:
653
- return {
654
- "status": "error",
655
- "error": str(e)
656
- }
657
- ```