Initial commit
This commit is contained in:
18
skills/code-style/templates/.eslintrc.json
Normal file
18
skills/code-style/templates/.eslintrc.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": { "browser": true, "es2020": true },
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["react-refresh"],
|
||||
"rules": {
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"no-unused-vars": "off",
|
||||
"react-refresh/only-export-components": "off"
|
||||
}
|
||||
}
|
||||
9
skills/code-style/templates/.prettierrc.json
Normal file
9
skills/code-style/templates/.prettierrc.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"printWidth": 90,
|
||||
"singleQuote": false,
|
||||
"endOfLine": "lf",
|
||||
"trailingComma": "all",
|
||||
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"]
|
||||
}
|
||||
186
skills/code-style/templates/python-endpoint.py
Normal file
186
skills/code-style/templates/python-endpoint.py
Normal file
@@ -0,0 +1,186 @@
|
||||
"""Example FastAPI Router Template.
|
||||
|
||||
Copy and adapt this for new Grey Haven API endpoints.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Annotated
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
# Replace with your actual imports
|
||||
from app.db.models.example import ExampleDB
|
||||
from app.db.repositories.example_repository import ExampleRepository
|
||||
from app.dependencies import get_current_user, get_example_repository
|
||||
from app.schemas.example import ExampleCreate, ExampleResponse, ExampleUpdate
|
||||
|
||||
# 1. Create router with tags and dependencies
|
||||
router = APIRouter(
|
||||
prefix="/examples",
|
||||
tags=["examples"],
|
||||
dependencies=[Depends(get_current_user)], # Require authentication
|
||||
)
|
||||
|
||||
|
||||
# 2. POST endpoint - Create resource
|
||||
@router.post("/", response_model=ExampleResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_example(
|
||||
data: ExampleCreate,
|
||||
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
|
||||
current_user: Annotated[dict, Depends(get_current_user)],
|
||||
) -> ExampleResponse:
|
||||
"""
|
||||
Create a new example resource.
|
||||
|
||||
Args:
|
||||
data: Example creation data
|
||||
repo: Example repository dependency
|
||||
current_user: Currently authenticated user
|
||||
|
||||
Returns:
|
||||
ExampleResponse: Created example
|
||||
|
||||
Raises:
|
||||
HTTPException: If creation fails
|
||||
"""
|
||||
# Create resource with tenant isolation
|
||||
example = await repo.create(data, tenant_id=current_user["tenant_id"])
|
||||
return ExampleResponse.model_validate(example)
|
||||
|
||||
|
||||
# 3. GET endpoint - Retrieve single resource
|
||||
@router.get("/{example_id}", response_model=ExampleResponse)
|
||||
async def get_example(
|
||||
example_id: UUID,
|
||||
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
|
||||
current_user: Annotated[dict, Depends(get_current_user)],
|
||||
) -> ExampleResponse:
|
||||
"""
|
||||
Get example by ID with tenant isolation.
|
||||
|
||||
Args:
|
||||
example_id: Example UUID
|
||||
repo: Example repository dependency
|
||||
current_user: Currently authenticated user
|
||||
|
||||
Returns:
|
||||
ExampleResponse: Example data
|
||||
|
||||
Raises:
|
||||
HTTPException: If not found
|
||||
"""
|
||||
# Get with tenant isolation
|
||||
example = await repo.get_by_id(example_id, tenant_id=current_user["tenant_id"])
|
||||
|
||||
if not example:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Example not found"
|
||||
)
|
||||
|
||||
return ExampleResponse.model_validate(example)
|
||||
|
||||
|
||||
# 4. GET endpoint - List resources with pagination
|
||||
@router.get("/", response_model=list[ExampleResponse])
|
||||
async def list_examples(
|
||||
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
|
||||
current_user: Annotated[dict, Depends(get_current_user)],
|
||||
limit: int = Query(100, ge=1, le=1000, description="Max items to return"),
|
||||
offset: int = Query(0, ge=0, description="Number of items to skip"),
|
||||
is_active: bool | None = Query(None, description="Filter by active status"),
|
||||
) -> list[ExampleResponse]:
|
||||
"""
|
||||
List examples with pagination and filtering.
|
||||
|
||||
Args:
|
||||
repo: Example repository dependency
|
||||
current_user: Currently authenticated user
|
||||
limit: Maximum number of items to return
|
||||
offset: Number of items to skip
|
||||
is_active: Optional filter by active status
|
||||
|
||||
Returns:
|
||||
list[ExampleResponse]: List of examples
|
||||
"""
|
||||
# List with tenant isolation
|
||||
examples = await repo.list_by_tenant(
|
||||
tenant_id=current_user["tenant_id"],
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
is_active=is_active,
|
||||
)
|
||||
|
||||
return [ExampleResponse.model_validate(e) for e in examples]
|
||||
|
||||
|
||||
# 5. PATCH endpoint - Update resource
|
||||
@router.patch("/{example_id}", response_model=ExampleResponse)
|
||||
async def update_example(
|
||||
example_id: UUID,
|
||||
data: ExampleUpdate,
|
||||
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
|
||||
current_user: Annotated[dict, Depends(get_current_user)],
|
||||
) -> ExampleResponse:
|
||||
"""
|
||||
Update example by ID with tenant isolation.
|
||||
|
||||
Args:
|
||||
example_id: Example UUID
|
||||
data: Update data (partial fields)
|
||||
repo: Example repository dependency
|
||||
current_user: Currently authenticated user
|
||||
|
||||
Returns:
|
||||
ExampleResponse: Updated example
|
||||
|
||||
Raises:
|
||||
HTTPException: If not found
|
||||
"""
|
||||
# Get existing example with tenant isolation
|
||||
example = await repo.get_by_id(example_id, tenant_id=current_user["tenant_id"])
|
||||
|
||||
if not example:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Example not found"
|
||||
)
|
||||
|
||||
# Update fields (exclude_unset to only update provided fields)
|
||||
update_dict = data.model_dump(exclude_unset=True)
|
||||
for field, value in update_dict.items():
|
||||
setattr(example, field, value)
|
||||
|
||||
# Save updates
|
||||
updated = await repo.update(example)
|
||||
return ExampleResponse.model_validate(updated)
|
||||
|
||||
|
||||
# 6. DELETE endpoint - Delete resource
|
||||
@router.delete("/{example_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_example(
|
||||
example_id: UUID,
|
||||
repo: Annotated[ExampleRepository, Depends(get_example_repository)],
|
||||
current_user: Annotated[dict, Depends(get_current_user)],
|
||||
) -> None:
|
||||
"""
|
||||
Delete example by ID with tenant isolation.
|
||||
|
||||
Args:
|
||||
example_id: Example UUID
|
||||
repo: Example repository dependency
|
||||
current_user: Currently authenticated user
|
||||
|
||||
Raises:
|
||||
HTTPException: If not found
|
||||
"""
|
||||
# Get existing example with tenant isolation
|
||||
example = await repo.get_by_id(example_id, tenant_id=current_user["tenant_id"])
|
||||
|
||||
if not example:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="Example not found"
|
||||
)
|
||||
|
||||
# Delete
|
||||
await repo.delete(example_id, tenant_id=current_user["tenant_id"])
|
||||
126
skills/code-style/templates/python-model.py
Normal file
126
skills/code-style/templates/python-model.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""Example SQLModel Database Model Template.
|
||||
|
||||
Copy and adapt this for new Grey Haven database models.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from sqlalchemy import Column as SAColumn
|
||||
from sqlmodel import JSON, Column, Field, SQLModel
|
||||
|
||||
|
||||
def utc_now() -> datetime:
|
||||
"""Return current UTC datetime."""
|
||||
from datetime import UTC
|
||||
|
||||
return datetime.now(UTC)
|
||||
|
||||
|
||||
class ExampleDB(SQLModel, table=True): # type: ignore[call-arg]
|
||||
"""
|
||||
Example database model with multi-tenant support.
|
||||
|
||||
This model demonstrates Grey Haven's database conventions:
|
||||
- snake_case field names
|
||||
- Multi-tenant isolation with tenant_id
|
||||
- UTC timestamps
|
||||
- Proper indexes
|
||||
- Comprehensive docstrings
|
||||
"""
|
||||
|
||||
__tablename__ = "examples"
|
||||
|
||||
# Primary identification
|
||||
id: UUID = Field(
|
||||
default_factory=uuid4, primary_key=True, description="Unique example identifier"
|
||||
)
|
||||
|
||||
# Multi-tenant field (CRITICAL - always include!)
|
||||
tenant_id: UUID = Field(
|
||||
foreign_key="tenants.id", index=True, description="Owning tenant identifier"
|
||||
)
|
||||
|
||||
# Example fields - all snake_case!
|
||||
name: str = Field(index=True, max_length=255, description="Example name")
|
||||
description: str | None = Field(
|
||||
default=None, max_length=1000, description="Optional description"
|
||||
)
|
||||
|
||||
# Relationships (foreign keys)
|
||||
owner_id: UUID | None = Field(
|
||||
default=None, foreign_key="users.id", index=True, description="Owner user ID"
|
||||
)
|
||||
|
||||
# Status flags
|
||||
is_active: bool = Field(default=True, description="Whether example is active")
|
||||
is_archived: bool = Field(default=False, description="Whether example is archived")
|
||||
|
||||
# JSON metadata field
|
||||
metadata: dict | None = Field(
|
||||
default=None,
|
||||
sa_column=Column(JSON),
|
||||
description="Flexible JSON metadata storage",
|
||||
)
|
||||
|
||||
# Numerical fields
|
||||
priority: int = Field(
|
||||
default=0, ge=0, le=10, description="Priority level (0-10)"
|
||||
)
|
||||
max_retries: int = Field(
|
||||
default=3, ge=0, description="Maximum number of retry attempts"
|
||||
)
|
||||
|
||||
# Timestamps (UTC)
|
||||
created_at: datetime = Field(
|
||||
default_factory=utc_now, description="Creation timestamp (UTC)"
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=utc_now, description="Last update timestamp (UTC)"
|
||||
)
|
||||
archived_at: datetime | None = Field(
|
||||
default=None, description="Archive timestamp (UTC)"
|
||||
)
|
||||
|
||||
# Uncomment if using custom UTCDateTime type
|
||||
# from app.db.db_types import UTCDateTime
|
||||
# created_at: datetime = Field(
|
||||
# default_factory=utc_now,
|
||||
# sa_column=SAColumn(UTCDateTime, nullable=False)
|
||||
# )
|
||||
# updated_at: datetime = Field(
|
||||
# default_factory=utc_now,
|
||||
# sa_column=SAColumn(UTCDateTime, nullable=False, onupdate=utc_now)
|
||||
# )
|
||||
|
||||
|
||||
# Pydantic schemas for API (in separate schemas file)
|
||||
# class ExampleBase(BaseModel):
|
||||
# """Base example schema with shared fields."""
|
||||
# name: str = Field(..., max_length=255)
|
||||
# description: str | None = None
|
||||
# is_active: bool = True
|
||||
# priority: int = Field(default=0, ge=0, le=10)
|
||||
#
|
||||
# class ExampleCreate(ExampleBase):
|
||||
# """Schema for creating an example."""
|
||||
# tenant_id: UUID
|
||||
#
|
||||
# class ExampleUpdate(BaseModel):
|
||||
# """Schema for updating an example (all fields optional)."""
|
||||
# name: str | None = None
|
||||
# description: str | None = None
|
||||
# is_active: bool | None = None
|
||||
# priority: int | None = Field(None, ge=0, le=10)
|
||||
#
|
||||
# class ExampleResponse(ExampleBase):
|
||||
# """Example response schema."""
|
||||
# id: UUID
|
||||
# tenant_id: UUID
|
||||
# owner_id: UUID | None
|
||||
# created_at: datetime
|
||||
# updated_at: datetime
|
||||
#
|
||||
# model_config = ConfigDict(from_attributes=True)
|
||||
73
skills/code-style/templates/ruff.toml
Normal file
73
skills/code-style/templates/ruff.toml
Normal file
@@ -0,0 +1,73 @@
|
||||
# Grey Haven Studio - Ruff Configuration Template
|
||||
# Copy this to your project root as pyproject.toml or ruff.toml
|
||||
|
||||
[tool.ruff]
|
||||
# CRITICAL: Line length is 130, not 80 or 88!
|
||||
line-length = 130
|
||||
indent-width = 4
|
||||
|
||||
# Auto-fix issues without showing unfixable errors
|
||||
fix-only = true
|
||||
show-fixes = true
|
||||
|
||||
# Python version
|
||||
target-version = "py312"
|
||||
|
||||
[tool.ruff.lint]
|
||||
# Enable specific linter rules
|
||||
select = [
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
"F", # pyflakes
|
||||
"I", # isort (import sorting)
|
||||
"B", # flake8-bugbear (bug detection)
|
||||
"C4", # flake8-comprehensions
|
||||
"UP", # pyupgrade (automatic upgrades)
|
||||
]
|
||||
|
||||
# Ignore specific rules if needed
|
||||
ignore = []
|
||||
|
||||
[tool.ruff.format]
|
||||
# Use double quotes for strings
|
||||
quote-style = "double"
|
||||
|
||||
# Use spaces for indentation
|
||||
indent-style = "space"
|
||||
|
||||
# Use Unix-style line endings
|
||||
line-ending = "lf"
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
# Configure import sorting
|
||||
known-first-party = ["app"]
|
||||
section-order = [
|
||||
"future",
|
||||
"standard-library",
|
||||
"third-party",
|
||||
"first-party",
|
||||
"local-folder",
|
||||
]
|
||||
|
||||
# MyPy Configuration (add to pyproject.toml)
|
||||
# [tool.mypy]
|
||||
# python_version = "3.12"
|
||||
# warn_return_any = true
|
||||
# warn_unused_configs = true
|
||||
# disallow_untyped_defs = true # REQUIRED: Type hints on all functions!
|
||||
# check_untyped_defs = true
|
||||
# ignore_missing_imports = false
|
||||
# strict_optional = true
|
||||
# warn_redundant_casts = true
|
||||
# warn_unused_ignores = true
|
||||
|
||||
# Pytest Configuration (add to pyproject.toml)
|
||||
# [tool.pytest.ini_options]
|
||||
# pythonpath = ["."]
|
||||
# asyncio_mode = "auto"
|
||||
# testpaths = ["tests"]
|
||||
# markers = [
|
||||
# "unit: Unit tests",
|
||||
# "integration: Integration tests",
|
||||
# "e2e: End-to-end tests",
|
||||
# ]
|
||||
85
skills/code-style/templates/typescript-component.tsx
Normal file
85
skills/code-style/templates/typescript-component.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
// Example React Component Template
|
||||
// Copy and adapt this for new Grey Haven components
|
||||
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Button } from "~/lib/components/ui/button";
|
||||
import { Card } from "~/lib/components/ui/card";
|
||||
import { queryClient } from "~/lib/query-client";
|
||||
|
||||
// 1. Define your types/interfaces
|
||||
interface MyComponentProps {
|
||||
id: string;
|
||||
onUpdate?: (data: MyData) => void;
|
||||
}
|
||||
|
||||
interface MyData {
|
||||
id: string;
|
||||
name: string;
|
||||
created_at: Date; // snake_case for database fields
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
// 2. Component (default export for routes)
|
||||
export default function MyComponent({ id, onUpdate }: MyComponentProps) {
|
||||
// 3. State management
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
// 4. Queries with TanStack Query
|
||||
const { data, isLoading, error } = useQuery(
|
||||
{
|
||||
queryKey: ["myData", id],
|
||||
queryFn: async () => {
|
||||
// Replace with your API call
|
||||
const response = await fetch(`/api/data/${id}`);
|
||||
return response.json();
|
||||
},
|
||||
staleTime: 60000, // 1 minute - Grey Haven default
|
||||
},
|
||||
queryClient,
|
||||
);
|
||||
|
||||
// 5. Event handlers
|
||||
const handleSave = async () => {
|
||||
// Replace with your save logic
|
||||
console.log("Saving...");
|
||||
setIsEditing(false);
|
||||
onUpdate?.(data);
|
||||
};
|
||||
|
||||
// 6. Conditional renders
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div>Error loading data</div>;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return <div>No data found</div>;
|
||||
}
|
||||
|
||||
// 7. Main render
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<h2 className="mb-4 text-2xl font-bold">{data.name}</h2>
|
||||
|
||||
{isEditing ? (
|
||||
<div className="space-y-4">
|
||||
{/* Edit mode UI */}
|
||||
<Button onClick={handleSave}>Save</Button>
|
||||
<Button variant="outline" onClick={() => setIsEditing(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{/* View mode UI */}
|
||||
<p>Status: {data.is_active ? "Active" : "Inactive"}</p>
|
||||
<Button onClick={() => setIsEditing(true)}>Edit</Button>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
92
skills/code-style/templates/typescript-server-function.ts
Normal file
92
skills/code-style/templates/typescript-server-function.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
// Example TanStack Start Server Function Template
|
||||
// Copy and adapt this for new Grey Haven server functions
|
||||
|
||||
import { createServerFn } from "@tanstack/start";
|
||||
import { db } from "~/lib/server/db";
|
||||
import { users } from "~/lib/server/schema/users";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
|
||||
// 1. Define input schema with Zod
|
||||
const getUserInputSchema = z.object({
|
||||
userId: z.string().uuid(),
|
||||
tenantId: z.string(), // Always include tenant_id for multi-tenant isolation
|
||||
});
|
||||
|
||||
// 2. Define output type
|
||||
interface UserOutput {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
created_at: Date;
|
||||
tenant_id: string;
|
||||
}
|
||||
|
||||
// 3. Create server function
|
||||
export const getUser = createServerFn("GET", async (input: unknown): Promise<UserOutput> => {
|
||||
// Validate input
|
||||
const { userId, tenantId } = getUserInputSchema.parse(input);
|
||||
|
||||
// Query database with tenant isolation
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.id, userId) && eq(users.tenant_id, tenantId), // Tenant filtering!
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error("User not found");
|
||||
}
|
||||
|
||||
// Return typed result
|
||||
return {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
created_at: user.created_at,
|
||||
tenant_id: user.tenant_id,
|
||||
};
|
||||
});
|
||||
|
||||
// 4. Mutation example
|
||||
const updateUserInputSchema = z.object({
|
||||
userId: z.string().uuid(),
|
||||
tenantId: z.string(),
|
||||
name: z.string().min(1),
|
||||
email: z.string().email(),
|
||||
});
|
||||
|
||||
export const updateUser = createServerFn(
|
||||
"POST",
|
||||
async (input: unknown): Promise<UserOutput> => {
|
||||
// Validate input
|
||||
const { userId, tenantId, name, email } = updateUserInputSchema.parse(input);
|
||||
|
||||
// Update with tenant isolation
|
||||
const [updatedUser] = await db
|
||||
.update(users)
|
||||
.set({ name, email, updated_at: new Date() })
|
||||
.where(eq(users.id, userId) && eq(users.tenant_id, tenantId)) // Tenant filtering!
|
||||
.returning();
|
||||
|
||||
if (!updatedUser) {
|
||||
throw new Error("User not found or update failed");
|
||||
}
|
||||
|
||||
return {
|
||||
id: updatedUser.id,
|
||||
name: updatedUser.name,
|
||||
email: updatedUser.email,
|
||||
created_at: updatedUser.created_at,
|
||||
tenant_id: updatedUser.tenant_id,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// 5. Usage in client components
|
||||
// import { useQuery } from "@tanstack/react-query";
|
||||
// import { getUser } from "~/lib/server/functions/users";
|
||||
//
|
||||
// const { data: user } = useQuery({
|
||||
// queryKey: ["user", userId],
|
||||
// queryFn: () => getUser({ userId, tenantId }),
|
||||
// staleTime: 60000,
|
||||
// });
|
||||
Reference in New Issue
Block a user