mirror of
https://github.com/SrIzan10/hctv.git
synced 2026-06-05 16:46:50 +00:00
8.6 KiB
8.6 KiB
Agent Guidelines for tv
This document provides essential information for AI coding agents working on the HackClub.tv codebase.
Project Overview
HackClub.tv is a live streaming platform built with Next.js 16, Prisma, and Turbo monorepo architecture.
- Monorepo: Turborepo with pnpm workspaces
- Apps: web (Next.js), chat (Hono), docs
- Packages: db (Prisma), auth (Lucia), hono-ws, sdk
- Package Manager: pnpm 10.6.5
Build, Lint, and Test Commands
Root Level Commands
pnpm install # Install all dependencies
pnpm build # Build all apps and packages (uses Turbo)
pnpm dev # Start all apps in dev mode (uses Turbo)
pnpm lint # Lint all apps (uses Turbo)
Database Commands
pnpm db:migrate # Run Prisma migrations in dev
pnpm prisma # Run any Prisma command in db package
App-Specific Commands
# Web app (Next.js)
pnpm --filter=@hctv/web dev # Start Next.js dev server
pnpm --filter=@hctv/web build # Build Next.js app
pnpm --filter=@hctv/web lint # Lint web app
pnpm --filter=@hctv/web check-types # Type check (tsc --noEmit)
pnpm --filter=@hctv/web ui:add # Add shadcn components
# Chat app (Hono)
pnpm --filter=@hctv/chat dev # Start chat server with watch
pnpm --filter=@hctv/chat build # Build chat server
# DB package (Prisma)
pnpm --filter=@hctv/db db:generate # Generate Prisma client
pnpm --filter=@hctv/db db:migrate # Run migrations
pnpm --filter=@hctv/db build # Generate client and build
Running Single Tests
This project does not currently have a test suite configured. When adding tests:
- Use Vitest or Jest for unit tests
- Use Playwright for E2E tests (recommended for Next.js)
- Follow the pattern:
pnpm test -- <test-file-path>
Docker Commands
pnpm docker:web # Build web app Docker image
pnpm docker:chat # Build chat app Docker image
pnpm r:rtmp # Restart RTMP server
Code Style Guidelines
Formatting
- Formatter: Prettier (
.prettierrc.json) - Indentation: 2 spaces (no tabs)
- Line Width: 100 characters
- Quotes: Single quotes
- Semicolons: Required
- Trailing Commas: ES5 style
- Linter: ESLint with Next.js rules (
next/core-web-vitals)
Import Organization
Imports should be ordered as follows (no blank lines between groups):
- React/Next.js core imports
- Third-party libraries
- Internal components (
@/components) - Internal utilities/libs (
@/lib) - Package imports (
@hctv/*) - Type imports (use
import typekeyword) - Relative imports
import { useState, useEffect } from 'react';
import { Send } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { validateRequest } from '@/lib/auth/validate';
import { prisma } from '@hctv/db';
import type { User, Channel } from '@hctv/db';
import { helper } from './utils';
TypeScript Usage
- Strict mode: Enabled
- Type over interface: Use
typefor unions/intersections,interfacefor object shapes - No implicit any: Always type your variables
- Type imports: Use
import typefor type-only imports - Prisma types: Import from
@hctv/dband use type composition
// Interfaces for props and object shapes
interface ChatMessage {
user?: User;
message: string;
type: 'message' | 'systemMsg';
}
// Type aliases for complex types
type FormFieldConfig = {
name: string;
label?: string;
};
// Prisma type composition
type StreamWithChannel = StreamInfo & { channel: Channel };
Naming Conventions
Files:
- React components:
PascalCase.tsx(e.g.,ChatPanel.tsx) - Utilities/helpers:
camelCase.ts(e.g.,validate.ts) - Next.js pages:
page.tsx,route.ts,layout.tsx - Client components:
page.client.tsx
Variables & Functions:
- Components:
PascalCase - Functions:
camelCase - Constants:
SCREAMING_SNAKE_CASE - Booleans: Use
is,has,shouldprefixes - Refs:
Refsuffix (e.g.,socketRef)
const MESSAGE_HISTORY_SIZE = 15;
const isFollowing = await checkFollowing();
const socketRef = useRef<WebSocket | null>(null);
Error Handling
API Routes:
- Return
Responseobjects with appropriate status codes - Use descriptive error messages
- Status codes: 400 (bad request), 401 (unauthorized), 403 (forbidden), 404 (not found)
if (!user) {
return new Response('Unauthorized', { status: 401 });
}
Server Actions:
- Return objects with
successboolean anderrorordatafields - Use
zodVerifyhelper for validation
const zod = await zodVerify(schema, formData);
if (!zod.success) {
return { success: false, error: zod.error };
}
return { success: true, data: result };
Client-side:
- Use try-catch for async operations
- Use toast notifications (sonner) for user feedback
- Log errors with
console.error
Async Patterns
- Prefer: async/await over Promise chains
- Parallel operations: Use
Promise.all() - No .then() chaining: Except in utility functions like fetchers
// Standard async/await
const data = await prisma.model.findMany();
// Parallel operations
const [channelA, channelB] = await Promise.all([
prisma.channel.findUnique({ where: { id: 'a' } }),
prisma.channel.findUnique({ where: { id: 'b' } }),
]);
React Component Patterns
Component Structure
- Directive (
'use client'or'use server') - Imports
- Component function
- Helper functions (if needed)
- Type/interface definitions (at bottom)
'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
export default function ChatPanel(props: Props) {
const [message, setMessage] = useState('');
return <div>{/* JSX */}</div>;
}
interface Props {
username: string;
}
Server vs Client Components
- Server components: Default (no directive), use for data fetching
- Client components: Add
'use client', use for interactivity - Fetch data in server components, pass to client components as props
Database Patterns (Prisma)
Imports
import { prisma } from '@hctv/db';
import type { User, Channel, StreamInfo } from '@hctv/db';
Common Queries
// FindUnique with relations
const channel = await prisma.channel.findUnique({
where: { name: channelName },
include: { owner: true, streamInfo: true },
});
// FindMany with dynamic filters
const where: Prisma.StreamInfoWhereInput = {};
if (isLive) where.isLive = true;
const streams = await prisma.streamInfo.findMany({ where });
// Create with relations
await prisma.channel.create({
data: {
name: channelName,
ownerId: user.id,
personalFor: { connect: { id: user.id } },
},
});
// Update
await prisma.streamInfo.update({
where: { username },
data: { title: newTitle },
});
// Upsert
await prisma.streamKey.upsert({
create: { key: newKey, channelId },
update: { key: newKey },
where: { channelId },
});
Redis Usage
import { getRedisConnection } from '@hctv/db';
const redis = getRedisConnection();
await redis.set('key', 'value');
await redis.get('key');
await redis.setex('key', 30, 'value'); // with expiry
API Route Patterns
Next.js App Router
import { validateRequest } from '@/lib/auth/validate';
import { prisma } from '@hctv/db';
import { NextRequest } from 'next/server';
export async function GET(request: NextRequest) {
const { user } = await validateRequest();
if (!user) {
return new Response('Unauthorized', { status: 401 });
}
const searchParams = request.nextUrl.searchParams;
const param = searchParams.get('param');
const data = await prisma.model.findMany();
return Response.json(data);
}
Server Actions
'use server';
export async function createChannel(prev: any, formData: FormData) {
const { user } = await validateRequest();
if (!user) {
return { success: false, error: 'Unauthorized' };
}
const zod = await zodVerify(createChannelSchema, formData);
if (!zod.success) {
return zod;
}
// ... processing
return { success: true };
}
Important Notes
- Turbo caching: Build outputs are cached. Use
--forceto bypass cache - Environment variables: Use
NEXT_PUBLIC_prefix for client-side vars - Styling: Tailwind CSS with shadcn/ui components, use
cn()for conditional classes - Data fetching: SWR for client-side, direct Prisma for server components
- Validation: Zod schemas for form and API validation
- Cache invalidation: Use
revalidatePath()after mutations