Initial commit
This commit is contained in:
421
commands/auth-setup.md
Normal file
421
commands/auth-setup.md
Normal file
@@ -0,0 +1,421 @@
|
||||
---
|
||||
description: Generate authentication boilerplate with JWT, OAuth, and session support
|
||||
shortcut: as
|
||||
category: backend
|
||||
difficulty: intermediate
|
||||
estimated_time: 5-10 minutes
|
||||
---
|
||||
|
||||
# Auth Setup
|
||||
|
||||
Generates complete authentication boilerplate including JWT, OAuth (Google/GitHub), session management, and password reset flows.
|
||||
|
||||
## What This Command Does
|
||||
|
||||
**Generated Auth System:**
|
||||
- JWT authentication with refresh tokens
|
||||
- OAuth2 (Google, GitHub, Facebook)
|
||||
- Password hashing (bcrypt)
|
||||
- Email verification
|
||||
- Password reset flow
|
||||
- Session management
|
||||
- Rate limiting on auth endpoints
|
||||
- Authentication middleware
|
||||
|
||||
**Output:** Complete authentication system ready for production
|
||||
|
||||
**Time:** 5-10 minutes
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Generate full auth system
|
||||
/auth-setup jwt
|
||||
|
||||
# Shortcut
|
||||
/as oauth --providers google,github
|
||||
|
||||
# With specific features
|
||||
/as jwt --features email-verification,password-reset,2fa
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Output
|
||||
|
||||
### **JWT Authentication**
|
||||
|
||||
**auth.service.ts:**
|
||||
```typescript
|
||||
import bcrypt from 'bcrypt'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { User } from './models/User'
|
||||
|
||||
export class AuthService {
|
||||
async register(email: string, password: string, name: string) {
|
||||
// Check if user exists
|
||||
const existing = await User.findOne({ email })
|
||||
if (existing) {
|
||||
throw new Error('Email already registered')
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(password, 12)
|
||||
|
||||
// Create user
|
||||
const user = await User.create({
|
||||
email,
|
||||
password: hashedPassword,
|
||||
name,
|
||||
emailVerified: false
|
||||
})
|
||||
|
||||
// Generate verification token
|
||||
const verificationToken = this.generateToken({ userId: user.id, type: 'verify' }, '24h')
|
||||
|
||||
// Send verification email (implement sendEmail)
|
||||
await this.sendVerificationEmail(email, verificationToken)
|
||||
|
||||
// Generate auth tokens
|
||||
const accessToken = this.generateAccessToken(user)
|
||||
const refreshToken = this.generateRefreshToken(user)
|
||||
|
||||
return {
|
||||
user: { id: user.id, email: user.email, name: user.name },
|
||||
accessToken,
|
||||
refreshToken
|
||||
}
|
||||
}
|
||||
|
||||
async login(email: string, password: string) {
|
||||
const user = await User.findOne({ email })
|
||||
if (!user) {
|
||||
throw new Error('Invalid credentials')
|
||||
}
|
||||
|
||||
const validPassword = await bcrypt.compare(password, user.password)
|
||||
if (!validPassword) {
|
||||
throw new Error('Invalid credentials')
|
||||
}
|
||||
|
||||
if (!user.emailVerified) {
|
||||
throw new Error('Please verify your email')
|
||||
}
|
||||
|
||||
const accessToken = this.generateAccessToken(user)
|
||||
const refreshToken = this.generateRefreshToken(user)
|
||||
|
||||
return {
|
||||
user: { id: user.id, email: user.email, name: user.name },
|
||||
accessToken,
|
||||
refreshToken
|
||||
}
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string) {
|
||||
try {
|
||||
const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!) as any
|
||||
|
||||
const user = await User.findById(decoded.userId)
|
||||
if (!user) {
|
||||
throw new Error('User not found')
|
||||
}
|
||||
|
||||
const accessToken = this.generateAccessToken(user)
|
||||
return { accessToken }
|
||||
} catch (error) {
|
||||
throw new Error('Invalid refresh token')
|
||||
}
|
||||
}
|
||||
|
||||
async verifyEmail(token: string) {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any
|
||||
|
||||
if (decoded.type !== 'verify') {
|
||||
throw new Error('Invalid token type')
|
||||
}
|
||||
|
||||
await User.findByIdAndUpdate(decoded.userId, { emailVerified: true })
|
||||
return { message: 'Email verified successfully' }
|
||||
}
|
||||
|
||||
async requestPasswordReset(email: string) {
|
||||
const user = await User.findOne({ email })
|
||||
if (!user) {
|
||||
// Don't reveal if user exists
|
||||
return { message: 'If email exists, reset link sent' }
|
||||
}
|
||||
|
||||
const resetToken = this.generateToken({ userId: user.id, type: 'reset' }, '1h')
|
||||
await this.sendPasswordResetEmail(email, resetToken)
|
||||
|
||||
return { message: 'If email exists, reset link sent' }
|
||||
}
|
||||
|
||||
async resetPassword(token: string, newPassword: string) {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any
|
||||
|
||||
if (decoded.type !== 'reset') {
|
||||
throw new Error('Invalid token type')
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(newPassword, 12)
|
||||
await User.findByIdAndUpdate(decoded.userId, { password: hashedPassword })
|
||||
|
||||
return { message: 'Password reset successfully' }
|
||||
}
|
||||
|
||||
private generateAccessToken(user: any) {
|
||||
return jwt.sign(
|
||||
{ userId: user.id, email: user.email },
|
||||
process.env.JWT_SECRET!,
|
||||
{ expiresIn: '15m' }
|
||||
)
|
||||
}
|
||||
|
||||
private generateRefreshToken(user: any) {
|
||||
return jwt.sign(
|
||||
{ userId: user.id },
|
||||
process.env.JWT_REFRESH_SECRET!,
|
||||
{ expiresIn: '7d' }
|
||||
)
|
||||
}
|
||||
|
||||
private generateToken(payload: any, expiresIn: string) {
|
||||
return jwt.sign(payload, process.env.JWT_SECRET!, { expiresIn })
|
||||
}
|
||||
|
||||
private async sendVerificationEmail(email: string, token: string) {
|
||||
// Implement with SendGrid, Resend, etc.
|
||||
}
|
||||
|
||||
private async sendPasswordResetEmail(email: string, token: string) {
|
||||
// Implement with SendGrid, Resend, etc.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **OAuth2 Setup (Google)**
|
||||
|
||||
**oauth.controller.ts:**
|
||||
```typescript
|
||||
import { OAuth2Client } from 'google-auth-library'
|
||||
|
||||
const googleClient = new OAuth2Client(
|
||||
process.env.GOOGLE_CLIENT_ID,
|
||||
process.env.GOOGLE_CLIENT_SECRET,
|
||||
process.env.GOOGLE_REDIRECT_URI
|
||||
)
|
||||
|
||||
export class OAuthController {
|
||||
async googleLogin(req: Request, res: Response) {
|
||||
const authUrl = googleClient.generateAuthUrl({
|
||||
access_type: 'offline',
|
||||
scope: ['profile', 'email']
|
||||
})
|
||||
|
||||
res.redirect(authUrl)
|
||||
}
|
||||
|
||||
async googleCallback(req: Request, res: Response) {
|
||||
const { code } = req.query
|
||||
|
||||
const { tokens } = await googleClient.getToken(code as string)
|
||||
googleClient.setCredentials(tokens)
|
||||
|
||||
const ticket = await googleClient.verifyIdToken({
|
||||
idToken: tokens.id_token!,
|
||||
audience: process.env.GOOGLE_CLIENT_ID
|
||||
})
|
||||
|
||||
const payload = ticket.getPayload()
|
||||
if (!payload) {
|
||||
throw new Error('Invalid token')
|
||||
}
|
||||
|
||||
// Find or create user
|
||||
let user = await User.findOne({ email: payload.email })
|
||||
|
||||
if (!user) {
|
||||
user = await User.create({
|
||||
email: payload.email,
|
||||
name: payload.name,
|
||||
avatar: payload.picture,
|
||||
emailVerified: true,
|
||||
provider: 'google',
|
||||
providerId: payload.sub
|
||||
})
|
||||
}
|
||||
|
||||
// Generate tokens
|
||||
const accessToken = generateAccessToken(user)
|
||||
const refreshToken = generateRefreshToken(user)
|
||||
|
||||
// Redirect with tokens
|
||||
res.redirect(`/auth/success?token=${accessToken}&refresh=${refreshToken}`)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Authentication Middleware**
|
||||
|
||||
**auth.middleware.ts:**
|
||||
```typescript
|
||||
import { Request, Response, NextFunction } from 'express'
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: {
|
||||
userId: string
|
||||
email: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function authenticate(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const authHeader = req.headers.authorization
|
||||
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'No token provided' })
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1]
|
||||
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any
|
||||
|
||||
req.user = {
|
||||
userId: decoded.userId,
|
||||
email: decoded.email
|
||||
}
|
||||
|
||||
next()
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.TokenExpiredError) {
|
||||
return res.status(401).json({ error: 'Token expired' })
|
||||
}
|
||||
return res.status(401).json({ error: 'Invalid token' })
|
||||
}
|
||||
}
|
||||
|
||||
export function authorize(...roles: string[]) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ error: 'Not authenticated' })
|
||||
}
|
||||
|
||||
const user = await User.findById(req.user.userId)
|
||||
|
||||
if (!user || !roles.includes(user.role)) {
|
||||
return res.status(403).json({ error: 'Insufficient permissions' })
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Rate Limiting**
|
||||
|
||||
```typescript
|
||||
import rateLimit from 'express-rate-limit'
|
||||
|
||||
export const authLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 5, // 5 requests per window
|
||||
message: 'Too many login attempts, please try again later',
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false
|
||||
})
|
||||
|
||||
// Usage
|
||||
app.post('/api/auth/login', authLimiter, authController.login)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
# JWT
|
||||
JWT_SECRET=your-super-secret-key-min-32-chars
|
||||
JWT_REFRESH_SECRET=your-refresh-secret-key
|
||||
JWT_EXPIRES_IN=15m
|
||||
JWT_REFRESH_EXPIRES_IN=7d
|
||||
|
||||
# OAuth - Google
|
||||
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
||||
GOOGLE_CLIENT_SECRET=your-client-secret
|
||||
GOOGLE_REDIRECT_URI=http://localhost:3000/api/auth/google/callback
|
||||
|
||||
# OAuth - GitHub
|
||||
GITHUB_CLIENT_ID=your-github-client-id
|
||||
GITHUB_CLIENT_SECRET=your-github-client-secret
|
||||
GITHUB_REDIRECT_URI=http://localhost:3000/api/auth/github/callback
|
||||
|
||||
# Email
|
||||
SMTP_HOST=smtp.sendgrid.net
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=apikey
|
||||
SMTP_PASSWORD=your-sendgrid-api-key
|
||||
FROM_EMAIL=[email protected]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Routes
|
||||
|
||||
```typescript
|
||||
// routes/auth.routes.ts
|
||||
import { Router } from 'express'
|
||||
import { AuthController } from '../controllers/auth.controller'
|
||||
import { authenticate } from '../middleware/auth.middleware'
|
||||
import { authLimiter } from '../middleware/rate-limit'
|
||||
|
||||
const router = Router()
|
||||
const authController = new AuthController()
|
||||
|
||||
// Registration & Login
|
||||
router.post('/register', authController.register)
|
||||
router.post('/login', authLimiter, authController.login)
|
||||
router.post('/refresh', authController.refreshToken)
|
||||
router.post('/logout', authenticate, authController.logout)
|
||||
|
||||
// Email Verification
|
||||
router.post('/verify-email', authController.verifyEmail)
|
||||
router.post('/resend-verification', authController.resendVerification)
|
||||
|
||||
// Password Reset
|
||||
router.post('/forgot-password', authLimiter, authController.forgotPassword)
|
||||
router.post('/reset-password', authController.resetPassword)
|
||||
|
||||
// OAuth
|
||||
router.get('/google', authController.googleLogin)
|
||||
router.get('/google/callback', authController.googleCallback)
|
||||
router.get('/github', authController.githubLogin)
|
||||
router.get('/github/callback', authController.githubCallback)
|
||||
|
||||
// Profile
|
||||
router.get('/me', authenticate, authController.getProfile)
|
||||
router.patch('/me', authenticate, authController.updateProfile)
|
||||
router.post('/change-password', authenticate, authController.changePassword)
|
||||
|
||||
export default router
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- `/env-config-setup` - Generate environment config
|
||||
- `/express-api-scaffold` - Generate Express API
|
||||
- `/fastapi-scaffold` - Generate FastAPI
|
||||
|
||||
---
|
||||
|
||||
**Secure authentication. Easy integration. Production-ready.**
|
||||
342
commands/component-generator.md
Normal file
342
commands/component-generator.md
Normal file
@@ -0,0 +1,342 @@
|
||||
---
|
||||
description: Generate React components from descriptions with TypeScript and tests
|
||||
shortcut: cg
|
||||
category: frontend
|
||||
difficulty: beginner
|
||||
estimated_time: 2-5 minutes
|
||||
---
|
||||
|
||||
# Component Generator
|
||||
|
||||
Generates production-ready React components from natural language descriptions, including TypeScript types, styling, and tests.
|
||||
|
||||
## What This Command Does
|
||||
|
||||
**Component Generation:**
|
||||
- Creates React component with TypeScript
|
||||
- Generates prop types and interfaces
|
||||
- Adds basic styling (CSS modules or Tailwind)
|
||||
- Creates test file (React Testing Library)
|
||||
- Includes JSDoc comments
|
||||
- Follows React best practices
|
||||
|
||||
**Output:** Complete component with types, styles, and tests
|
||||
|
||||
**Time:** 2-5 minutes
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Generate component from description
|
||||
/component-generator "Create a Card component with image, title, description, and action button"
|
||||
|
||||
# Shortcut
|
||||
/cg "Button component with loading state and icon support"
|
||||
|
||||
# Specify framework/style
|
||||
/cg "Modal dialog with animations" --style tailwind --test vitest
|
||||
|
||||
# Generate with directory
|
||||
/cg "UserProfile component" --output src/components/UserProfile
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Output
|
||||
|
||||
**Input:**
|
||||
```
|
||||
/cg "Create a Button component with variants (primary, secondary, ghost), sizes (sm, md, lg), loading state, and icon support"
|
||||
```
|
||||
|
||||
**Generated Files:**
|
||||
|
||||
**Button.tsx:**
|
||||
```tsx
|
||||
import { ButtonHTMLAttributes, ReactNode } from 'react'
|
||||
import styles from './Button.module.css'
|
||||
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
/** Button visual variant */
|
||||
variant?: 'primary' | 'secondary' | 'ghost'
|
||||
/** Button size */
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
/** Show loading spinner */
|
||||
loading?: boolean
|
||||
/** Icon to display before text */
|
||||
icon?: ReactNode
|
||||
/** Button content */
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Button component with multiple variants and sizes
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Button variant="primary" size="md" onClick={handleClick}>
|
||||
* Click me
|
||||
* </Button>
|
||||
* ```
|
||||
*/
|
||||
export function Button({
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
loading = false,
|
||||
icon,
|
||||
children,
|
||||
disabled,
|
||||
className = '',
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className={`${styles.button} ${styles[variant]} ${styles[size]} ${className}`}
|
||||
disabled={disabled || loading}
|
||||
aria-busy={loading}
|
||||
{...props}
|
||||
>
|
||||
{loading ? (
|
||||
<span className={styles.spinner} aria-label="Loading" />
|
||||
) : (
|
||||
<>
|
||||
{icon && <span className={styles.icon}>{icon}</span>}
|
||||
{children}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Button.module.css:**
|
||||
```css
|
||||
.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Variants */
|
||||
.primary {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.primary:hover:not(:disabled) {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: #e5e7eb;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
background: transparent;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
/* Sizes */
|
||||
.sm {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.md {
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.lg {
|
||||
padding: 1rem 2rem;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border: 2px solid currentColor;
|
||||
border-right-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
```
|
||||
|
||||
**Button.test.tsx:**
|
||||
```tsx
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import { Button } from './Button'
|
||||
|
||||
describe('Button', () => {
|
||||
it('renders button with text', () => {
|
||||
render(<Button>Click me</Button>)
|
||||
expect(screen.getByRole('button')).toHaveTextContent('Click me')
|
||||
})
|
||||
|
||||
it('handles click events', () => {
|
||||
const handleClick = jest.fn()
|
||||
render(<Button onClick={handleClick}>Click</Button>)
|
||||
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
expect(handleClick).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('shows loading state', () => {
|
||||
render(<Button loading>Submit</Button>)
|
||||
|
||||
const button = screen.getByRole('button')
|
||||
expect(button).toHaveAttribute('aria-busy', 'true')
|
||||
expect(button).toBeDisabled()
|
||||
})
|
||||
|
||||
it('disables button when disabled prop is true', () => {
|
||||
render(<Button disabled>Disabled</Button>)
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
})
|
||||
|
||||
it('renders with icon', () => {
|
||||
const icon = <span data-testid="icon"></span>
|
||||
render(<Button icon={icon}>With Icon</Button>)
|
||||
|
||||
expect(screen.getByTestId('icon')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('applies correct variant classes', () => {
|
||||
const { rerender } = render(<Button variant="primary">Primary</Button>)
|
||||
expect(screen.getByRole('button')).toHaveClass('primary')
|
||||
|
||||
rerender(<Button variant="secondary">Secondary</Button>)
|
||||
expect(screen.getByRole('button')).toHaveClass('secondary')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Templates
|
||||
|
||||
### Common Components Generated
|
||||
|
||||
**1. Card Component:**
|
||||
```tsx
|
||||
<Card
|
||||
image="/product.jpg"
|
||||
title="Product Name"
|
||||
description="Product description"
|
||||
action={<Button>Buy Now</Button>}
|
||||
/>
|
||||
```
|
||||
|
||||
**2. Modal Component:**
|
||||
```tsx
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={handleClose}
|
||||
title="Confirm Action"
|
||||
>
|
||||
<p>Are you sure?</p>
|
||||
</Modal>
|
||||
```
|
||||
|
||||
**3. Form Field Component:**
|
||||
```tsx
|
||||
<FormField
|
||||
label="Email"
|
||||
type="email"
|
||||
error={errors.email}
|
||||
required
|
||||
/>
|
||||
```
|
||||
|
||||
**4. Dropdown Component:**
|
||||
```tsx
|
||||
<Dropdown
|
||||
items={options}
|
||||
value={selected}
|
||||
onChange={handleChange}
|
||||
placeholder="Select option"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Accessibility Built-In
|
||||
- Semantic HTML elements
|
||||
- ARIA attributes where needed
|
||||
- Keyboard navigation support
|
||||
- Focus management
|
||||
- Screen reader announcements
|
||||
|
||||
### TypeScript Support
|
||||
- Full type definitions
|
||||
- Prop validation
|
||||
- IntelliSense support
|
||||
- Generic types where appropriate
|
||||
|
||||
### Testing Included
|
||||
- Unit tests with React Testing Library
|
||||
- Accessibility tests
|
||||
- User interaction tests
|
||||
- Edge case coverage
|
||||
|
||||
### Styling Options
|
||||
- CSS Modules (default)
|
||||
- Tailwind CSS
|
||||
- Styled Components
|
||||
- Emotion
|
||||
- Plain CSS
|
||||
|
||||
---
|
||||
|
||||
## Best Practices Applied
|
||||
|
||||
**Component Structure:**
|
||||
- Single responsibility
|
||||
- Composable design
|
||||
- Prop drilling avoided
|
||||
- Performance optimized (React.memo where beneficial)
|
||||
|
||||
**Code Quality:**
|
||||
- ESLint compliant
|
||||
- Prettier formatted
|
||||
- TypeScript strict mode
|
||||
- JSDoc comments
|
||||
|
||||
**Testing:**
|
||||
- 80%+ code coverage
|
||||
- User-centric tests (not implementation details)
|
||||
- Accessibility assertions
|
||||
- Happy path + edge cases
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- `/css-utility-generator` - Generate utility CSS classes
|
||||
- React Specialist (agent) - React architecture guidance
|
||||
- UI/UX Expert (agent) - Design review
|
||||
|
||||
---
|
||||
|
||||
**Generate components in seconds. Ship features faster.** ️
|
||||
620
commands/css-utility-generator.md
Normal file
620
commands/css-utility-generator.md
Normal file
@@ -0,0 +1,620 @@
|
||||
---
|
||||
description: Generate utility CSS classes for spacing, colors, typography, and layout
|
||||
shortcut: cug
|
||||
category: frontend
|
||||
difficulty: beginner
|
||||
estimated_time: 2-5 minutes
|
||||
---
|
||||
|
||||
# CSS Utility Generator
|
||||
|
||||
Generates utility CSS classes similar to Tailwind CSS for common styling needs, creating a custom utility-first CSS framework.
|
||||
|
||||
## What This Command Does
|
||||
|
||||
**Utility Class Generation:**
|
||||
- Spacing utilities (margin, padding)
|
||||
- Color utilities (background, text, border)
|
||||
- Typography utilities (font size, weight, line height)
|
||||
- Layout utilities (flexbox, grid, display)
|
||||
- Responsive breakpoints
|
||||
- Custom design tokens
|
||||
|
||||
**Output:** Complete utility CSS file with organized sections
|
||||
|
||||
**Time:** 2-5 minutes
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Generate full utility class system
|
||||
/css-utility-generator
|
||||
|
||||
# Shortcut
|
||||
/cug
|
||||
|
||||
# Generate specific categories
|
||||
/cug --categories spacing,colors,typography
|
||||
|
||||
# Custom design tokens
|
||||
/cug --config design-tokens.json
|
||||
|
||||
# With custom breakpoints
|
||||
/cug --breakpoints mobile:640px,tablet:768px,desktop:1024px
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Output
|
||||
|
||||
**Input:**
|
||||
```
|
||||
/cug --categories spacing,colors,flex
|
||||
```
|
||||
|
||||
**Generated utilities.css:**
|
||||
|
||||
```css
|
||||
/* ==========================================================================
|
||||
Utility CSS Classes
|
||||
Generated by CSS Utility Generator
|
||||
========================================================================== */
|
||||
|
||||
/* Spacing Utilities
|
||||
========================================================================== */
|
||||
|
||||
/* Margin */
|
||||
.m-0 { margin: 0; }
|
||||
.m-1 { margin: 0.25rem; } /* 4px */
|
||||
.m-2 { margin: 0.5rem; } /* 8px */
|
||||
.m-3 { margin: 0.75rem; } /* 12px */
|
||||
.m-4 { margin: 1rem; } /* 16px */
|
||||
.m-5 { margin: 1.25rem; } /* 20px */
|
||||
.m-6 { margin: 1.5rem; } /* 24px */
|
||||
.m-8 { margin: 2rem; } /* 32px */
|
||||
.m-10 { margin: 2.5rem; } /* 40px */
|
||||
.m-12 { margin: 3rem; } /* 48px */
|
||||
.m-16 { margin: 4rem; } /* 64px */
|
||||
.m-auto { margin: auto; }
|
||||
|
||||
/* Margin Top */
|
||||
.mt-0 { margin-top: 0; }
|
||||
.mt-1 { margin-top: 0.25rem; }
|
||||
.mt-2 { margin-top: 0.5rem; }
|
||||
.mt-3 { margin-top: 0.75rem; }
|
||||
.mt-4 { margin-top: 1rem; }
|
||||
.mt-6 { margin-top: 1.5rem; }
|
||||
.mt-8 { margin-top: 2rem; }
|
||||
.mt-12 { margin-top: 3rem; }
|
||||
|
||||
/* Margin Right */
|
||||
.mr-0 { margin-right: 0; }
|
||||
.mr-1 { margin-right: 0.25rem; }
|
||||
.mr-2 { margin-right: 0.5rem; }
|
||||
.mr-3 { margin-right: 0.75rem; }
|
||||
.mr-4 { margin-right: 1rem; }
|
||||
.mr-6 { margin-right: 1.5rem; }
|
||||
.mr-8 { margin-right: 2rem; }
|
||||
.mr-auto { margin-right: auto; }
|
||||
|
||||
/* Margin Bottom */
|
||||
.mb-0 { margin-bottom: 0; }
|
||||
.mb-1 { margin-bottom: 0.25rem; }
|
||||
.mb-2 { margin-bottom: 0.5rem; }
|
||||
.mb-3 { margin-bottom: 0.75rem; }
|
||||
.mb-4 { margin-bottom: 1rem; }
|
||||
.mb-6 { margin-bottom: 1.5rem; }
|
||||
.mb-8 { margin-bottom: 2rem; }
|
||||
.mb-12 { margin-bottom: 3rem; }
|
||||
|
||||
/* Margin Left */
|
||||
.ml-0 { margin-left: 0; }
|
||||
.ml-1 { margin-left: 0.25rem; }
|
||||
.ml-2 { margin-left: 0.5rem; }
|
||||
.ml-3 { margin-left: 0.75rem; }
|
||||
.ml-4 { margin-left: 1rem; }
|
||||
.ml-6 { margin-left: 1.5rem; }
|
||||
.ml-8 { margin-left: 2rem; }
|
||||
.ml-auto { margin-left: auto; }
|
||||
|
||||
/* Margin X-axis (horizontal) */
|
||||
.mx-0 { margin-left: 0; margin-right: 0; }
|
||||
.mx-1 { margin-left: 0.25rem; margin-right: 0.25rem; }
|
||||
.mx-2 { margin-left: 0.5rem; margin-right: 0.5rem; }
|
||||
.mx-4 { margin-left: 1rem; margin-right: 1rem; }
|
||||
.mx-auto { margin-left: auto; margin-right: auto; }
|
||||
|
||||
/* Margin Y-axis (vertical) */
|
||||
.my-0 { margin-top: 0; margin-bottom: 0; }
|
||||
.my-1 { margin-top: 0.25rem; margin-bottom: 0.25rem; }
|
||||
.my-2 { margin-top: 0.5rem; margin-bottom: 0.5rem; }
|
||||
.my-4 { margin-top: 1rem; margin-bottom: 1rem; }
|
||||
.my-8 { margin-top: 2rem; margin-bottom: 2rem; }
|
||||
|
||||
/* Padding */
|
||||
.p-0 { padding: 0; }
|
||||
.p-1 { padding: 0.25rem; }
|
||||
.p-2 { padding: 0.5rem; }
|
||||
.p-3 { padding: 0.75rem; }
|
||||
.p-4 { padding: 1rem; }
|
||||
.p-5 { padding: 1.25rem; }
|
||||
.p-6 { padding: 1.5rem; }
|
||||
.p-8 { padding: 2rem; }
|
||||
.p-10 { padding: 2.5rem; }
|
||||
.p-12 { padding: 3rem; }
|
||||
|
||||
/* Padding Top */
|
||||
.pt-0 { padding-top: 0; }
|
||||
.pt-1 { padding-top: 0.25rem; }
|
||||
.pt-2 { padding-top: 0.5rem; }
|
||||
.pt-4 { padding-top: 1rem; }
|
||||
.pt-6 { padding-top: 1.5rem; }
|
||||
.pt-8 { padding-top: 2rem; }
|
||||
|
||||
/* Padding Right */
|
||||
.pr-0 { padding-right: 0; }
|
||||
.pr-2 { padding-right: 0.5rem; }
|
||||
.pr-4 { padding-right: 1rem; }
|
||||
.pr-6 { padding-right: 1.5rem; }
|
||||
.pr-8 { padding-right: 2rem; }
|
||||
|
||||
/* Padding Bottom */
|
||||
.pb-0 { padding-bottom: 0; }
|
||||
.pb-2 { padding-bottom: 0.5rem; }
|
||||
.pb-4 { padding-bottom: 1rem; }
|
||||
.pb-6 { padding-bottom: 1.5rem; }
|
||||
.pb-8 { padding-bottom: 2rem; }
|
||||
|
||||
/* Padding Left */
|
||||
.pl-0 { padding-left: 0; }
|
||||
.pl-2 { padding-left: 0.5rem; }
|
||||
.pl-4 { padding-left: 1rem; }
|
||||
.pl-6 { padding-left: 1.5rem; }
|
||||
.pl-8 { padding-left: 2rem; }
|
||||
|
||||
/* Padding X-axis */
|
||||
.px-0 { padding-left: 0; padding-right: 0; }
|
||||
.px-2 { padding-left: 0.5rem; padding-right: 0.5rem; }
|
||||
.px-4 { padding-left: 1rem; padding-right: 1rem; }
|
||||
.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
|
||||
.px-8 { padding-left: 2rem; padding-right: 2rem; }
|
||||
|
||||
/* Padding Y-axis */
|
||||
.py-0 { padding-top: 0; padding-bottom: 0; }
|
||||
.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
|
||||
.py-4 { padding-top: 1rem; padding-bottom: 1rem; }
|
||||
.py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; }
|
||||
.py-8 { padding-top: 2rem; padding-bottom: 2rem; }
|
||||
|
||||
/* Color Utilities
|
||||
========================================================================== */
|
||||
|
||||
/* Background Colors */
|
||||
.bg-white { background-color: #ffffff; }
|
||||
.bg-gray-50 { background-color: #f9fafb; }
|
||||
.bg-gray-100 { background-color: #f3f4f6; }
|
||||
.bg-gray-200 { background-color: #e5e7eb; }
|
||||
.bg-gray-300 { background-color: #d1d5db; }
|
||||
.bg-gray-400 { background-color: #9ca3af; }
|
||||
.bg-gray-500 { background-color: #6b7280; }
|
||||
.bg-gray-600 { background-color: #4b5563; }
|
||||
.bg-gray-700 { background-color: #374151; }
|
||||
.bg-gray-800 { background-color: #1f2937; }
|
||||
.bg-gray-900 { background-color: #111827; }
|
||||
.bg-black { background-color: #000000; }
|
||||
|
||||
.bg-primary { background-color: #3b82f6; }
|
||||
.bg-secondary { background-color: #6b7280; }
|
||||
.bg-success { background-color: #10b981; }
|
||||
.bg-danger { background-color: #ef4444; }
|
||||
.bg-warning { background-color: #f59e0b; }
|
||||
.bg-info { background-color: #3b82f6; }
|
||||
|
||||
.bg-transparent { background-color: transparent; }
|
||||
|
||||
/* Text Colors */
|
||||
.text-white { color: #ffffff; }
|
||||
.text-gray-50 { color: #f9fafb; }
|
||||
.text-gray-100 { color: #f3f4f6; }
|
||||
.text-gray-200 { color: #e5e7eb; }
|
||||
.text-gray-300 { color: #d1d5db; }
|
||||
.text-gray-400 { color: #9ca3af; }
|
||||
.text-gray-500 { color: #6b7280; }
|
||||
.text-gray-600 { color: #4b5563; }
|
||||
.text-gray-700 { color: #374151; }
|
||||
.text-gray-800 { color: #1f2937; }
|
||||
.text-gray-900 { color: #111827; }
|
||||
.text-black { color: #000000; }
|
||||
|
||||
.text-primary { color: #3b82f6; }
|
||||
.text-secondary { color: #6b7280; }
|
||||
.text-success { color: #10b981; }
|
||||
.text-danger { color: #ef4444; }
|
||||
.text-warning { color: #f59e0b; }
|
||||
.text-info { color: #3b82f6; }
|
||||
|
||||
/* Flexbox Utilities
|
||||
========================================================================== */
|
||||
|
||||
/* Display */
|
||||
.flex { display: flex; }
|
||||
.inline-flex { display: inline-flex; }
|
||||
|
||||
/* Flex Direction */
|
||||
.flex-row { flex-direction: row; }
|
||||
.flex-row-reverse { flex-direction: row-reverse; }
|
||||
.flex-col { flex-direction: column; }
|
||||
.flex-col-reverse { flex-direction: column-reverse; }
|
||||
|
||||
/* Flex Wrap */
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
.flex-nowrap { flex-wrap: nowrap; }
|
||||
.flex-wrap-reverse { flex-wrap: wrap-reverse; }
|
||||
|
||||
/* Justify Content */
|
||||
.justify-start { justify-content: flex-start; }
|
||||
.justify-end { justify-content: flex-end; }
|
||||
.justify-center { justify-content: center; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
.justify-around { justify-content: space-around; }
|
||||
.justify-evenly { justify-content: space-evenly; }
|
||||
|
||||
/* Align Items */
|
||||
.items-start { align-items: flex-start; }
|
||||
.items-end { align-items: flex-end; }
|
||||
.items-center { align-items: center; }
|
||||
.items-baseline { align-items: baseline; }
|
||||
.items-stretch { align-items: stretch; }
|
||||
|
||||
/* Align Self */
|
||||
.self-auto { align-self: auto; }
|
||||
.self-start { align-self: flex-start; }
|
||||
.self-end { align-self: flex-end; }
|
||||
.self-center { align-self: center; }
|
||||
.self-stretch { align-self: stretch; }
|
||||
|
||||
/* Gap */
|
||||
.gap-0 { gap: 0; }
|
||||
.gap-1 { gap: 0.25rem; }
|
||||
.gap-2 { gap: 0.5rem; }
|
||||
.gap-3 { gap: 0.75rem; }
|
||||
.gap-4 { gap: 1rem; }
|
||||
.gap-6 { gap: 1.5rem; }
|
||||
.gap-8 { gap: 2rem; }
|
||||
|
||||
/* Flex Grow/Shrink */
|
||||
.flex-1 { flex: 1 1 0%; }
|
||||
.flex-auto { flex: 1 1 auto; }
|
||||
.flex-initial { flex: 0 1 auto; }
|
||||
.flex-none { flex: none; }
|
||||
|
||||
.flex-grow { flex-grow: 1; }
|
||||
.flex-grow-0 { flex-grow: 0; }
|
||||
|
||||
.flex-shrink { flex-shrink: 1; }
|
||||
.flex-shrink-0 { flex-shrink: 0; }
|
||||
|
||||
/* Responsive Breakpoints
|
||||
========================================================================== */
|
||||
|
||||
@media (min-width: 640px) {
|
||||
/* sm: spacing */
|
||||
.sm\:m-0 { margin: 0; }
|
||||
.sm\:m-4 { margin: 1rem; }
|
||||
.sm\:p-4 { padding: 1rem; }
|
||||
|
||||
/* sm: flexbox */
|
||||
.sm\:flex { display: flex; }
|
||||
.sm\:flex-row { flex-direction: row; }
|
||||
.sm\:justify-center { justify-content: center; }
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
/* md: spacing */
|
||||
.md\:m-0 { margin: 0; }
|
||||
.md\:m-6 { margin: 1.5rem; }
|
||||
.md\:p-6 { padding: 1.5rem; }
|
||||
|
||||
/* md: flexbox */
|
||||
.md\:flex { display: flex; }
|
||||
.md\:flex-row { flex-direction: row; }
|
||||
.md\:justify-between { justify-content: space-between; }
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
/* lg: spacing */
|
||||
.lg\:m-0 { margin: 0; }
|
||||
.lg\:m-8 { margin: 2rem; }
|
||||
.lg\:p-8 { padding: 2rem; }
|
||||
|
||||
/* lg: flexbox */
|
||||
.lg\:flex { display: flex; }
|
||||
.lg\:flex-row { flex-direction: row; }
|
||||
.lg\:gap-8 { gap: 2rem; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Utility Categories
|
||||
|
||||
### 1. Spacing (Margin & Padding)
|
||||
```css
|
||||
/* Scale: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24 */
|
||||
/* Directions: all, t, r, b, l, x, y */
|
||||
.m-4 /* margin: 1rem */
|
||||
.mt-2 /* margin-top: 0.5rem */
|
||||
.px-4 /* padding-left/right: 1rem */
|
||||
.my-8 /* margin-top/bottom: 2rem */
|
||||
```
|
||||
|
||||
### 2. Typography
|
||||
```css
|
||||
/* Font Size */
|
||||
.text-xs { font-size: 0.75rem; }
|
||||
.text-sm { font-size: 0.875rem; }
|
||||
.text-base { font-size: 1rem; }
|
||||
.text-lg { font-size: 1.125rem; }
|
||||
.text-xl { font-size: 1.25rem; }
|
||||
.text-2xl { font-size: 1.5rem; }
|
||||
.text-3xl { font-size: 1.875rem; }
|
||||
.text-4xl { font-size: 2.25rem; }
|
||||
|
||||
/* Font Weight */
|
||||
.font-thin { font-weight: 100; }
|
||||
.font-light { font-weight: 300; }
|
||||
.font-normal { font-weight: 400; }
|
||||
.font-medium { font-weight: 500; }
|
||||
.font-semibold { font-weight: 600; }
|
||||
.font-bold { font-weight: 700; }
|
||||
.font-extrabold { font-weight: 800; }
|
||||
|
||||
/* Text Align */
|
||||
.text-left { text-align: left; }
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; }
|
||||
.text-justify { text-align: justify; }
|
||||
|
||||
/* Line Height */
|
||||
.leading-none { line-height: 1; }
|
||||
.leading-tight { line-height: 1.25; }
|
||||
.leading-normal { line-height: 1.5; }
|
||||
.leading-relaxed { line-height: 1.75; }
|
||||
.leading-loose { line-height: 2; }
|
||||
```
|
||||
|
||||
### 3. Layout
|
||||
```css
|
||||
/* Display */
|
||||
.block { display: block; }
|
||||
.inline { display: inline; }
|
||||
.inline-block { display: inline-block; }
|
||||
.flex { display: flex; }
|
||||
.inline-flex { display: inline-flex; }
|
||||
.grid { display: grid; }
|
||||
.hidden { display: none; }
|
||||
|
||||
/* Position */
|
||||
.static { position: static; }
|
||||
.relative { position: relative; }
|
||||
.absolute { position: absolute; }
|
||||
.fixed { position: fixed; }
|
||||
.sticky { position: sticky; }
|
||||
|
||||
/* Width */
|
||||
.w-auto { width: auto; }
|
||||
.w-full { width: 100%; }
|
||||
.w-screen { width: 100vw; }
|
||||
.w-1\/2 { width: 50%; }
|
||||
.w-1\/3 { width: 33.333333%; }
|
||||
.w-1\/4 { width: 25%; }
|
||||
|
||||
/* Height */
|
||||
.h-auto { height: auto; }
|
||||
.h-full { height: 100%; }
|
||||
.h-screen { height: 100vh; }
|
||||
```
|
||||
|
||||
### 4. Grid System
|
||||
```css
|
||||
/* Grid Template Columns */
|
||||
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
||||
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||
.grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
||||
.grid-cols-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); }
|
||||
|
||||
/* Grid Gap */
|
||||
.gap-0 { gap: 0; }
|
||||
.gap-2 { gap: 0.5rem; }
|
||||
.gap-4 { gap: 1rem; }
|
||||
.gap-6 { gap: 1.5rem; }
|
||||
.gap-8 { gap: 2rem; }
|
||||
```
|
||||
|
||||
### 5. Borders & Radius
|
||||
```css
|
||||
/* Border Width */
|
||||
.border-0 { border-width: 0; }
|
||||
.border { border-width: 1px; }
|
||||
.border-2 { border-width: 2px; }
|
||||
.border-4 { border-width: 4px; }
|
||||
|
||||
/* Border Radius */
|
||||
.rounded-none { border-radius: 0; }
|
||||
.rounded-sm { border-radius: 0.125rem; }
|
||||
.rounded { border-radius: 0.25rem; }
|
||||
.rounded-md { border-radius: 0.375rem; }
|
||||
.rounded-lg { border-radius: 0.5rem; }
|
||||
.rounded-xl { border-radius: 0.75rem; }
|
||||
.rounded-full { border-radius: 9999px; }
|
||||
|
||||
/* Border Color */
|
||||
.border-gray-200 { border-color: #e5e7eb; }
|
||||
.border-gray-300 { border-color: #d1d5db; }
|
||||
.border-primary { border-color: #3b82f6; }
|
||||
```
|
||||
|
||||
### 6. Effects
|
||||
```css
|
||||
/* Shadow */
|
||||
.shadow-none { box-shadow: none; }
|
||||
.shadow-sm { box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); }
|
||||
.shadow { box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); }
|
||||
.shadow-md { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); }
|
||||
.shadow-lg { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); }
|
||||
.shadow-xl { box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); }
|
||||
|
||||
/* Opacity */
|
||||
.opacity-0 { opacity: 0; }
|
||||
.opacity-25 { opacity: 0.25; }
|
||||
.opacity-50 { opacity: 0.5; }
|
||||
.opacity-75 { opacity: 0.75; }
|
||||
.opacity-100 { opacity: 1; }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Card Component
|
||||
```html
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-4">
|
||||
<h2 class="text-2xl font-bold mb-2 text-gray-900">Card Title</h2>
|
||||
<p class="text-gray-600 mb-4">Card description goes here.</p>
|
||||
<button class="bg-primary text-white px-4 py-2 rounded">
|
||||
Action
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Example 2: Flexbox Layout
|
||||
```html
|
||||
<div class="flex justify-between items-center p-4 bg-gray-50">
|
||||
<div class="flex items-center gap-2">
|
||||
<img src="logo.png" class="w-8 h-8" />
|
||||
<span class="font-semibold">Brand</span>
|
||||
</div>
|
||||
<nav class="flex gap-4">
|
||||
<a href="#" class="text-gray-700">Home</a>
|
||||
<a href="#" class="text-gray-700">About</a>
|
||||
<a href="#" class="text-gray-700">Contact</a>
|
||||
</nav>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Example 3: Responsive Grid
|
||||
```html
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-8">
|
||||
<div class="bg-white p-4 rounded shadow">Item 1</div>
|
||||
<div class="bg-white p-4 rounded shadow">Item 2</div>
|
||||
<div class="bg-white p-4 rounded shadow">Item 3</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Customization Options
|
||||
|
||||
### Design Tokens Configuration
|
||||
|
||||
**design-tokens.json:**
|
||||
```json
|
||||
{
|
||||
"colors": {
|
||||
"primary": "#3b82f6",
|
||||
"secondary": "#6b7280",
|
||||
"success": "#10b981",
|
||||
"danger": "#ef4444",
|
||||
"warning": "#f59e0b"
|
||||
},
|
||||
"spacing": {
|
||||
"scale": [0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64]
|
||||
},
|
||||
"typography": {
|
||||
"fontSizes": {
|
||||
"xs": "0.75rem",
|
||||
"sm": "0.875rem",
|
||||
"base": "1rem",
|
||||
"lg": "1.125rem",
|
||||
"xl": "1.25rem",
|
||||
"2xl": "1.5rem"
|
||||
},
|
||||
"fontWeights": {
|
||||
"normal": 400,
|
||||
"medium": 500,
|
||||
"semibold": 600,
|
||||
"bold": 700
|
||||
}
|
||||
},
|
||||
"breakpoints": {
|
||||
"sm": "640px",
|
||||
"md": "768px",
|
||||
"lg": "1024px",
|
||||
"xl": "1280px"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benefits
|
||||
|
||||
**1. No Build Step Required**
|
||||
- Pure CSS, works immediately
|
||||
- No JavaScript runtime
|
||||
- No npm dependencies
|
||||
|
||||
**2. Familiar Syntax**
|
||||
- Tailwind-like class names
|
||||
- Easy to learn for Tailwind users
|
||||
- Predictable naming conventions
|
||||
|
||||
**3. Customizable**
|
||||
- Define your own design tokens
|
||||
- Choose which categories to include
|
||||
- Adjust spacing scales and breakpoints
|
||||
|
||||
**4. Lightweight**
|
||||
- Generate only what you need
|
||||
- ~10-50KB depending on categories
|
||||
- Much smaller than full Tailwind
|
||||
|
||||
**5. Framework Agnostic**
|
||||
- Works with React, Vue, vanilla HTML
|
||||
- No framework lock-in
|
||||
- Pure CSS solution
|
||||
|
||||
---
|
||||
|
||||
## Integration
|
||||
|
||||
### Add to HTML
|
||||
```html
|
||||
<link rel="stylesheet" href="utilities.css">
|
||||
```
|
||||
|
||||
### Import in CSS
|
||||
```css
|
||||
@import url('utilities.css');
|
||||
```
|
||||
|
||||
### Import in JavaScript
|
||||
```javascript
|
||||
import './utilities.css'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- `/component-generator` - Generate React components using these utilities
|
||||
- React Specialist (agent) - Component architecture guidance
|
||||
- UI/UX Expert (agent) - Design system review
|
||||
|
||||
---
|
||||
|
||||
**Build your design system. Style faster. Ship consistent UIs.**
|
||||
337
commands/env-config-setup.md
Normal file
337
commands/env-config-setup.md
Normal file
@@ -0,0 +1,337 @@
|
||||
---
|
||||
description: Generate environment configuration files and validation schemas
|
||||
shortcut: ecs
|
||||
category: devops
|
||||
difficulty: beginner
|
||||
estimated_time: 2-3 minutes
|
||||
---
|
||||
|
||||
# Environment Config Setup
|
||||
|
||||
Generates environment configuration files (.env templates, validation schemas, and type-safe config loading) for multiple environments.
|
||||
|
||||
## What This Command Does
|
||||
|
||||
**Generated Configuration:**
|
||||
- .env.example (committed template)
|
||||
- .env.development, .env.production
|
||||
- Config validation schema (Zod)
|
||||
- Type-safe config loader
|
||||
- Secret management guidance
|
||||
- Docker environment setup
|
||||
|
||||
**Output:** Complete environment configuration system
|
||||
|
||||
**Time:** 2-3 minutes
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Generate basic environment config
|
||||
/env-config-setup
|
||||
|
||||
# Shortcut
|
||||
/ecs --services database,redis,email
|
||||
|
||||
# With specific platform
|
||||
/ecs --platform aws --features secrets-manager
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Generated Files
|
||||
|
||||
### **.env.example** (Template - Committed to Repo)
|
||||
|
||||
```bash
|
||||
# Application
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
APP_NAME=My Application
|
||||
APP_URL=http://localhost:3000
|
||||
|
||||
# Database
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
|
||||
DATABASE_POOL_MIN=2
|
||||
DATABASE_POOL_MAX=10
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://localhost:6379
|
||||
REDIS_PREFIX=myapp:
|
||||
|
||||
# Authentication
|
||||
JWT_SECRET=generate-random-32-char-secret-here
|
||||
JWT_EXPIRES_IN=15m
|
||||
JWT_REFRESH_SECRET=generate-random-32-char-refresh-secret
|
||||
JWT_REFRESH_EXPIRES_IN=7d
|
||||
|
||||
# Email (SendGrid)
|
||||
SENDGRID_API_KEY=SG.your-api-key-here
|
||||
FROM_EMAIL=[email protected]
|
||||
|
||||
# AWS (Optional)
|
||||
AWS_ACCESS_KEY_ID=your-access-key
|
||||
AWS_SECRET_ACCESS_KEY=your-secret-key
|
||||
AWS_REGION=us-east-1
|
||||
S3_BUCKET=your-bucket-name
|
||||
|
||||
# External APIs
|
||||
STRIPE_SECRET_KEY=sk_test_your-stripe-key
|
||||
STRIPE_WEBHOOK_SECRET=whsec_your-webhook-secret
|
||||
|
||||
# Monitoring
|
||||
SENTRY_DSN=https://your-sentry-dsn
|
||||
LOG_LEVEL=info
|
||||
|
||||
# Feature Flags
|
||||
ENABLE_FEATURE_X=false
|
||||
```
|
||||
|
||||
### **.env.development**
|
||||
|
||||
```bash
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
DATABASE_URL=postgresql://postgres:password@localhost:5432/myapp_dev
|
||||
REDIS_URL=redis://localhost:6379
|
||||
LOG_LEVEL=debug
|
||||
```
|
||||
|
||||
### **.env.production**
|
||||
|
||||
```bash
|
||||
NODE_ENV=production
|
||||
PORT=8080
|
||||
# Use environment variables or secrets manager for sensitive values
|
||||
DATABASE_URL=${DATABASE_URL}
|
||||
REDIS_URL=${REDIS_URL}
|
||||
JWT_SECRET=${JWT_SECRET}
|
||||
LOG_LEVEL=warn
|
||||
```
|
||||
|
||||
### **config/env.ts** (Type-Safe Config Loader)
|
||||
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
// Load appropriate .env file
|
||||
const envFile = process.env.NODE_ENV === 'production'
|
||||
? '.env.production'
|
||||
: '.env.development'
|
||||
|
||||
dotenv.config({ path: envFile })
|
||||
|
||||
// Define validation schema
|
||||
const envSchema = z.object({
|
||||
// Application
|
||||
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
||||
PORT: z.coerce.number().min(1).max(65535).default(3000),
|
||||
APP_NAME: z.string().min(1),
|
||||
APP_URL: z.string().url(),
|
||||
|
||||
// Database
|
||||
DATABASE_URL: z.string().url(),
|
||||
DATABASE_POOL_MIN: z.coerce.number().min(0).default(2),
|
||||
DATABASE_POOL_MAX: z.coerce.number().min(1).default(10),
|
||||
|
||||
// Redis
|
||||
REDIS_URL: z.string().url(),
|
||||
REDIS_PREFIX: z.string().default(''),
|
||||
|
||||
// Authentication
|
||||
JWT_SECRET: z.string().min(32),
|
||||
JWT_EXPIRES_IN: z.string().default('15m'),
|
||||
JWT_REFRESH_SECRET: z.string().min(32),
|
||||
JWT_REFRESH_EXPIRES_IN: z.string().default('7d'),
|
||||
|
||||
// Email
|
||||
SENDGRID_API_KEY: z.string().startsWith('SG.'),
|
||||
FROM_EMAIL: z.string().email(),
|
||||
|
||||
// AWS (optional)
|
||||
AWS_ACCESS_KEY_ID: z.string().optional(),
|
||||
AWS_SECRET_ACCESS_KEY: z.string().optional(),
|
||||
AWS_REGION: z.string().default('us-east-1'),
|
||||
S3_BUCKET: z.string().optional(),
|
||||
|
||||
// External APIs
|
||||
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
|
||||
STRIPE_WEBHOOK_SECRET: z.string().startsWith('whsec_'),
|
||||
|
||||
// Monitoring
|
||||
SENTRY_DSN: z.string().url().optional(),
|
||||
LOG_LEVEL: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
|
||||
|
||||
// Feature Flags
|
||||
ENABLE_FEATURE_X: z.coerce.boolean().default(false)
|
||||
})
|
||||
|
||||
// Parse and validate
|
||||
const parsedEnv = envSchema.safeParse(process.env)
|
||||
|
||||
if (!parsedEnv.success) {
|
||||
console.error(' Invalid environment variables:')
|
||||
console.error(parsedEnv.error.flatten().fieldErrors)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
export const env = parsedEnv.data
|
||||
|
||||
// Type-safe access
|
||||
export type Env = z.infer<typeof envSchema>
|
||||
```
|
||||
|
||||
### **config/secrets.ts** (AWS Secrets Manager)
|
||||
|
||||
```typescript
|
||||
import { SecretsManager } from '@aws-sdk/client-secrets-manager'
|
||||
|
||||
const client = new SecretsManager({ region: process.env.AWS_REGION })
|
||||
|
||||
export async function loadSecrets(secretName: string) {
|
||||
try {
|
||||
const response = await client.getSecretValue({ SecretId: secretName })
|
||||
return JSON.parse(response.SecretString || '{}')
|
||||
} catch (error) {
|
||||
console.error('Failed to load secrets:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const secrets = await loadSecrets('prod/myapp/secrets')
|
||||
process.env.JWT_SECRET = secrets.JWT_SECRET
|
||||
```
|
||||
|
||||
### **docker-compose.env.yml**
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
env_file:
|
||||
- .env.development
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- PORT=3000
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
db:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-myapp_dev}
|
||||
ports:
|
||||
- "5432:5432"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
**1. Never Commit Secrets:**
|
||||
```bash
|
||||
# .gitignore
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
.env.production
|
||||
*.key
|
||||
*.pem
|
||||
secrets/
|
||||
```
|
||||
|
||||
**2. Use Secret Rotation:**
|
||||
```bash
|
||||
# Rotate secrets regularly
|
||||
# Use AWS Secrets Manager, GCP Secret Manager, or Azure Key Vault
|
||||
# Example: Rotate JWT secrets every 30 days
|
||||
```
|
||||
|
||||
**3. Least Privilege:**
|
||||
```bash
|
||||
# Only provide necessary permissions
|
||||
# Use separate credentials for dev/staging/prod
|
||||
# Implement role-based access control
|
||||
```
|
||||
|
||||
**4. Environment Validation:**
|
||||
```typescript
|
||||
// Validate on startup
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
if (!env.JWT_SECRET || env.JWT_SECRET.length < 32) {
|
||||
throw new Error('Production JWT_SECRET must be at least 32 characters')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Secret Generation
|
||||
|
||||
```bash
|
||||
# Generate secure random secrets
|
||||
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
||||
|
||||
# Or use openssl
|
||||
openssl rand -hex 32
|
||||
|
||||
# For JWT secrets (base64)
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Platform-Specific Setup
|
||||
|
||||
**Vercel:**
|
||||
```bash
|
||||
# Set environment variables via CLI
|
||||
vercel env add DATABASE_URL production
|
||||
vercel env add JWT_SECRET production
|
||||
```
|
||||
|
||||
**Railway:**
|
||||
```bash
|
||||
# Environment variables in dashboard
|
||||
# Or via railway.json
|
||||
{
|
||||
"deploy": {
|
||||
"envVars": {
|
||||
"NODE_ENV": "production"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**AWS ECS:**
|
||||
```json
|
||||
{
|
||||
"containerDefinitions": [{
|
||||
"secrets": [
|
||||
{
|
||||
"name": "DATABASE_URL",
|
||||
"valueFrom": "arn:aws:secretsmanager:region:account:secret:name"
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- `/auth-setup` - Generate authentication system
|
||||
- `/project-scaffold` - Generate full project structure
|
||||
|
||||
---
|
||||
|
||||
**Manage secrets safely. Configure environments easily. Deploy confidently.** ️
|
||||
658
commands/express-api-scaffold.md
Normal file
658
commands/express-api-scaffold.md
Normal file
@@ -0,0 +1,658 @@
|
||||
---
|
||||
description: Generate production-ready Express.js REST API with TypeScript and auth
|
||||
shortcut: eas
|
||||
category: backend
|
||||
difficulty: intermediate
|
||||
estimated_time: 5-10 minutes
|
||||
---
|
||||
|
||||
# Express API Scaffold
|
||||
|
||||
Generates a complete Express.js REST API boilerplate with TypeScript, authentication, database integration, and testing setup.
|
||||
|
||||
## What This Command Does
|
||||
|
||||
**Generated Project:**
|
||||
- Express.js with TypeScript
|
||||
- JWT authentication
|
||||
- Database integration (Prisma or TypeORM)
|
||||
- Input validation (Zod)
|
||||
- Error handling middleware
|
||||
- Rate limiting & security (Helmet, CORS)
|
||||
- Testing setup (Jest + Supertest)
|
||||
- Docker configuration
|
||||
- Example CRUD endpoints
|
||||
|
||||
**Output:** Complete API project ready for development
|
||||
|
||||
**Time:** 5-10 minutes
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Generate full Express API
|
||||
/express-api-scaffold "Task Management API"
|
||||
|
||||
# Shortcut
|
||||
/eas "E-commerce API"
|
||||
|
||||
# With specific database
|
||||
/eas "Blog API" --database postgresql
|
||||
|
||||
# With authentication type
|
||||
/eas "Social API" --auth jwt --database mongodb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Output
|
||||
|
||||
**Input:**
|
||||
```
|
||||
/eas "Task Management API" --database postgresql
|
||||
```
|
||||
|
||||
**Generated Project Structure:**
|
||||
```
|
||||
task-api/
|
||||
├── src/
|
||||
│ ├── controllers/ # Request handlers
|
||||
│ │ ├── auth.controller.ts
|
||||
│ │ └── task.controller.ts
|
||||
│ ├── middleware/ # Express middleware
|
||||
│ │ ├── auth.middleware.ts
|
||||
│ │ ├── error.middleware.ts
|
||||
│ │ └── validation.middleware.ts
|
||||
│ ├── models/ # Database models
|
||||
│ │ └── task.model.ts
|
||||
│ ├── routes/ # API routes
|
||||
│ │ ├── auth.routes.ts
|
||||
│ │ └── task.routes.ts
|
||||
│ ├── services/ # Business logic
|
||||
│ │ ├── auth.service.ts
|
||||
│ │ └── task.service.ts
|
||||
│ ├── utils/ # Utilities
|
||||
│ │ ├── jwt.util.ts
|
||||
│ │ └── password.util.ts
|
||||
│ ├── config/ # Configuration
|
||||
│ │ └── database.ts
|
||||
│ ├── types/ # TypeScript types
|
||||
│ │ └── express.d.ts
|
||||
│ ├── app.ts # Express app setup
|
||||
│ └── server.ts # Server entry point
|
||||
├── tests/
|
||||
│ ├── auth.test.ts
|
||||
│ └── task.test.ts
|
||||
├── prisma/
|
||||
│ └── schema.prisma # Database schema
|
||||
├── .env.example
|
||||
├── .gitignore
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
├── jest.config.js
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Generated Files
|
||||
|
||||
### 1. **src/server.ts** (Entry Point)
|
||||
|
||||
```typescript
|
||||
import app from './app'
|
||||
import { config } from './config'
|
||||
|
||||
const PORT = process.env.PORT || 3000
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`)
|
||||
console.log(`Environment: ${process.env.NODE_ENV}`)
|
||||
})
|
||||
```
|
||||
|
||||
### 2. **src/app.ts** (Express Setup)
|
||||
|
||||
```typescript
|
||||
import express, { Application } from 'express'
|
||||
import cors from 'cors'
|
||||
import helmet from 'helmet'
|
||||
import morgan from 'morgan'
|
||||
import rateLimit from 'express-rate-limit'
|
||||
|
||||
import authRoutes from './routes/auth.routes'
|
||||
import taskRoutes from './routes/task.routes'
|
||||
import { errorHandler } from './middleware/error.middleware'
|
||||
import { notFoundHandler } from './middleware/notFound.middleware'
|
||||
|
||||
const app: Application = express()
|
||||
|
||||
// Security middleware
|
||||
app.use(helmet())
|
||||
app.use(cors({
|
||||
origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
|
||||
credentials: true
|
||||
}))
|
||||
|
||||
// Rate limiting
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 100, // 100 requests per window
|
||||
message: 'Too many requests, please try again later'
|
||||
})
|
||||
app.use('/api/', limiter)
|
||||
|
||||
// Parsing middleware
|
||||
app.use(express.json())
|
||||
app.use(express.urlencoded({ extended: true }))
|
||||
|
||||
// Logging
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
app.use(morgan('combined'))
|
||||
}
|
||||
|
||||
// Health check
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() })
|
||||
})
|
||||
|
||||
// Routes
|
||||
app.use('/api/auth', authRoutes)
|
||||
app.use('/api/tasks', taskRoutes)
|
||||
|
||||
// Error handling
|
||||
app.use(notFoundHandler)
|
||||
app.use(errorHandler)
|
||||
|
||||
export default app
|
||||
```
|
||||
|
||||
### 3. **src/controllers/auth.controller.ts**
|
||||
|
||||
```typescript
|
||||
import { Request, Response, NextFunction } from 'express'
|
||||
import { AuthService } from '../services/auth.service'
|
||||
import { ApiError } from '../utils/ApiError'
|
||||
|
||||
const authService = new AuthService()
|
||||
|
||||
export class AuthController {
|
||||
async register(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const { email, password, name } = req.body
|
||||
|
||||
const result = await authService.register({ email, password, name })
|
||||
|
||||
res.status(201).json({
|
||||
data: {
|
||||
user: result.user,
|
||||
token: result.token
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
async login(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const { email, password } = req.body
|
||||
|
||||
const result = await authService.login(email, password)
|
||||
|
||||
res.json({
|
||||
data: {
|
||||
user: result.user,
|
||||
token: result.token
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
|
||||
async getProfile(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const userId = req.user!.id
|
||||
|
||||
const user = await authService.getUserById(userId)
|
||||
|
||||
res.json({ data: user })
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. **src/middleware/auth.middleware.ts**
|
||||
|
||||
```typescript
|
||||
import { Request, Response, NextFunction } from 'express'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { ApiError } from '../utils/ApiError'
|
||||
|
||||
interface JwtPayload {
|
||||
userId: string
|
||||
email: string
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: {
|
||||
id: string
|
||||
email: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function authenticate(req: Request, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const authHeader = req.headers.authorization
|
||||
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
throw new ApiError(401, 'No token provided')
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1]
|
||||
|
||||
const decoded = jwt.verify(
|
||||
token,
|
||||
process.env.JWT_SECRET!
|
||||
) as JwtPayload
|
||||
|
||||
req.user = {
|
||||
id: decoded.userId,
|
||||
email: decoded.email
|
||||
}
|
||||
|
||||
next()
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.JsonWebTokenError) {
|
||||
next(new ApiError(401, 'Invalid token'))
|
||||
} else {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. **src/middleware/error.middleware.ts**
|
||||
|
||||
```typescript
|
||||
import { Request, Response, NextFunction } from 'express'
|
||||
import { ApiError } from '../utils/ApiError'
|
||||
import { ZodError } from 'zod'
|
||||
|
||||
export function errorHandler(
|
||||
err: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
console.error('Error:', err)
|
||||
|
||||
// Handle known API errors
|
||||
if (err instanceof ApiError) {
|
||||
return res.status(err.statusCode).json({
|
||||
error: {
|
||||
code: err.name,
|
||||
message: err.message,
|
||||
...(err.details && { details: err.details })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Handle validation errors (Zod)
|
||||
if (err instanceof ZodError) {
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: 'Validation failed',
|
||||
details: err.errors.map(e => ({
|
||||
field: e.path.join('.'),
|
||||
message: e.message
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Handle unexpected errors
|
||||
res.status(500).json({
|
||||
error: {
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: process.env.NODE_ENV === 'production'
|
||||
? 'An unexpected error occurred'
|
||||
: err.message
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 6. **src/routes/task.routes.ts**
|
||||
|
||||
```typescript
|
||||
import { Router } from 'express'
|
||||
import { TaskController } from '../controllers/task.controller'
|
||||
import { authenticate } from '../middleware/auth.middleware'
|
||||
import { validate } from '../middleware/validation.middleware'
|
||||
import { createTaskSchema, updateTaskSchema } from '../schemas/task.schema'
|
||||
|
||||
const router = Router()
|
||||
const taskController = new TaskController()
|
||||
|
||||
// All routes require authentication
|
||||
router.use(authenticate)
|
||||
|
||||
router.get('/', taskController.list)
|
||||
router.post('/', validate(createTaskSchema), taskController.create)
|
||||
router.get('/:id', taskController.getById)
|
||||
router.patch('/:id', validate(updateTaskSchema), taskController.update)
|
||||
router.delete('/:id', taskController.delete)
|
||||
|
||||
export default router
|
||||
```
|
||||
|
||||
### 7. **src/services/task.service.ts**
|
||||
|
||||
```typescript
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import { ApiError } from '../utils/ApiError'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
export class TaskService {
|
||||
async create(userId: string, data: { title: string; description?: string }) {
|
||||
return await prisma.task.create({
|
||||
data: {
|
||||
...data,
|
||||
userId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async findAll(userId: string) {
|
||||
return await prisma.task.findMany({
|
||||
where: { userId },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
}
|
||||
|
||||
async findById(id: string, userId: string) {
|
||||
const task = await prisma.task.findUnique({
|
||||
where: { id }
|
||||
})
|
||||
|
||||
if (!task) {
|
||||
throw new ApiError(404, 'Task not found')
|
||||
}
|
||||
|
||||
if (task.userId !== userId) {
|
||||
throw new ApiError(403, 'Access denied')
|
||||
}
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
async update(id: string, userId: string, data: Partial<{ title: string; description: string; completed: boolean }>) {
|
||||
await this.findById(id, userId) // Check ownership
|
||||
|
||||
return await prisma.task.update({
|
||||
where: { id },
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
async delete(id: string, userId: string) {
|
||||
await this.findById(id, userId) // Check ownership
|
||||
|
||||
await prisma.task.delete({
|
||||
where: { id }
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. **prisma/schema.prisma** (Database Schema)
|
||||
|
||||
```prisma
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
password String
|
||||
name String
|
||||
tasks Task[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model Task {
|
||||
id String @id @default(uuid())
|
||||
title String
|
||||
description String?
|
||||
completed Boolean @default(false)
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([userId])
|
||||
@@map("tasks")
|
||||
}
|
||||
```
|
||||
|
||||
### 9. **tests/task.test.ts** (Integration Tests)
|
||||
|
||||
```typescript
|
||||
import request from 'supertest'
|
||||
import app from '../src/app'
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
describe('Task API', () => {
|
||||
let authToken: string
|
||||
let userId: string
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create test user and get token
|
||||
const res = await request(app)
|
||||
.post('/api/auth/register')
|
||||
.send({
|
||||
email: '[email protected]',
|
||||
password: 'password123',
|
||||
name: 'Test User'
|
||||
})
|
||||
|
||||
authToken = res.body.data.token
|
||||
userId = res.body.data.user.id
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
// Cleanup
|
||||
await prisma.task.deleteMany({ where: { userId } })
|
||||
await prisma.user.delete({ where: { id: userId } })
|
||||
await prisma.$disconnect()
|
||||
})
|
||||
|
||||
describe('POST /api/tasks', () => {
|
||||
it('should create a new task', async () => {
|
||||
const res = await request(app)
|
||||
.post('/api/tasks')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({
|
||||
title: 'Test Task',
|
||||
description: 'Test description'
|
||||
})
|
||||
|
||||
expect(res.status).toBe(201)
|
||||
expect(res.body.data).toHaveProperty('id')
|
||||
expect(res.body.data.title).toBe('Test Task')
|
||||
})
|
||||
|
||||
it('should require authentication', async () => {
|
||||
const res = await request(app)
|
||||
.post('/api/tasks')
|
||||
.send({ title: 'Test' })
|
||||
|
||||
expect(res.status).toBe(401)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /api/tasks', () => {
|
||||
it('should list user tasks', async () => {
|
||||
const res = await request(app)
|
||||
.get('/api/tasks')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(Array.isArray(res.body.data)).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### 10. **package.json**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "task-api",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/server.js",
|
||||
"test": "jest --coverage",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"format": "prettier --write \"src/**/*.ts\"",
|
||||
"db:migrate": "prisma migrate dev",
|
||||
"db:push": "prisma db push",
|
||||
"db:generate": "prisma generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"cors": "^2.8.5",
|
||||
"helmet": "^7.1.0",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"morgan": "^1.10.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"zod": "^3.22.4",
|
||||
"@prisma/client": "^5.8.0",
|
||||
"dotenv": "^16.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.10.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/morgan": "^1.9.9",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/jsonwebtoken": "^9.0.5",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"typescript": "^5.3.3",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"supertest": "^6.3.3",
|
||||
"prisma": "^5.8.0",
|
||||
"eslint": "^8.56.0",
|
||||
"prettier": "^3.1.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
**Security:**
|
||||
- Helmet.js for HTTP headers
|
||||
- CORS with configurable origins
|
||||
- Rate limiting (100 req/15min)
|
||||
- JWT authentication
|
||||
- Password hashing (bcrypt)
|
||||
- Input validation (Zod)
|
||||
|
||||
**Database:**
|
||||
- Prisma ORM with TypeScript
|
||||
- Automatic migrations
|
||||
- Type-safe queries
|
||||
- Supports PostgreSQL, MySQL, SQLite
|
||||
|
||||
**Testing:**
|
||||
- Jest + Supertest
|
||||
- Integration tests
|
||||
- Coverage reporting
|
||||
- Test database isolation
|
||||
|
||||
**Development:**
|
||||
- Hot reload (ts-node-dev)
|
||||
- TypeScript with strict mode
|
||||
- ESLint + Prettier
|
||||
- Environment variables
|
||||
|
||||
**Production:**
|
||||
- Docker support
|
||||
- Health check endpoint
|
||||
- Error logging
|
||||
- Graceful shutdown
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
**1. Install dependencies:**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
**2. Configure environment:**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your database URL and secrets
|
||||
```
|
||||
|
||||
**3. Run database migrations:**
|
||||
```bash
|
||||
npm run db:migrate
|
||||
```
|
||||
|
||||
**4. Start development server:**
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
**5. Run tests:**
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- `/fastapi-scaffold` - Generate FastAPI boilerplate
|
||||
- Backend Architect (agent) - Architecture review
|
||||
- API Builder (agent) - API design guidance
|
||||
|
||||
---
|
||||
|
||||
**Build production-ready APIs. Ship faster. Scale confidently.**
|
||||
673
commands/fastapi-scaffold.md
Normal file
673
commands/fastapi-scaffold.md
Normal file
@@ -0,0 +1,673 @@
|
||||
---
|
||||
description: Generate production-ready FastAPI REST API with async and authentication
|
||||
shortcut: fas
|
||||
category: backend
|
||||
difficulty: intermediate
|
||||
estimated_time: 5-10 minutes
|
||||
---
|
||||
|
||||
# FastAPI Scaffold
|
||||
|
||||
Generates a complete FastAPI REST API boilerplate with async support, authentication, database integration, and testing setup.
|
||||
|
||||
## What This Command Does
|
||||
|
||||
**Generated Project:**
|
||||
- FastAPI with Python 3.10+
|
||||
- Async/await throughout
|
||||
- JWT authentication
|
||||
- Database integration (SQLAlchemy async)
|
||||
- Pydantic models & validation
|
||||
- Automatic OpenAPI docs
|
||||
- Testing setup (Pytest + httpx)
|
||||
- Docker configuration
|
||||
- Example CRUD endpoints
|
||||
|
||||
**Output:** Complete API project ready for development
|
||||
|
||||
**Time:** 5-10 minutes
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Generate full FastAPI API
|
||||
/fastapi-scaffold "Task Management API"
|
||||
|
||||
# Shortcut
|
||||
/fas "E-commerce API"
|
||||
|
||||
# With specific database
|
||||
/fas "Blog API" --database postgresql
|
||||
|
||||
# With authentication type
|
||||
/fas "Social API" --auth jwt --database postgresql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Output
|
||||
|
||||
**Input:**
|
||||
```
|
||||
/fas "Task Management API" --database postgresql
|
||||
```
|
||||
|
||||
**Generated Project Structure:**
|
||||
```
|
||||
task-api/
|
||||
├── app/
|
||||
│ ├── api/
|
||||
│ │ ├── deps.py # Dependencies
|
||||
│ │ └── v1/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── auth.py # Auth endpoints
|
||||
│ │ └── tasks.py # Task endpoints
|
||||
│ ├── core/
|
||||
│ │ ├── config.py # Settings
|
||||
│ │ ├── security.py # JWT, password hashing
|
||||
│ │ └── database.py # Database connection
|
||||
│ ├── models/ # SQLAlchemy models
|
||||
│ │ ├── user.py
|
||||
│ │ └── task.py
|
||||
│ ├── schemas/ # Pydantic schemas
|
||||
│ │ ├── user.py
|
||||
│ │ └── task.py
|
||||
│ ├── services/ # Business logic
|
||||
│ │ ├── auth.py
|
||||
│ │ └── task.py
|
||||
│ ├── db/
|
||||
│ │ └── init_db.py # Database initialization
|
||||
│ ├── main.py # FastAPI app
|
||||
│ └── __init__.py
|
||||
├── tests/
|
||||
│ ├── conftest.py
|
||||
│ ├── test_auth.py
|
||||
│ └── test_tasks.py
|
||||
├── alembic/ # Database migrations
|
||||
│ ├── versions/
|
||||
│ └── env.py
|
||||
├── .env.example
|
||||
├── .gitignore
|
||||
├── requirements.txt
|
||||
├── pyproject.toml
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Generated Files
|
||||
|
||||
### 1. **app/main.py** (Application Entry)
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
||||
|
||||
from app.api.v1 import auth, tasks
|
||||
from app.core.config import settings
|
||||
from app.core.database import engine
|
||||
from app.models import Base
|
||||
|
||||
# Create database tables
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.PROJECT_NAME,
|
||||
version="1.0.0",
|
||||
openapi_url=f"{settings.API_V1_STR}/openapi.json",
|
||||
docs_url=f"{settings.API_V1_STR}/docs",
|
||||
)
|
||||
|
||||
# CORS middleware
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.ALLOWED_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Security middleware
|
||||
app.add_middleware(
|
||||
TrustedHostMiddleware,
|
||||
allowed_hosts=settings.ALLOWED_HOSTS
|
||||
)
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
return {
|
||||
"status": "ok",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
||||
# Include routers
|
||||
app.include_router(auth.router, prefix=f"{settings.API_V1_STR}/auth", tags=["auth"])
|
||||
app.include_router(tasks.router, prefix=f"{settings.API_V1_STR}/tasks", tags=["tasks"])
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
```
|
||||
|
||||
### 2. **app/core/config.py** (Settings)
|
||||
|
||||
```python
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import List
|
||||
|
||||
class Settings(BaseSettings):
|
||||
PROJECT_NAME: str = "Task API"
|
||||
API_V1_STR: str = "/api/v1"
|
||||
|
||||
# Database
|
||||
DATABASE_URL: str
|
||||
|
||||
# Security
|
||||
SECRET_KEY: str
|
||||
ALGORITHM: str = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 days
|
||||
|
||||
# CORS
|
||||
ALLOWED_ORIGINS: List[str] = ["http://localhost:3000"]
|
||||
ALLOWED_HOSTS: List[str] = ["*"]
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = True
|
||||
|
||||
settings = Settings()
|
||||
```
|
||||
|
||||
### 3. **app/core/security.py** (Authentication)
|
||||
|
||||
```python
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from jose import jwt, JWTError
|
||||
from passlib.context import CryptContext
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/auth/login")
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
return pwd_context.hash(password)
|
||||
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
||||
to_encode = data.copy()
|
||||
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(
|
||||
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(
|
||||
to_encode,
|
||||
settings.SECRET_KEY,
|
||||
algorithm=settings.ALGORITHM
|
||||
)
|
||||
return encoded_jwt
|
||||
|
||||
def decode_access_token(token: str) -> dict:
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
settings.SECRET_KEY,
|
||||
algorithms=[settings.ALGORITHM]
|
||||
)
|
||||
return payload
|
||||
except JWTError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
```
|
||||
|
||||
### 4. **app/core/database.py** (Database Setup)
|
||||
|
||||
```python
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.core.config import settings
|
||||
|
||||
engine = create_engine(
|
||||
settings.DATABASE_URL,
|
||||
pool_pre_ping=True,
|
||||
echo=False
|
||||
)
|
||||
|
||||
SessionLocal = sessionmaker(
|
||||
autocommit=False,
|
||||
autoflush=False,
|
||||
bind=engine
|
||||
)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
```
|
||||
|
||||
### 5. **app/models/user.py** (User Model)
|
||||
|
||||
```python
|
||||
from sqlalchemy import Column, String, DateTime
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
email = Column(String, unique=True, index=True, nullable=False)
|
||||
hashed_password = Column(String, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
tasks = relationship("Task", back_populates="owner", cascade="all, delete-orphan")
|
||||
```
|
||||
|
||||
### 6. **app/models/task.py** (Task Model)
|
||||
|
||||
```python
|
||||
from sqlalchemy import Column, String, Boolean, DateTime, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
class Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
|
||||
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
title = Column(String, nullable=False)
|
||||
description = Column(String, nullable=True)
|
||||
completed = Column(Boolean, default=False)
|
||||
user_id = Column(String, ForeignKey("users.id", ondelete="CASCADE"))
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
# Relationships
|
||||
owner = relationship("User", back_populates="tasks")
|
||||
```
|
||||
|
||||
### 7. **app/schemas/user.py** (Pydantic Schemas)
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
class UserBase(BaseModel):
|
||||
email: EmailStr
|
||||
name: str
|
||||
|
||||
class UserCreate(UserBase):
|
||||
password: str
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
email: Optional[EmailStr] = None
|
||||
|
||||
class UserInDB(UserBase):
|
||||
id: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class User(UserInDB):
|
||||
pass
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
class TokenData(BaseModel):
|
||||
email: Optional[str] = None
|
||||
```
|
||||
|
||||
### 8. **app/schemas/task.py** (Task Schemas)
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
class TaskBase(BaseModel):
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
|
||||
class TaskCreate(TaskBase):
|
||||
pass
|
||||
|
||||
class TaskUpdate(BaseModel):
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
completed: Optional[bool] = None
|
||||
|
||||
class TaskInDB(TaskBase):
|
||||
id: str
|
||||
completed: bool
|
||||
user_id: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class Task(TaskInDB):
|
||||
pass
|
||||
```
|
||||
|
||||
### 9. **app/api/deps.py** (Dependencies)
|
||||
|
||||
```python
|
||||
from typing import Generator
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import oauth2_scheme, decode_access_token
|
||||
from app.models.user import User
|
||||
|
||||
def get_current_user(
|
||||
db: Session = Depends(get_db),
|
||||
token: str = Depends(oauth2_scheme)
|
||||
) -> User:
|
||||
payload = decode_access_token(token)
|
||||
email: str = payload.get("sub")
|
||||
|
||||
if email is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials"
|
||||
)
|
||||
|
||||
user = db.query(User).filter(User.email == email).first()
|
||||
|
||||
if user is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="User not found"
|
||||
)
|
||||
|
||||
return user
|
||||
```
|
||||
|
||||
### 10. **app/api/v1/tasks.py** (Task Endpoints)
|
||||
|
||||
```python
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_user
|
||||
from app.core.database import get_db
|
||||
from app.models.user import User
|
||||
from app.models.task import Task as TaskModel
|
||||
from app.schemas.task import Task, TaskCreate, TaskUpdate
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/", response_model=List[Task])
|
||||
async def list_tasks(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
skip: int = 0,
|
||||
limit: int = 100
|
||||
):
|
||||
tasks = db.query(TaskModel)\
|
||||
.filter(TaskModel.user_id == current_user.id)\
|
||||
.offset(skip)\
|
||||
.limit(limit)\
|
||||
.all()
|
||||
return tasks
|
||||
|
||||
@router.post("/", response_model=Task, status_code=status.HTTP_201_CREATED)
|
||||
async def create_task(
|
||||
task_in: TaskCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
task = TaskModel(
|
||||
**task_in.dict(),
|
||||
user_id=current_user.id
|
||||
)
|
||||
db.add(task)
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
return task
|
||||
|
||||
@router.get("/{task_id}", response_model=Task)
|
||||
async def get_task(
|
||||
task_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
task = db.query(TaskModel)\
|
||||
.filter(TaskModel.id == task_id, TaskModel.user_id == current_user.id)\
|
||||
.first()
|
||||
|
||||
if not task:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found"
|
||||
)
|
||||
|
||||
return task
|
||||
|
||||
@router.patch("/{task_id}", response_model=Task)
|
||||
async def update_task(
|
||||
task_id: str,
|
||||
task_in: TaskUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
task = db.query(TaskModel)\
|
||||
.filter(TaskModel.id == task_id, TaskModel.user_id == current_user.id)\
|
||||
.first()
|
||||
|
||||
if not task:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found"
|
||||
)
|
||||
|
||||
for field, value in task_in.dict(exclude_unset=True).items():
|
||||
setattr(task, field, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
return task
|
||||
|
||||
@router.delete("/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_task(
|
||||
task_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
task = db.query(TaskModel)\
|
||||
.filter(TaskModel.id == task_id, TaskModel.user_id == current_user.id)\
|
||||
.first()
|
||||
|
||||
if not task:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found"
|
||||
)
|
||||
|
||||
db.delete(task)
|
||||
db.commit()
|
||||
```
|
||||
|
||||
### 11. **tests/test_tasks.py** (Pytest Tests)
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
from app.main import app
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_task(client: AsyncClient, test_user_token):
|
||||
response = await client.post(
|
||||
"/api/v1/tasks/",
|
||||
json={
|
||||
"title": "Test Task",
|
||||
"description": "Test description"
|
||||
},
|
||||
headers={"Authorization": f"Bearer {test_user_token}"}
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["title"] == "Test Task"
|
||||
assert "id" in data
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_tasks(client: AsyncClient, test_user_token):
|
||||
response = await client.get(
|
||||
"/api/v1/tasks/",
|
||||
headers={"Authorization": f"Bearer {test_user_token}"}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert isinstance(data, list)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_task_unauthorized(client: AsyncClient):
|
||||
response = await client.post(
|
||||
"/api/v1/tasks/",
|
||||
json={"title": "Test"}
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
```
|
||||
|
||||
### 12. **requirements.txt**
|
||||
|
||||
```
|
||||
fastapi==0.109.0
|
||||
uvicorn[standard]==0.27.0
|
||||
sqlalchemy==2.0.25
|
||||
pydantic==2.5.3
|
||||
pydantic-settings==2.1.0
|
||||
python-jose[cryptography]==3.3.0
|
||||
passlib[bcrypt]==1.7.4
|
||||
python-multipart==0.0.6
|
||||
alembic==1.13.1
|
||||
psycopg2-binary==2.9.9
|
||||
|
||||
# Development
|
||||
pytest==7.4.4
|
||||
pytest-asyncio==0.23.3
|
||||
httpx==0.26.0
|
||||
black==23.12.1
|
||||
isort==5.13.2
|
||||
mypy==1.8.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
**Performance:**
|
||||
- Async/await for high concurrency
|
||||
- Background tasks support
|
||||
- WebSocket support (optional)
|
||||
- Automatic Pydantic validation
|
||||
|
||||
**Documentation:**
|
||||
- Auto-generated OpenAPI (Swagger)
|
||||
- ReDoc documentation
|
||||
- Type hints throughout
|
||||
|
||||
**Database:**
|
||||
- SQLAlchemy ORM with async support
|
||||
- Alembic migrations
|
||||
- Connection pooling
|
||||
|
||||
**Security:**
|
||||
- JWT authentication
|
||||
- Password hashing (bcrypt)
|
||||
- CORS middleware
|
||||
- Trusted host middleware
|
||||
|
||||
**Testing:**
|
||||
- Pytest with async support
|
||||
- Test fixtures
|
||||
- Coverage reporting
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
**1. Install dependencies:**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**2. Configure environment:**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your database URL and secrets
|
||||
```
|
||||
|
||||
**3. Run database migrations:**
|
||||
```bash
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
**4. Start development server:**
|
||||
```bash
|
||||
uvicorn app.main:app --reload
|
||||
```
|
||||
|
||||
**5. View API docs:**
|
||||
- Swagger UI: http://localhost:8000/api/v1/docs
|
||||
- ReDoc: http://localhost:8000/api/v1/redoc
|
||||
|
||||
**6. Run tests:**
|
||||
```bash
|
||||
pytest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- `/express-api-scaffold` - Generate Express.js boilerplate
|
||||
- Backend Architect (agent) - Architecture review
|
||||
- API Builder (agent) - API design guidance
|
||||
|
||||
---
|
||||
|
||||
**Build high-performance APIs. Scale effortlessly. Deploy confidently.**
|
||||
582
commands/prisma-schema-gen.md
Normal file
582
commands/prisma-schema-gen.md
Normal file
@@ -0,0 +1,582 @@
|
||||
---
|
||||
description: Generate Prisma schema from natural language descriptions
|
||||
shortcut: psg
|
||||
category: database
|
||||
difficulty: beginner
|
||||
estimated_time: 3-5 minutes
|
||||
---
|
||||
|
||||
# Prisma Schema Generator
|
||||
|
||||
Generates complete Prisma schema files from natural language descriptions, including models, relationships, indexes, and validation.
|
||||
|
||||
## What This Command Does
|
||||
|
||||
**Generated Schema:**
|
||||
- Prisma models with fields
|
||||
- Relationships (one-to-one, one-to-many, many-to-many)
|
||||
- Indexes and unique constraints
|
||||
- Default values and validators
|
||||
- Database-specific configuration
|
||||
- Example queries
|
||||
|
||||
**Output:** Complete `schema.prisma` file ready for migration
|
||||
|
||||
**Time:** 3-5 minutes
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Generate schema from description
|
||||
/prisma-schema-gen "Blog with users, posts, and comments"
|
||||
|
||||
# Shortcut
|
||||
/psg "E-commerce with products, orders, and customers"
|
||||
|
||||
# Specify database
|
||||
/psg "Task management app" --database postgresql
|
||||
|
||||
# With specific relationships
|
||||
/psg "Social network with users, posts, likes, and follows"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Output
|
||||
|
||||
**Input:**
|
||||
```
|
||||
/psg "Blog platform with users, posts, comments, and tags"
|
||||
```
|
||||
|
||||
**Generated schema.prisma:**
|
||||
|
||||
```prisma
|
||||
// This is your Prisma schema file
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
name String
|
||||
password String
|
||||
bio String?
|
||||
avatar String?
|
||||
posts Post[]
|
||||
comments Comment[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([email])
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model Post {
|
||||
id String @id @default(uuid())
|
||||
title String
|
||||
slug String @unique
|
||||
content String @db.Text
|
||||
excerpt String?
|
||||
published Boolean @default(false)
|
||||
publishedAt DateTime?
|
||||
viewCount Int @default(0)
|
||||
authorId String
|
||||
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
||||
comments Comment[]
|
||||
tags PostTag[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([authorId])
|
||||
@@index([slug])
|
||||
@@index([published, publishedAt])
|
||||
@@map("posts")
|
||||
}
|
||||
|
||||
model Comment {
|
||||
id String @id @default(uuid())
|
||||
content String @db.Text
|
||||
postId String
|
||||
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
||||
authorId String
|
||||
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([postId])
|
||||
@@index([authorId])
|
||||
@@map("comments")
|
||||
}
|
||||
|
||||
model Tag {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
slug String @unique
|
||||
posts PostTag[]
|
||||
|
||||
@@index([slug])
|
||||
@@map("tags")
|
||||
}
|
||||
|
||||
// Junction table for many-to-many
|
||||
model PostTag {
|
||||
id String @id @default(uuid())
|
||||
postId String
|
||||
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
||||
tagId String
|
||||
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([postId, tagId])
|
||||
@@index([postId])
|
||||
@@index([tagId])
|
||||
@@map("post_tags")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Generated Files
|
||||
|
||||
### **Migrations**
|
||||
|
||||
```bash
|
||||
# After generating schema, run:
|
||||
npx prisma migrate dev --name init
|
||||
|
||||
# This creates:
|
||||
# - migrations/
|
||||
# └── 20250110000000_init/
|
||||
# └── migration.sql
|
||||
```
|
||||
|
||||
### **Example Queries (TypeScript)**
|
||||
|
||||
```typescript
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
// Create user
|
||||
async function createUser() {
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: '[email protected]',
|
||||
name: 'John Doe',
|
||||
password: 'hashed_password_here'
|
||||
}
|
||||
})
|
||||
return user
|
||||
}
|
||||
|
||||
// Create post with tags
|
||||
async function createPost() {
|
||||
const post = await prisma.post.create({
|
||||
data: {
|
||||
title: 'Getting Started with Prisma',
|
||||
slug: 'getting-started-with-prisma',
|
||||
content: 'Full blog post content...',
|
||||
published: true,
|
||||
publishedAt: new Date(),
|
||||
authorId: 'user-uuid-here',
|
||||
tags: {
|
||||
create: [
|
||||
{
|
||||
tag: {
|
||||
connectOrCreate: {
|
||||
where: { slug: 'prisma' },
|
||||
create: { name: 'Prisma', slug: 'prisma' }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
include: {
|
||||
author: true,
|
||||
tags: {
|
||||
include: {
|
||||
tag: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return post
|
||||
}
|
||||
|
||||
// Get posts with related data
|
||||
async function getPosts() {
|
||||
const posts = await prisma.post.findMany({
|
||||
where: {
|
||||
published: true
|
||||
},
|
||||
include: {
|
||||
author: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
avatar: true
|
||||
}
|
||||
},
|
||||
tags: {
|
||||
include: {
|
||||
tag: true
|
||||
}
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
comments: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
publishedAt: 'desc'
|
||||
},
|
||||
take: 10
|
||||
})
|
||||
return posts
|
||||
}
|
||||
|
||||
// Create comment
|
||||
async function createComment() {
|
||||
const comment = await prisma.comment.create({
|
||||
data: {
|
||||
content: 'Great article!',
|
||||
postId: 'post-uuid-here',
|
||||
authorId: 'user-uuid-here'
|
||||
},
|
||||
include: {
|
||||
author: {
|
||||
select: {
|
||||
name: true,
|
||||
avatar: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return comment
|
||||
}
|
||||
|
||||
// Search posts
|
||||
async function searchPosts(query: string) {
|
||||
const posts = await prisma.post.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ title: { contains: query, mode: 'insensitive' } },
|
||||
{ content: { contains: query, mode: 'insensitive' } }
|
||||
],
|
||||
published: true
|
||||
},
|
||||
include: {
|
||||
author: true
|
||||
}
|
||||
})
|
||||
return posts
|
||||
}
|
||||
|
||||
// Get post with comments
|
||||
async function getPostWithComments(slug: string) {
|
||||
const post = await prisma.post.findUnique({
|
||||
where: { slug },
|
||||
include: {
|
||||
author: true,
|
||||
comments: {
|
||||
include: {
|
||||
author: {
|
||||
select: {
|
||||
name: true,
|
||||
avatar: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
}
|
||||
},
|
||||
tags: {
|
||||
include: {
|
||||
tag: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!post) {
|
||||
throw new Error('Post not found')
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
await prisma.post.update({
|
||||
where: { id: post.id },
|
||||
data: { viewCount: { increment: 1 } }
|
||||
})
|
||||
|
||||
return post
|
||||
}
|
||||
|
||||
// Get posts by tag
|
||||
async function getPostsByTag(tagSlug: string) {
|
||||
const posts = await prisma.post.findMany({
|
||||
where: {
|
||||
published: true,
|
||||
tags: {
|
||||
some: {
|
||||
tag: {
|
||||
slug: tagSlug
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
include: {
|
||||
author: true,
|
||||
tags: {
|
||||
include: {
|
||||
tag: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
publishedAt: 'desc'
|
||||
}
|
||||
})
|
||||
return posts
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### **1. E-commerce Schema**
|
||||
|
||||
```prisma
|
||||
model Customer {
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
name String
|
||||
phone String?
|
||||
orders Order[]
|
||||
cart Cart?
|
||||
}
|
||||
|
||||
model Product {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
description String?
|
||||
price Decimal @db.Decimal(10, 2)
|
||||
stock Int @default(0)
|
||||
orderItems OrderItem[]
|
||||
cartItems CartItem[]
|
||||
}
|
||||
|
||||
model Order {
|
||||
id String @id @default(uuid())
|
||||
customerId String
|
||||
customer Customer @relation(fields: [customerId], references: [id])
|
||||
items OrderItem[]
|
||||
total Decimal @db.Decimal(10, 2)
|
||||
status String // 'pending', 'paid', 'shipped', 'delivered'
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model OrderItem {
|
||||
id String @id @default(uuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id])
|
||||
productId String
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
quantity Int
|
||||
price Decimal @db.Decimal(10, 2)
|
||||
}
|
||||
```
|
||||
|
||||
### **2. Social Network Schema**
|
||||
|
||||
```prisma
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
username String @unique
|
||||
email String @unique
|
||||
posts Post[]
|
||||
likes Like[]
|
||||
following Follow[] @relation("Following")
|
||||
followers Follow[] @relation("Followers")
|
||||
}
|
||||
|
||||
model Post {
|
||||
id String @id @default(uuid())
|
||||
content String
|
||||
authorId String
|
||||
author User @relation(fields: [authorId], references: [id])
|
||||
likes Like[]
|
||||
createdAt DateTime @default(now())
|
||||
}
|
||||
|
||||
model Like {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
postId String
|
||||
post Post @relation(fields: [postId], references: [id])
|
||||
|
||||
@@unique([userId, postId])
|
||||
}
|
||||
|
||||
model Follow {
|
||||
id String @id @default(uuid())
|
||||
followerId String
|
||||
followingId String
|
||||
follower User @relation("Following", fields: [followerId], references: [id])
|
||||
following User @relation("Followers", fields: [followingId], references: [id])
|
||||
|
||||
@@unique([followerId, followingId])
|
||||
}
|
||||
```
|
||||
|
||||
### **3. Multi-tenant SaaS Schema**
|
||||
|
||||
```prisma
|
||||
model Organization {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
slug String @unique
|
||||
members Member[]
|
||||
projects Project[]
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
memberships Member[]
|
||||
}
|
||||
|
||||
model Member {
|
||||
id String @id @default(uuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id])
|
||||
role String // 'owner', 'admin', 'member'
|
||||
|
||||
@@unique([userId, orgId])
|
||||
}
|
||||
|
||||
model Project {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
orgId String
|
||||
org Organization @relation(fields: [orgId], references: [id])
|
||||
tasks Task[]
|
||||
}
|
||||
|
||||
model Task {
|
||||
id String @id @default(uuid())
|
||||
title String
|
||||
completed Boolean @default(false)
|
||||
projectId String
|
||||
project Project @relation(fields: [projectId], references: [id])
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Support
|
||||
|
||||
**PostgreSQL:**
|
||||
```prisma
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// PostgreSQL-specific types
|
||||
model Example {
|
||||
jsonData Json
|
||||
textData String @db.Text
|
||||
amount Decimal @db.Decimal(10, 2)
|
||||
}
|
||||
```
|
||||
|
||||
**MySQL:**
|
||||
```prisma
|
||||
datasource db {
|
||||
provider = "mysql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
```
|
||||
|
||||
**SQLite (Development):**
|
||||
```prisma
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = "file:./dev.db"
|
||||
}
|
||||
```
|
||||
|
||||
**MongoDB:**
|
||||
```prisma
|
||||
datasource db {
|
||||
provider = "mongodb"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
email String @unique
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
**1. Install Prisma:**
|
||||
```bash
|
||||
npm install @prisma/client
|
||||
npm install -D prisma
|
||||
```
|
||||
|
||||
**2. Initialize Prisma:**
|
||||
```bash
|
||||
npx prisma init
|
||||
```
|
||||
|
||||
**3. Use generated schema:**
|
||||
- Replace `prisma/schema.prisma` with generated content
|
||||
- Set `DATABASE_URL` in `.env`
|
||||
|
||||
**4. Create migration:**
|
||||
```bash
|
||||
npx prisma migrate dev --name init
|
||||
```
|
||||
|
||||
**5. Generate Prisma Client:**
|
||||
```bash
|
||||
npx prisma generate
|
||||
```
|
||||
|
||||
**6. Use in code:**
|
||||
```typescript
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
const prisma = new PrismaClient()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- `/sql-query-builder` - Generate SQL queries
|
||||
- Database Designer (agent) - Schema design review
|
||||
|
||||
---
|
||||
|
||||
**Generate schemas fast. Migrate safely. Query confidently.** ️
|
||||
354
commands/project-scaffold.md
Normal file
354
commands/project-scaffold.md
Normal file
@@ -0,0 +1,354 @@
|
||||
---
|
||||
description: Generate complete fullstack project structure with all boilerplate
|
||||
shortcut: ps
|
||||
category: devops
|
||||
difficulty: beginner
|
||||
estimated_time: 5-10 minutes
|
||||
---
|
||||
|
||||
# Project Scaffold
|
||||
|
||||
Generates a complete fullstack project structure with frontend, backend, database, authentication, testing, and deployment configuration.
|
||||
|
||||
## What This Command Does
|
||||
|
||||
**Generated Project:**
|
||||
- Frontend (React + TypeScript + Vite)
|
||||
- Backend (Express or FastAPI)
|
||||
- Database (PostgreSQL + Prisma/SQLAlchemy)
|
||||
- Authentication (JWT + OAuth)
|
||||
- Testing (Jest/Pytest + E2E)
|
||||
- CI/CD (GitHub Actions)
|
||||
- Docker setup
|
||||
- Documentation
|
||||
|
||||
**Output:** Production-ready fullstack application
|
||||
|
||||
**Time:** 5-10 minutes
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Generate fullstack project
|
||||
/project-scaffold "Task Management App"
|
||||
|
||||
# Shortcut
|
||||
/ps "E-commerce Platform" --stack react,express,postgresql
|
||||
|
||||
# With specific features
|
||||
/ps "Blog Platform" --features auth,admin,payments,analytics
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Generated Structure
|
||||
|
||||
```
|
||||
my-app/
|
||||
├── client/ # Frontend (React + TypeScript + Vite)
|
||||
│ ├── src/
|
||||
│ │ ├── components/ # React components
|
||||
│ │ ├── pages/ # Page components
|
||||
│ │ ├── hooks/ # Custom hooks
|
||||
│ │ ├── services/ # API services
|
||||
│ │ ├── context/ # Context providers
|
||||
│ │ ├── utils/ # Utilities
|
||||
│ │ ├── types/ # TypeScript types
|
||||
│ │ ├── App.tsx
|
||||
│ │ └── main.tsx
|
||||
│ ├── public/
|
||||
│ ├── index.html
|
||||
│ ├── package.json
|
||||
│ ├── vite.config.ts
|
||||
│ ├── tsconfig.json
|
||||
│ └── tailwind.config.js
|
||||
│
|
||||
├── server/ # Backend (Express + TypeScript)
|
||||
│ ├── src/
|
||||
│ │ ├── controllers/ # Request handlers
|
||||
│ │ ├── services/ # Business logic
|
||||
│ │ ├── models/ # Database models
|
||||
│ │ ├── routes/ # API routes
|
||||
│ │ ├── middleware/ # Express middleware
|
||||
│ │ ├── utils/ # Utilities
|
||||
│ │ ├── config/ # Configuration
|
||||
│ │ ├── app.ts
|
||||
│ │ └── server.ts
|
||||
│ ├── tests/
|
||||
│ ├── prisma/
|
||||
│ │ └── schema.prisma
|
||||
│ ├── package.json
|
||||
│ ├── tsconfig.json
|
||||
│ └── jest.config.js
|
||||
│
|
||||
├── .github/
|
||||
│ └── workflows/
|
||||
│ ├── ci.yml # Continuous Integration
|
||||
│ └── deploy.yml # Deployment
|
||||
│
|
||||
├── docker-compose.yml # Development environment
|
||||
├── Dockerfile # Production container
|
||||
├── .env.example # Environment template
|
||||
├── .gitignore
|
||||
├── README.md
|
||||
└── package.json # Root workspace
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example: Task Management App
|
||||
|
||||
**Frontend (client/src/pages/Dashboard.tsx):**
|
||||
```tsx
|
||||
import { useState, useEffect } from 'react'
|
||||
import { TaskList } from '../components/TaskList'
|
||||
import { CreateTaskForm } from '../components/CreateTaskForm'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { taskService } from '../services/api'
|
||||
|
||||
export function Dashboard() {
|
||||
const { user } = useAuth()
|
||||
const [tasks, setTasks] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
loadTasks()
|
||||
}, [])
|
||||
|
||||
async function loadTasks() {
|
||||
try {
|
||||
const data = await taskService.getAll()
|
||||
setTasks(data)
|
||||
} catch (error) {
|
||||
console.error('Failed to load tasks:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreateTask(task: CreateTaskInput) {
|
||||
const newTask = await taskService.create(task)
|
||||
setTasks([newTask, ...tasks])
|
||||
}
|
||||
|
||||
async function handleToggleTask(id: string) {
|
||||
const updated = await taskService.toggle(id)
|
||||
setTasks(tasks.map(t => t.id === id ? updated : t))
|
||||
}
|
||||
|
||||
if (loading) return <div>Loading...</div>
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-4">
|
||||
<h1 className="text-3xl font-bold mb-6">
|
||||
Welcome, {user?.name}
|
||||
</h1>
|
||||
|
||||
<CreateTaskForm onSubmit={handleCreateTask} />
|
||||
|
||||
<TaskList
|
||||
tasks={tasks}
|
||||
onToggle={handleToggleTask}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Backend (server/src/controllers/task.controller.ts):**
|
||||
```typescript
|
||||
import { Request, Response } from 'express'
|
||||
import { TaskService } from '../services/task.service'
|
||||
|
||||
const taskService = new TaskService()
|
||||
|
||||
export class TaskController {
|
||||
async getAll(req: Request, res: Response) {
|
||||
const tasks = await taskService.findAll(req.user!.userId)
|
||||
res.json({ data: tasks })
|
||||
}
|
||||
|
||||
async create(req: Request, res: Response) {
|
||||
const task = await taskService.create(req.user!.userId, req.body)
|
||||
res.status(201).json({ data: task })
|
||||
}
|
||||
|
||||
async toggle(req: Request, res: Response) {
|
||||
const task = await taskService.toggle(req.params.id, req.user!.userId)
|
||||
res.json({ data: task })
|
||||
}
|
||||
|
||||
async delete(req: Request, res: Response) {
|
||||
await taskService.delete(req.params.id, req.user!.userId)
|
||||
res.status(204).send()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
**1. Install dependencies:**
|
||||
```bash
|
||||
# Install all dependencies (client + server)
|
||||
npm install
|
||||
|
||||
# Or individually
|
||||
cd client && npm install
|
||||
cd server && npm install
|
||||
```
|
||||
|
||||
**2. Setup environment:**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your configuration
|
||||
```
|
||||
|
||||
**3. Setup database:**
|
||||
```bash
|
||||
cd server
|
||||
npx prisma migrate dev
|
||||
npx prisma generate
|
||||
```
|
||||
|
||||
**4. Start development:**
|
||||
```bash
|
||||
# Start all services (client, server, database)
|
||||
docker-compose up
|
||||
|
||||
# Or start individually
|
||||
npm run dev:client # Frontend on http://localhost:5173
|
||||
npm run dev:server # Backend on http://localhost:3000
|
||||
```
|
||||
|
||||
**5. Run tests:**
|
||||
```bash
|
||||
npm run test # All tests
|
||||
npm run test:client # Frontend tests
|
||||
npm run test:server # Backend tests
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stack Options
|
||||
|
||||
**Frontend:**
|
||||
- React + TypeScript + Vite (default)
|
||||
- Next.js 14 (App Router)
|
||||
- Vue 3 + TypeScript
|
||||
|
||||
**Backend:**
|
||||
- Express + TypeScript (default)
|
||||
- FastAPI + Python
|
||||
- NestJS
|
||||
|
||||
**Database:**
|
||||
- PostgreSQL + Prisma (default)
|
||||
- MongoDB + Mongoose
|
||||
- MySQL + TypeORM
|
||||
|
||||
**Styling:**
|
||||
- Tailwind CSS (default)
|
||||
- CSS Modules
|
||||
- Styled Components
|
||||
|
||||
---
|
||||
|
||||
## Included Features
|
||||
|
||||
**Authentication:**
|
||||
- JWT authentication
|
||||
- OAuth (Google, GitHub)
|
||||
- Email verification
|
||||
- Password reset
|
||||
|
||||
**Testing:**
|
||||
- Frontend: Jest + React Testing Library + Cypress
|
||||
- Backend: Jest + Supertest
|
||||
- E2E: Playwright
|
||||
|
||||
**CI/CD:**
|
||||
- GitHub Actions workflows
|
||||
- Automated testing
|
||||
- Docker build and push
|
||||
- Deployment to cloud platforms
|
||||
|
||||
**Development:**
|
||||
- Hot reload (frontend + backend)
|
||||
- Docker development environment
|
||||
- Database migrations
|
||||
- Seed data
|
||||
|
||||
**Production:**
|
||||
- Optimized Docker images
|
||||
- Health checks
|
||||
- Logging and monitoring
|
||||
- Environment-based config
|
||||
|
||||
---
|
||||
|
||||
## Customization
|
||||
|
||||
**Add Features:**
|
||||
```bash
|
||||
# Add payment processing
|
||||
/ps --add-feature payments --provider stripe
|
||||
|
||||
# Add file uploads
|
||||
/ps --add-feature uploads --storage s3
|
||||
|
||||
# Add email service
|
||||
/ps --add-feature email --provider sendgrid
|
||||
|
||||
# Add admin dashboard
|
||||
/ps --add-feature admin
|
||||
```
|
||||
|
||||
**Change Stack:**
|
||||
```bash
|
||||
# Use Next.js instead of React
|
||||
/ps --frontend nextjs
|
||||
|
||||
# Use FastAPI instead of Express
|
||||
/ps --backend fastapi
|
||||
|
||||
# Use MongoDB instead of PostgreSQL
|
||||
/ps --database mongodb
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
**Vercel (Frontend):**
|
||||
```bash
|
||||
cd client
|
||||
vercel
|
||||
```
|
||||
|
||||
**Railway (Backend):**
|
||||
```bash
|
||||
cd server
|
||||
railway up
|
||||
```
|
||||
|
||||
**Docker (Full Stack):**
|
||||
```bash
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- `/express-api-scaffold` - Generate Express API
|
||||
- `/fastapi-scaffold` - Generate FastAPI
|
||||
- `/auth-setup` - Authentication boilerplate
|
||||
- `/env-config-setup` - Environment configuration
|
||||
|
||||
---
|
||||
|
||||
**Start building immediately. Ship faster. Scale effortlessly.**
|
||||
460
commands/sql-query-builder.md
Normal file
460
commands/sql-query-builder.md
Normal file
@@ -0,0 +1,460 @@
|
||||
---
|
||||
description: Generate optimized SQL queries from natural language descriptions
|
||||
shortcut: sqb
|
||||
category: database
|
||||
difficulty: beginner
|
||||
estimated_time: 2-3 minutes
|
||||
---
|
||||
|
||||
# SQL Query Builder
|
||||
|
||||
Generates optimized SQL queries from natural language descriptions, supporting SELECT, INSERT, UPDATE, DELETE with JOINs, indexes, and best practices.
|
||||
|
||||
## What This Command Does
|
||||
|
||||
**Generated Queries:**
|
||||
- SELECT queries with JOINs
|
||||
- INSERT, UPDATE, DELETE operations
|
||||
- Aggregations and GROUP BY
|
||||
- Subqueries and CTEs
|
||||
- Indexes and optimization tips
|
||||
- PostgreSQL, MySQL, SQLite syntax
|
||||
|
||||
**Output:** Production-ready SQL queries
|
||||
|
||||
**Time:** 2-3 minutes
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Generate query from description
|
||||
/sql-query-builder "Get all users with their posts"
|
||||
|
||||
# Shortcut
|
||||
/sqb "Find top 10 products by sales"
|
||||
|
||||
# Complex query
|
||||
/sqb "Get users who have made purchases in last 30 days, grouped by country"
|
||||
|
||||
# Specific database
|
||||
/sqb "List orders with customer info" --database postgresql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Output
|
||||
|
||||
**Input:**
|
||||
```
|
||||
/sqb "Get all blog posts with author info and comment count, ordered by recent"
|
||||
```
|
||||
|
||||
**Generated SQL:**
|
||||
|
||||
```sql
|
||||
-- Query: Get all blog posts with author info and comment count
|
||||
-- Database: PostgreSQL
|
||||
-- Optimized for performance with proper indexes
|
||||
|
||||
-- Main query
|
||||
SELECT
|
||||
p.id,
|
||||
p.title,
|
||||
p.slug,
|
||||
p.content,
|
||||
p.published,
|
||||
p.created_at,
|
||||
p.updated_at,
|
||||
-- Author information
|
||||
u.id AS author_id,
|
||||
u.name AS author_name,
|
||||
u.email AS author_email,
|
||||
-- Comment count (calculated)
|
||||
COUNT(c.id) AS comment_count
|
||||
FROM posts p
|
||||
INNER JOIN users u ON p.author_id = u.id
|
||||
LEFT JOIN comments c ON p.id = c.post_id
|
||||
WHERE p.published = true
|
||||
GROUP BY
|
||||
p.id,
|
||||
p.title,
|
||||
p.slug,
|
||||
p.content,
|
||||
p.published,
|
||||
p.created_at,
|
||||
p.updated_at,
|
||||
u.id,
|
||||
u.name,
|
||||
u.email
|
||||
ORDER BY p.created_at DESC
|
||||
LIMIT 20 OFFSET 0;
|
||||
|
||||
-- Recommended indexes for optimal performance
|
||||
CREATE INDEX IF NOT EXISTS idx_posts_published_created ON posts(published, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_posts_author_id ON posts(author_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_comments_post_id ON comments(post_id);
|
||||
|
||||
-- Explain plan (run to verify performance)
|
||||
EXPLAIN ANALYZE
|
||||
SELECT /* ... query above ... */;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Query Examples
|
||||
|
||||
### **1. Simple SELECT**
|
||||
|
||||
**Request:** "Get all active users"
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id,
|
||||
email,
|
||||
name,
|
||||
created_at
|
||||
FROM users
|
||||
WHERE active = true
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- Index recommendation
|
||||
CREATE INDEX idx_users_active ON users(active, created_at DESC);
|
||||
```
|
||||
|
||||
### **2. JOIN Queries**
|
||||
|
||||
**Request:** "Get orders with customer and product information"
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
o.id AS order_id,
|
||||
o.order_date,
|
||||
o.total,
|
||||
o.status,
|
||||
-- Customer info
|
||||
c.id AS customer_id,
|
||||
c.name AS customer_name,
|
||||
c.email AS customer_email,
|
||||
-- Order items
|
||||
oi.quantity,
|
||||
oi.price AS unit_price,
|
||||
-- Product info
|
||||
p.id AS product_id,
|
||||
p.name AS product_name
|
||||
FROM orders o
|
||||
INNER JOIN customers c ON o.customer_id = c.id
|
||||
INNER JOIN order_items oi ON o.id = oi.order_id
|
||||
INNER JOIN products p ON oi.product_id = p.id
|
||||
WHERE o.created_at >= CURRENT_DATE - INTERVAL '30 days'
|
||||
ORDER BY o.created_at DESC;
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
|
||||
CREATE INDEX idx_orders_created_at ON orders(created_at DESC);
|
||||
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
|
||||
CREATE INDEX idx_order_items_product_id ON order_items(product_id);
|
||||
```
|
||||
|
||||
### **3. Aggregations**
|
||||
|
||||
**Request:** "Get total sales by product category"
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
c.name AS category,
|
||||
COUNT(DISTINCT o.id) AS order_count,
|
||||
SUM(oi.quantity) AS units_sold,
|
||||
SUM(oi.quantity * oi.price) AS total_revenue,
|
||||
AVG(oi.price) AS avg_price
|
||||
FROM categories c
|
||||
INNER JOIN products p ON c.id = p.category_id
|
||||
INNER JOIN order_items oi ON p.id = oi.product_id
|
||||
INNER JOIN orders o ON oi.order_id = o.id
|
||||
WHERE o.status = 'completed'
|
||||
AND o.created_at >= CURRENT_DATE - INTERVAL '1 year'
|
||||
GROUP BY c.id, c.name
|
||||
HAVING SUM(oi.quantity * oi.price) > 1000
|
||||
ORDER BY total_revenue DESC;
|
||||
```
|
||||
|
||||
### **4. Subqueries**
|
||||
|
||||
**Request:** "Get users who have never made a purchase"
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
u.id,
|
||||
u.email,
|
||||
u.name,
|
||||
u.created_at
|
||||
FROM users u
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM orders o
|
||||
WHERE o.customer_id = u.id
|
||||
)
|
||||
ORDER BY u.created_at DESC;
|
||||
|
||||
-- Alternative using LEFT JOIN (often faster)
|
||||
SELECT
|
||||
u.id,
|
||||
u.email,
|
||||
u.name,
|
||||
u.created_at
|
||||
FROM users u
|
||||
LEFT JOIN orders o ON u.id = o.customer_id
|
||||
WHERE o.id IS NULL
|
||||
ORDER BY u.created_at DESC;
|
||||
```
|
||||
|
||||
### **5. Common Table Expressions (CTEs)**
|
||||
|
||||
**Request:** "Get top customers by purchase amount with their order history"
|
||||
|
||||
```sql
|
||||
WITH customer_totals AS (
|
||||
SELECT
|
||||
c.id,
|
||||
c.name,
|
||||
c.email,
|
||||
COUNT(o.id) AS order_count,
|
||||
SUM(o.total) AS total_spent
|
||||
FROM customers c
|
||||
INNER JOIN orders o ON c.id = o.customer_id
|
||||
WHERE o.status = 'completed'
|
||||
GROUP BY c.id, c.name, c.email
|
||||
HAVING SUM(o.total) > 500
|
||||
)
|
||||
SELECT
|
||||
ct.*,
|
||||
o.id AS order_id,
|
||||
o.order_date,
|
||||
o.total AS order_total
|
||||
FROM customer_totals ct
|
||||
INNER JOIN orders o ON ct.id = o.customer_id
|
||||
ORDER BY ct.total_spent DESC, o.order_date DESC;
|
||||
```
|
||||
|
||||
### **6. Window Functions**
|
||||
|
||||
**Request:** "Rank products by sales within each category"
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
p.id,
|
||||
p.name AS product_name,
|
||||
c.name AS category_name,
|
||||
SUM(oi.quantity * oi.price) AS total_sales,
|
||||
RANK() OVER (
|
||||
PARTITION BY p.category_id
|
||||
ORDER BY SUM(oi.quantity * oi.price) DESC
|
||||
) AS rank_in_category
|
||||
FROM products p
|
||||
INNER JOIN categories c ON p.category_id = c.id
|
||||
INNER JOIN order_items oi ON p.id = oi.product_id
|
||||
INNER JOIN orders o ON oi.order_id = o.id
|
||||
WHERE o.status = 'completed'
|
||||
GROUP BY p.id, p.name, p.category_id, c.name
|
||||
ORDER BY c.name, rank_in_category;
|
||||
```
|
||||
|
||||
### **7. INSERT Queries**
|
||||
|
||||
**Request:** "Insert new user with validation"
|
||||
|
||||
```sql
|
||||
-- Insert single user
|
||||
INSERT INTO users (id, email, name, password, created_at, updated_at)
|
||||
VALUES (
|
||||
gen_random_uuid(),
|
||||
'[email protected]',
|
||||
'John Doe',
|
||||
'hashed_password_here',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
)
|
||||
ON CONFLICT (email) DO NOTHING
|
||||
RETURNING id, email, name, created_at;
|
||||
|
||||
-- Bulk insert
|
||||
INSERT INTO users (id, email, name, password, created_at, updated_at)
|
||||
VALUES
|
||||
(gen_random_uuid(), '[email protected]', 'User 1', 'hash1', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
(gen_random_uuid(), '[email protected]', 'User 2', 'hash2', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||||
(gen_random_uuid(), '[email protected]', 'User 3', 'hash3', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT (email) DO NOTHING;
|
||||
```
|
||||
|
||||
### **8. UPDATE Queries**
|
||||
|
||||
**Request:** "Update product stock after order"
|
||||
|
||||
```sql
|
||||
-- Single update
|
||||
UPDATE products
|
||||
SET
|
||||
stock = stock - 5,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = 'product-uuid-here'
|
||||
AND stock >= 5 -- Safety check
|
||||
RETURNING id, name, stock;
|
||||
|
||||
-- Batch update with JOIN
|
||||
UPDATE products p
|
||||
SET
|
||||
stock = p.stock - oi.quantity,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
FROM order_items oi
|
||||
WHERE p.id = oi.product_id
|
||||
AND oi.order_id = 'order-uuid-here'
|
||||
AND p.stock >= oi.quantity;
|
||||
```
|
||||
|
||||
### **9. DELETE Queries**
|
||||
|
||||
**Request:** "Delete old inactive users"
|
||||
|
||||
```sql
|
||||
-- Soft delete (recommended)
|
||||
UPDATE users
|
||||
SET
|
||||
deleted_at = CURRENT_TIMESTAMP,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE active = false
|
||||
AND last_login_at < CURRENT_DATE - INTERVAL '1 year'
|
||||
RETURNING id, email;
|
||||
|
||||
-- Hard delete (with safety checks)
|
||||
DELETE FROM users
|
||||
WHERE active = false
|
||||
AND last_login_at < CURRENT_DATE - INTERVAL '2 years'
|
||||
AND id NOT IN (
|
||||
SELECT DISTINCT customer_id FROM orders
|
||||
);
|
||||
```
|
||||
|
||||
### **10. Full-Text Search**
|
||||
|
||||
**Request:** "Search blog posts by keyword"
|
||||
|
||||
**PostgreSQL:**
|
||||
```sql
|
||||
-- Create text search index
|
||||
CREATE INDEX idx_posts_search ON posts
|
||||
USING GIN (to_tsvector('english', title || ' ' || content));
|
||||
|
||||
-- Search query
|
||||
SELECT
|
||||
id,
|
||||
title,
|
||||
content,
|
||||
ts_rank(
|
||||
to_tsvector('english', title || ' ' || content),
|
||||
plainto_tsquery('english', 'search keywords')
|
||||
) AS relevance
|
||||
FROM posts
|
||||
WHERE to_tsvector('english', title || ' ' || content) @@
|
||||
plainto_tsquery('english', 'search keywords')
|
||||
AND published = true
|
||||
ORDER BY relevance DESC, created_at DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
**MySQL:**
|
||||
```sql
|
||||
-- Create fulltext index
|
||||
CREATE FULLTEXT INDEX idx_posts_search ON posts(title, content);
|
||||
|
||||
-- Search query
|
||||
SELECT
|
||||
id,
|
||||
title,
|
||||
content,
|
||||
MATCH(title, content) AGAINST('search keywords' IN NATURAL LANGUAGE MODE) AS relevance
|
||||
FROM posts
|
||||
WHERE MATCH(title, content) AGAINST('search keywords' IN NATURAL LANGUAGE MODE)
|
||||
AND published = true
|
||||
ORDER BY relevance DESC, created_at DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Optimization Tips
|
||||
|
||||
**1. Use Indexes Wisely:**
|
||||
```sql
|
||||
-- GOOD: Index foreign keys
|
||||
CREATE INDEX idx_posts_author_id ON posts(author_id);
|
||||
|
||||
-- GOOD: Index columns in WHERE clauses
|
||||
CREATE INDEX idx_posts_published ON posts(published, created_at DESC);
|
||||
|
||||
-- GOOD: Partial index for specific queries
|
||||
CREATE INDEX idx_active_users ON users(email) WHERE active = true;
|
||||
```
|
||||
|
||||
**2. Avoid SELECT *:**
|
||||
```sql
|
||||
-- BAD
|
||||
SELECT * FROM users;
|
||||
|
||||
-- GOOD
|
||||
SELECT id, email, name FROM users;
|
||||
```
|
||||
|
||||
**3. Use LIMIT:**
|
||||
```sql
|
||||
-- BAD (fetches all rows)
|
||||
SELECT * FROM posts ORDER BY created_at DESC;
|
||||
|
||||
-- GOOD (pagination)
|
||||
SELECT * FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 0;
|
||||
```
|
||||
|
||||
**4. Optimize JOINs:**
|
||||
```sql
|
||||
-- Use INNER JOIN when possible (faster than LEFT JOIN)
|
||||
-- Use EXISTS instead of IN for large datasets
|
||||
|
||||
-- BAD
|
||||
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);
|
||||
|
||||
-- GOOD
|
||||
SELECT u.* FROM users u WHERE EXISTS (
|
||||
SELECT 1 FROM orders o WHERE o.user_id = u.id
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database-Specific Syntax
|
||||
|
||||
**PostgreSQL:**
|
||||
- `gen_random_uuid()` for UUIDs
|
||||
- `INTERVAL` for date math
|
||||
- `RETURNING` clause
|
||||
- Full-text search with `tsvector`
|
||||
|
||||
**MySQL:**
|
||||
- `UUID()` for UUIDs
|
||||
- `DATE_SUB()` for date math
|
||||
- FULLTEXT indexes for search
|
||||
|
||||
**SQLite:**
|
||||
- `hex(randomblob(16))` for UUIDs
|
||||
- `datetime()` for dates
|
||||
- Limited JOIN types
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
- `/prisma-schema-gen` - Generate Prisma schemas
|
||||
- Database Designer (agent) - Schema design review
|
||||
|
||||
---
|
||||
|
||||
**Query smarter. Optimize faster. Scale confidently.**
|
||||
Reference in New Issue
Block a user