원클릭으로
api-route-creator
Creates Next.js 16 API routes with auth, validation, and tenant scoping. Use when creating API endpoints.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Creates Next.js 16 API routes with auth, validation, and tenant scoping. Use when creating API endpoints.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Implements AI tools for Canvas generation and updates. Use when creating GenUI tools for code generation, document editing, or Canvas interactions.
Develops Canvas code execution features with Pyodide/iframe sandboxing. Use when working on Python/JS execution, package management, or sandbox security.
Creates and extends Canvas UI components with Monaco editor, split views, and educational context. Use when building Canvas panel, editor, or preview features.
Foundational Canvas type definitions and language support patterns. Use when working with Canvas types, schemas, or language configuration.
Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks
Creates educational UI components following learning science principles. Use for classroom, student, or teacher interfaces.
| name | api-route-creator |
| description | Creates Next.js 16 API routes with auth, validation, and tenant scoping. Use when creating API endpoints. |
| allowed-tools | Read, Write, Edit, Glob |
| context | fork |
Use this skill when creating:
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/lib/auth/config';
import { z } from 'zod';
import { db } from '@/lib/db';
import { eq, and } from 'drizzle-orm';
import { tableName } from '@/lib/db/schema';
import { auditLogger } from '@/lib/audit/logger';
// Input validation schema
const inputSchema = z.object({
name: z.string().min(1).max(100).trim(),
description: z.string().max(500).optional(),
});
// GET - Read (with tenant scoping)
export async function GET(request: NextRequest) {
try {
const session = await auth();
// Authentication check
if (!session?.user) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// Role check (if needed)
if (!['teacher', 'tenant_admin'].includes(session.user.role)) {
return NextResponse.json(
{ error: 'Forbidden' },
{ status: 403 }
);
}
// ✅ CRITICAL: Always scope by tenant
const data = await db.query.tableName.findMany({
where: eq(tableName.tenantId, session.user.tenantId),
});
return NextResponse.json({ data });
} catch (error) {
console.error('[API] Error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
// POST - Create
export async function POST(request: NextRequest) {
try {
const session = await auth();
if (!session?.user) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// Parse and validate input
const body = await request.json();
const validatedInput = inputSchema.parse(body);
// ✅ CRITICAL: Use session tenant, NEVER request body
const [created] = await db.insert(tableName).values({
...validatedInput,
tenantId: session.user.tenantId, // FROM SESSION ONLY
createdBy: session.user.id,
}).returning();
// Audit log sensitive operations
await auditLogger.log({
action: 'CREATE',
resourceType: 'RESOURCE_NAME',
resourceId: created.id,
userId: session.user.id,
tenantId: session.user.tenantId,
});
return NextResponse.json({ data: created }, { status: 201 });
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: 'Validation error', details: error.errors },
{ status: 400 }
);
}
console.error('[API] Error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/lib/auth/config';
import { db } from '@/lib/db';
import { eq, and } from 'drizzle-orm';
import { tableName } from '@/lib/db/schema';
interface RouteContext {
params: Promise<{ id: string }>;
}
export async function GET(
request: NextRequest,
context: RouteContext
) {
try {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// ✅ Await params in Next.js 16
const { id } = await context.params;
// ✅ CRITICAL: Scope by BOTH id AND tenant
const item = await db.query.tableName.findFirst({
where: and(
eq(tableName.id, id),
eq(tableName.tenantId, session.user.tenantId)
),
});
if (!item) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
return NextResponse.json({ data: item });
} catch (error) {
console.error('[API] Error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
// Standard error responses (FERPA-safe - no data leakage)
{ error: 'Unauthorized' } // 401 - Not authenticated
{ error: 'Forbidden' } // 403 - Wrong role
{ error: 'Not found' } // 404 - Doesn't exist or wrong tenant
{ error: 'Validation error', details: [...] } // 400 - Bad input
{ error: 'Internal server error' } // 500 - Something broke
// ❌ NEVER expose internal details
{ error: `Item ${id} not found in tenant ${tenantId}` } // WRONG!
| Role | Can Access |
|---|---|
| student | Own data, joined assistants |
| teacher | Own assistants, class students |
| tenant_admin | All tenant data, user management |
| platform_admin | Everything (cross-tenant) |