Initial commit
This commit is contained in:
8
skills/database-conventions/examples/INDEX.md
Normal file
8
skills/database-conventions/examples/INDEX.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Database Convention Examples Index
|
||||
|
||||
**All files under 500 lines for optimal loading.**
|
||||
|
||||
- **[drizzle-schemas.md](drizzle-schemas.md)** - TypeScript/Drizzle ORM examples
|
||||
- **[sqlmodel-schemas.md](sqlmodel-schemas.md)** - Python/SQLModel examples
|
||||
- **[migrations.md](migrations.md)** - Migration patterns for both stacks
|
||||
- **[rls-policies.md](rls-policies.md)** - Row Level Security examples
|
||||
87
skills/database-conventions/examples/drizzle-schemas.md
Normal file
87
skills/database-conventions/examples/drizzle-schemas.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Drizzle Schema Examples
|
||||
|
||||
**Complete TypeScript/Drizzle ORM schema patterns.**
|
||||
|
||||
## Basic Table with Multi-Tenant
|
||||
|
||||
```typescript
|
||||
// db/schema/users.ts
|
||||
import { pgTable, uuid, text, timestamp, boolean, index } from "drizzle-orm/pg-core";
|
||||
|
||||
export const users = pgTable("users", {
|
||||
// Primary key
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
|
||||
// Timestamps (required on all tables)
|
||||
created_at: timestamp("created_at").defaultNow().notNull(),
|
||||
updated_at: timestamp("updated_at").defaultNow().notNull().$onUpdate(() => new Date()),
|
||||
|
||||
// Multi-tenant (required on all tables)
|
||||
tenant_id: uuid("tenant_id").notNull(),
|
||||
|
||||
// User fields
|
||||
email_address: text("email_address").notNull().unique(),
|
||||
full_name: text("full_name").notNull(),
|
||||
hashed_password: text("hashed_password").notNull(),
|
||||
is_active: boolean("is_active").default(true).notNull(),
|
||||
is_verified: boolean("is_verified").default(false).notNull(),
|
||||
last_login_at: timestamp("last_login_at"),
|
||||
|
||||
// Soft delete
|
||||
deleted_at: timestamp("deleted_at"),
|
||||
});
|
||||
|
||||
// Indexes
|
||||
export const usersIndex = index("users_tenant_id_idx").on(users.tenant_id);
|
||||
export const usersEmailIndex = index("users_email_idx").on(users.email_address);
|
||||
```
|
||||
|
||||
## Reusable Timestamp Pattern
|
||||
|
||||
```typescript
|
||||
// db/schema/base.ts
|
||||
export const baseTimestamps = {
|
||||
created_at: timestamp("created_at").defaultNow().notNull(),
|
||||
updated_at: timestamp("updated_at").defaultNow().notNull().$onUpdate(() => new Date()),
|
||||
};
|
||||
|
||||
// Use in tables
|
||||
export const teams = pgTable("teams", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
...baseTimestamps,
|
||||
tenant_id: uuid("tenant_id").notNull(),
|
||||
name: text("name").notNull(),
|
||||
});
|
||||
```
|
||||
|
||||
## One-to-Many Relationships
|
||||
|
||||
```typescript
|
||||
// db/schema/posts.ts
|
||||
import { relations } from "drizzle-orm";
|
||||
|
||||
export const posts = pgTable("posts", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
created_at: timestamp("created_at").defaultNow().notNull(),
|
||||
updated_at: timestamp("updated_at").defaultNow().notNull(),
|
||||
tenant_id: uuid("tenant_id").notNull(),
|
||||
|
||||
// Foreign key to users
|
||||
author_id: uuid("author_id").notNull(),
|
||||
|
||||
title: text("title").notNull(),
|
||||
content: text("content").notNull(),
|
||||
is_published: boolean("is_published").default(false).notNull(),
|
||||
});
|
||||
|
||||
// Define relations
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
posts: many(posts), // User has many posts
|
||||
}));
|
||||
|
||||
export const postsRelations = relations(posts, ({ one }) => ({
|
||||
author: one(users, { fields: [posts.author_id], references: [users.id] }),
|
||||
}));
|
||||
```
|
||||
|
||||
**See [../templates/drizzle-table.ts](../templates/drizzle-table.ts) for complete template.**
|
||||
31
skills/database-conventions/examples/migrations.md
Normal file
31
skills/database-conventions/examples/migrations.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Migration Examples
|
||||
|
||||
**Migration patterns for Drizzle (TypeScript) and Alembic (Python).**
|
||||
|
||||
## Drizzle Migrations (TypeScript)
|
||||
|
||||
```bash
|
||||
# Generate migration
|
||||
bun run drizzle-kit generate:pg
|
||||
|
||||
# Apply migration
|
||||
bun run drizzle-kit push:pg
|
||||
|
||||
# Check migration status
|
||||
bun run drizzle-kit check:pg
|
||||
```
|
||||
|
||||
## Alembic Migrations (Python)
|
||||
|
||||
```bash
|
||||
# Generate migration
|
||||
alembic revision --autogenerate -m "Add users table"
|
||||
|
||||
# Apply migration
|
||||
alembic upgrade head
|
||||
|
||||
# Rollback
|
||||
alembic downgrade -1
|
||||
```
|
||||
|
||||
**See [../reference/migrations.md](../reference/migrations.md) for complete setup.**
|
||||
27
skills/database-conventions/examples/rls-policies.md
Normal file
27
skills/database-conventions/examples/rls-policies.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Row Level Security Examples
|
||||
|
||||
**RLS policy patterns for multi-tenant isolation.**
|
||||
|
||||
## Enable RLS
|
||||
|
||||
```sql
|
||||
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
||||
```
|
||||
|
||||
## Tenant Isolation Policy
|
||||
|
||||
```sql
|
||||
CREATE POLICY "tenant_isolation" ON users
|
||||
FOR ALL TO authenticated
|
||||
USING (tenant_id = (current_setting('request.jwt.claims')::json->>'tenant_id')::uuid);
|
||||
```
|
||||
|
||||
## Admin Override Policy
|
||||
|
||||
```sql
|
||||
CREATE POLICY "admin_access" ON users
|
||||
FOR ALL TO admin
|
||||
USING (true);
|
||||
```
|
||||
|
||||
**See [../reference/rls-policies.md](../reference/rls-policies.md) for complete RLS guide.**
|
||||
102
skills/database-conventions/examples/sqlmodel-schemas.md
Normal file
102
skills/database-conventions/examples/sqlmodel-schemas.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# SQLModel Schema Examples
|
||||
|
||||
**Complete Python/SQLModel schema patterns.**
|
||||
|
||||
## Basic Model with Multi-Tenant
|
||||
|
||||
```python
|
||||
# app/models/user.py
|
||||
from sqlmodel import Field, SQLModel
|
||||
from uuid import UUID, uuid4
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
class User(SQLModel, table=True):
|
||||
"""User model with multi-tenant isolation."""
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
# Primary key
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
|
||||
# Timestamps (required on all tables)
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow,
|
||||
sa_column_kwargs={"onupdate": datetime.utcnow}
|
||||
)
|
||||
|
||||
# Multi-tenant (required on all tables)
|
||||
tenant_id: UUID = Field(foreign_key="tenants.id", index=True)
|
||||
|
||||
# User fields
|
||||
email_address: str = Field(unique=True, index=True, max_length=255)
|
||||
full_name: str = Field(max_length=255)
|
||||
hashed_password: str = Field(max_length=255)
|
||||
is_active: bool = Field(default=True)
|
||||
is_verified: bool = Field(default=False)
|
||||
last_login_at: Optional[datetime] = None
|
||||
|
||||
# Soft delete
|
||||
deleted_at: Optional[datetime] = None
|
||||
```
|
||||
|
||||
## Reusable Timestamp Mixin
|
||||
|
||||
```python
|
||||
# app/models/base.py
|
||||
from sqlmodel import Field
|
||||
from datetime import datetime
|
||||
|
||||
class TimestampMixin:
|
||||
"""Mixin for created_at and updated_at timestamps."""
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow,
|
||||
sa_column_kwargs={"onupdate": datetime.utcnow}
|
||||
)
|
||||
|
||||
# Use in models
|
||||
class Team(TimestampMixin, SQLModel, table=True):
|
||||
__tablename__ = "teams"
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
tenant_id: UUID = Field(foreign_key="tenants.id", index=True)
|
||||
name: str = Field(max_length=255)
|
||||
```
|
||||
|
||||
## One-to-Many Relationships
|
||||
|
||||
```python
|
||||
# app/models/post.py
|
||||
from sqlmodel import Field, SQLModel, Relationship
|
||||
from uuid import UUID, uuid4
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
|
||||
class Post(SQLModel, table=True):
|
||||
"""Post model with author relationship."""
|
||||
|
||||
__tablename__ = "posts"
|
||||
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
tenant_id: UUID = Field(foreign_key="tenants.id", index=True)
|
||||
|
||||
# Foreign key to users
|
||||
author_id: UUID = Field(foreign_key="users.id", index=True)
|
||||
|
||||
title: str = Field(max_length=255)
|
||||
content: str
|
||||
is_published: bool = Field(default=False)
|
||||
|
||||
# Relationship
|
||||
author: Optional["User"] = Relationship(back_populates="posts")
|
||||
|
||||
# Add to User model
|
||||
class User(TimestampMixin, SQLModel, table=True):
|
||||
# ... (previous fields)
|
||||
posts: List["Post"] = Relationship(back_populates="author")
|
||||
```
|
||||
|
||||
**See [../templates/sqlmodel-table.py](../templates/sqlmodel-table.py) for complete template.**
|
||||
Reference in New Issue
Block a user