Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Welcome to Hackcontrol

Hackcontrol is a modern hackathon management platform that makes organizing and participating in hackathons simple and enjoyable. This guide will walk you through how to use the platform as a participant, organizer, or judge.

Getting Started as a Participant

1. Joining a Hackathon

When an organizer shares a Hackcontrol hackathon link (like hackcontrol.dev/dQ5YRq), here's how to join:

  1. Visit the hackathon page using the provided link
  2. Sign in with GitHub - Click the "Sign in with GitHub" button and authorize the app
  3. View the hackathon details - Read the description, rules, and submission criteria
  4. Check announcements - Stay updated with important information from organizers

2. Submitting Your Project

Once you're ready to submit your project:

  1. Click "Submit Project" on the hackathon page

  2. Fill out the submission form:

    • Project Title: Give your project a catchy name
    • Description: Explain what your project does and how you built it
    • Project URL: Link to your GitHub repo, live demo, or presentation
    • Team Members: Add your teammates (optional)
  3. Submit and track progress - Your submission will appear on the hackathon page and you can see its review status

3. Following Results

  • Check review status - See if judges have reviewed your project
  • View announcements - Get updates from organizers about judging progress
  • See final results - Winners are announced on the hackathon page

Using Hackcontrol as an Organizer

1. Creating Your First Hackathon

After signing in and getting organizer privileges:

  1. Go to your dashboard (/app) and click "Create Hackathon"
  2. Set up your event:
    • Name: Your hackathon's title
    • Description: Event overview and goals
    • Rules: Participation guidelines and requirements
    • Criteria: How projects will be judged

2. Managing Your Hackathon

Invite Judges:

  • Use the "Judge Manager" to add judges by searching for their usernames
  • Judges get access to review and score submissions

Make Announcements:

  • Keep participants informed with updates
  • Mark important announcements to highlight them
  • Announcements appear on the hackathon page in real-time

Monitor Submissions:

  • View all project submissions in one place
  • Track which projects have been reviewed
  • Mark winners once judging is complete

Share Your Event:

  • Copy and share your hackathon's URL
  • Participants can join directly without additional registration

3. Ending Your Hackathon

When judging is complete:

  1. Review final scores - Check the leaderboard and judge feedback
  2. Select winners - Mark winning projects
  3. Finish the hackathon - This locks submissions and finalizes results
  4. Celebrate - Winners are displayed prominently on the hackathon page

Judging Projects

1. Accessing Your Judge Dashboard

When invited as a judge:

  1. Sign in with your GitHub account
  2. Visit the hackathon page - You'll see a "Judge Dashboard" option
  3. View projects to review - See all submissions you need to evaluate

2. Evaluating Projects

For each project:

  1. Review the submission - Read the description and visit the project URL
  2. Score the project - Give it a numeric score based on the hackathon criteria
  3. Submit your evaluation - Your score is recorded and averages with other judges

3. Judge Responsibilities

  • Review all assigned projects fairly and thoroughly
  • Use consistent criteria across all submissions
  • Complete reviews in a timely manner to help organizers announce results
  • Maintain objectivity when evaluating projects

Understanding User Roles

Regular User:

  • Can join hackathons and submit projects
  • View public hackathon information
  • Receive announcements from organizers

Organizer:

  • Create and manage hackathons
  • Invite judges and manage participants
  • Make announcements and control event settings
  • Access analytics and submission data

Judge:

  • Evaluate and score hackathon submissions
  • Access special judge dashboard
  • Provide feedback on projects

Tips for Success

For Participants

  • Submit early - Don't wait until the last minute
  • Read the criteria - Understand how you'll be judged
  • Include a good demo - Make sure your project URL works and showcases your work
  • Follow announcements - Stay updated on deadlines and changes

For Organizers

  • Set clear expectations - Write detailed rules and judging criteria
  • Communicate regularly - Use announcements to keep participants informed
  • Plan judging carefully - Invite enough judges and give them clear guidelines
  • Test everything - Make sure your hackathon setup works before sharing

For Judges

  • Be fair and consistent - Use the same standards for all projects
  • Visit project links - Actually try the demos and read the code
  • Score thoughtfully - Take time to properly evaluate each submission
  • Ask questions - Reach out to organizers if criteria are unclear

Getting Help

If you encounter issues or have questions:

  • Check if there are any announcements from the organizer
  • Look for contact information on the hackathon page
  • For technical issues, contact the platform administrators

Quick Start Guide

This guide will get you up and running with Hackcontrol in under 10 minutes.

Prerequisites

  • Node.js 18+ installed
  • Git installed
  • CockroachDB account (free tier available)
  • GitHub account for OAuth

1. Clone and Install

git clone https://github.com/raducarabat/hackcontrol.git
cd hackcontrol
npm install

2. Environment Setup

Create a .env file in the root directory:

# Database
DATABASE_URL="your-cockroachdb-connection-string"

# NextAuth
NEXTAUTH_SECRET="your-secret-key"
NEXTAUTH_URL="http://localhost:3000"

# GitHub OAuth
GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"

3. Database Setup

npx prisma migrate dev --name init

This creates the initial migration and applies it to your database.

4. Run Development Server

npm run dev

Open http://localhost:3000 in your browser.

5. First Login & Admin Setup

  1. Click "Sign in with GitHub"
  2. Authorize the application
  3. Important: Set your account as admin by running this SQL command in your CockroachDB console:
UPDATE users SET role = 'ADMIN' WHERE email = 'your-email@example.com';

Replace your-email@example.com with the email associated with your GitHub account.

  1. Refresh the page - you now have full admin privileges!

Next Steps

Local Installation

Complete guide for setting up Hackcontrol on your local development environment.

System Requirements

  • Node.js: Version 18.0 or higher
  • npm: Version 8.0 or higher (or pnpm/yarn)
  • Git: Latest version
  • Database: CockroachDB (cloud or local)

Step-by-Step Installation

1. Clone the Repository

git clone https://github.com/raducarabat/hackcontrol.git
cd hackcontrol

2. Install Dependencies

Choose your preferred package manager:

# Using npm
npm install

# Using pnpm (recommended for faster installs)
pnpm install

# Using yarn
yarn install

3. Environment Configuration

Copy the example environment file and configure it:

cp .env.example .env  # If available, or create new .env

Edit .env with your configuration:

# CockroachDB connection string
DATABASE_URL="postgresql://username:password@host:port/database?sslmode=require"

# NextAuth configuration
NEXTAUTH_SECRET="your-generated-secret-here"
NEXTAUTH_URL="http://localhost:3000"

# GitHub OAuth Provider
GITHUB_CLIENT_ID="your-github-app-client-id"
GITHUB_CLIENT_SECRET="your-github-app-client-secret"

4. Database Migration

Apply the database schema:

npx prisma migrate dev --name init

This command will:

  • Create migration files in prisma/migrations/
  • Apply the schema to your database
  • Generate the Prisma client

5. Set Admin Privileges

After your first login, grant yourself admin access:

  1. Sign in through the application once
  2. Connect to your CockroachDB console
  3. Run this SQL command:
UPDATE users SET role = 'ADMIN' WHERE email = 'your-github-email@example.com';

6. Start Development Server

npm run dev

The application will be available at http://localhost:3000.

Verification

To verify your installation:

  1. Server starts: No errors in terminal
  2. Database connected: Check Prisma Studio with npm run studio
  3. Authentication works: Can sign in with GitHub
  4. Admin access: Can create new hackathons

Common Issues

  • Database connection errors: Check your DATABASE_URL format
  • Authentication fails: Verify GitHub OAuth app configuration
  • Migration errors: Ensure database exists and is accessible
  • Port conflicts: Change port with npm run dev -- -p 3001

Next Steps

Environment Configuration

Hackcontrol uses environment variables for configuration. This guide covers all required and optional environment variables.

Environment Variables

All environment variables are validated using Zod schemas in src/env/index.mjs.

Required Variables

Database

DATABASE_URL="postgresql://username:password@host:port/database?sslmode=require"
  • Purpose: CockroachDB connection string
  • Format: PostgreSQL connection URL with SSL
  • Example: postgresql://user:pass@cluster.cockroachdb.cloud:26257/hackcontrol?sslmode=require

NextAuth Configuration

NEXTAUTH_SECRET="your-secret-key-here"
NEXTAUTH_URL="http://localhost:3000"
  • NEXTAUTH_SECRET: Random string for JWT signing (use openssl rand -base64 32)
  • NEXTAUTH_URL: Your application's URL (production or development)

GitHub OAuth

GITHUB_CLIENT_ID="your-github-app-client-id"
GITHUB_CLIENT_SECRET="your-github-app-secret"
  • GITHUB_CLIENT_ID: Public identifier from your GitHub OAuth app
  • GITHUB_CLIENT_SECRET: Secret key from your GitHub OAuth app

Optional Variables

Node Environment

NODE_ENV="development"  # or "production" or "test"

Vercel Deployment

VERCEL_URL="your-app.vercel.app"  # Automatically set by Vercel

Environment Validation

The project uses Zod for runtime environment validation:

// Server-side validation
const server = z.object({
  DATABASE_URL: z.string().url(),
  NODE_ENV: z.enum(["development", "test", "production"]),
  NEXTAUTH_SECRET: z.string().min(1),
  NEXTAUTH_URL: z.string().url(),
  GITHUB_CLIENT_ID: z.string().min(1),
  GITHUB_CLIENT_SECRET: z.string().min(1),
});

Creating Environment Files

Development (.env)

DATABASE_URL="postgresql://user:pass@localhost:26257/hackcontrol_dev?sslmode=disable"
NEXTAUTH_SECRET="dev-secret-key-change-in-production"
NEXTAUTH_URL="http://localhost:3000"
GITHUB_CLIENT_ID="your-dev-github-client-id"
GITHUB_CLIENT_SECRET="your-dev-github-client-secret"
NODE_ENV="development"

Production (.env.production)

DATABASE_URL="postgresql://user:pass@cluster.cockroachdb.cloud:26257/hackcontrol?sslmode=require"
NEXTAUTH_SECRET="super-secure-production-secret"
NEXTAUTH_URL="https://your-domain.com"
GITHUB_CLIENT_ID="your-prod-github-client-id"
GITHUB_CLIENT_SECRET="your-prod-github-client-secret"
NODE_ENV="production"

Security Best Practices

  1. Never commit .env files to version control
  2. Use different OAuth apps for development and production
  3. Generate strong secrets using cryptographically secure methods
  4. Rotate secrets regularly in production
  5. Use environment-specific databases to avoid data conflicts

Generating Secrets

NEXTAUTH_SECRET

# Using OpenSSL
openssl rand -base64 32

# Using Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

Environment Validation Errors

Common validation errors and solutions:

  • Invalid URL format: Ensure DATABASE_URL and NEXTAUTH_URL are properly formatted URLs
  • Missing required variables: All variables in the server schema must be provided
  • Client-side access: Server variables cannot be accessed on the client side

Next Steps

Database Setup

Hackcontrol uses CockroachDB as its database, managed through Prisma ORM. This guide covers database setup and management.

CockroachDB Setup

1. Create a Free Cluster

  1. Visit CockroachDB Cloud
  2. Sign up for a free account
  3. Create a new cluster:
    • Choose "Serverless" for free tier
    • Select your preferred region
    • Name your cluster (e.g., "hackcontrol-dev")

2. Create Database User

  1. In your cluster dashboard, go to "SQL Users"
  2. Create a new user:
    • Username: hackcontrol_user (or your preference)
    • Generate a strong password
    • Save the credentials securely

3. Get Connection String

  1. Click "Connect" on your cluster
  2. Select "General connection string"
  3. Copy the connection string
  4. Replace <username> and <password> with your user credentials

Example connection string:

postgresql://hackcontrol_user:password@cluster-name.aws-region.cockroachlabs.cloud:26257/hackcontrol?sslmode=require

Database Schema

The application uses the following main models:

Core Models

  • Hackathon: Main hackathon events
  • Participation: User project submissions
  • User: User accounts and profiles
  • Judge: Judging assignments
  • Score: Judging scores
  • Announcement: Event announcements

Authentication Models

  • Account: OAuth account linking
  • Session: User sessions
  • VerificationToken: Email verification

Prisma Migration

Initial Setup

Run the initial migration to create all tables:

npx prisma migrate dev --name init

Schema Changes

When you modify prisma/schema.prisma:

# Create and apply migration
npx prisma migrate dev --name describe-your-change

# Generate Prisma client
npx prisma generate

Useful Commands

# View database in Prisma Studio
npm run studio

# Reset database (development only)
npx prisma migrate reset

# Deploy migrations (production)
npx prisma migrate deploy

# Check migration status
npx prisma migrate status

Database Administration

Setting Admin Role

After your first login, grant admin privileges:

  1. Open CockroachDB console or Prisma Studio
  2. Navigate to the users table
  3. Run this SQL command:
UPDATE users SET role = 'ADMIN' WHERE email = 'your-github-email@example.com';

Or using Prisma Studio:

  1. Open npm run studio
  2. Navigate to User model
  3. Find your user record
  4. Change role from USER to ADMIN
  5. Save changes

User Roles

The application supports three user roles:

  • USER: Default role, can participate in hackathons
  • ORGANIZER: Can create and manage hackathons
  • ADMIN: Full system access, can manage all hackathons and users

Backup and Recovery

Export Data

# Export schema
npx prisma db pull

# Export data (using pg_dump)
pg_dump $DATABASE_URL > backup.sql

Restore Data

# Restore from backup
psql $DATABASE_URL < backup.sql

Troubleshooting

Connection Issues

  • Verify connection string format
  • Check firewall settings
  • Ensure SSL mode is set correctly

Migration Errors

  • Check for schema conflicts
  • Verify database permissions
  • Review migration history

Performance

  • Monitor query performance in CockroachDB console
  • Use Prisma query logging for development
  • Consider database indexing for large datasets

Next Steps

Project Overview

Hackcontrol is a modern, full-stack hackathon management system built with cutting-edge web technologies.

What is Hackcontrol?

Hackcontrol is an open-source platform designed to streamline hackathon organization and participation. It provides tools for:

  • Event Management: Create, configure, and manage hackathon events
  • Participant Registration: Easy signup and project submission
  • Judging System: Structured evaluation and scoring
  • Team Management: Collaborative project development
  • Administrative Tools: User management and event oversight

Architecture Overview

Full-Stack TypeScript

The entire application is built with TypeScript, ensuring type safety from database to UI:

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Frontend      │    │   API Layer     │    │   Database      │
│   (Next.js)     │◄──►│   (tRPC)        │◄──►│   (CockroachDB) │
│   React + TS    │    │   TypeScript    │    │   + Prisma      │
└─────────────────┘    └─────────────────┘    └─────────────────┘

Key Architectural Decisions

Type-Safe API with tRPC

  • End-to-end type safety
  • Auto-generated TypeScript types
  • Client-server contract validation
  • Excellent developer experience

Prisma ORM

  • Type-safe database operations
  • Schema-first development
  • Auto-generated client
  • Migration management

Component-Based UI

  • Reusable UI components with Radix UI
  • Consistent design system
  • Accessibility built-in
  • Tailwind CSS for styling

Authentication Strategy

  • NextAuth.js for session management
  • GitHub OAuth integration
  • JWT-based sessions
  • Role-based access control

Core Features

For Participants

  • GitHub authentication
  • Project submission
  • Team collaboration
  • Progress tracking
  • Real-time updates

For Organizers

  • Hackathon creation and management
  • Participant oversight
  • Judge assignment
  • Announcement system
  • Event configuration

For Judges

  • Streamlined evaluation interface
  • Scoring system
  • Project review tools
  • Collaborative judging

For Administrators

  • User management
  • System-wide settings
  • Analytics and reporting
  • Platform maintenance

Technology Benefits

Developer Experience

  • Hot reloading for rapid development
  • Type safety prevents runtime errors
  • Auto-completion throughout the stack
  • Unified codebase with TypeScript

Performance

  • Static generation where possible
  • Incremental static regeneration
  • Optimized database queries with Prisma
  • Client-side caching with React Query

Scalability

  • Serverless-ready architecture
  • Database connection pooling
  • Horizontal scaling capabilities
  • CDN-optimized assets

Security

  • Environment variable validation
  • SQL injection prevention via Prisma
  • CSRF protection built-in
  • Secure authentication flows

Project Goals

  1. Simplicity: Easy to use for all stakeholders
  2. Reliability: Robust system for critical events
  3. Extensibility: Plugin-ready architecture
  4. Performance: Fast and responsive experience
  5. Accessibility: Inclusive design principles

Next Steps

Technology Stack

Hackcontrol is built with a modern, production-ready technology stack focused on type safety, developer experience, and performance.

Frontend Technologies

Next.js 15.4.6

  • Role: React framework and application foundation
  • Features: SSR, SSG, API routes, file-based routing
  • Benefits: Performance optimization, SEO, developer experience
  • Configuration: next.config.mjs

React 18.2.0

  • Role: UI library for component-based architecture
  • Features: Hooks, context, concurrent rendering
  • Benefits: Component reusability, virtual DOM, large ecosystem

TypeScript 5.1.6

  • Role: Static type checking and enhanced developer experience
  • Features: Type inference, interfaces, generics
  • Benefits: Catch errors at compile time, better IDE support
  • Configuration: tsconfig.json

Styling & UI

Tailwind CSS 3.3.3

  • Role: Utility-first CSS framework
  • Features: Responsive design, custom themes, JIT compilation
  • Benefits: Rapid development, consistent design, small bundle size
  • Configuration: tailwind.config.cjs

Radix UI Primitives

  • Components: Dialog, Tabs, Tooltip
  • Role: Unstyled, accessible UI components
  • Benefits: Accessibility compliance, keyboard navigation, ARIA support

clsx 2.0.0

  • Role: Conditional CSS class utility
  • Usage: Dynamic styling based on state/props
  • Benefits: Clean conditional styling patterns

Framer Motion 10.15.0

  • Role: Animation library for React
  • Features: Declarative animations, gesture handling
  • Usage: Page transitions, micro-interactions
  • Location: src/animations/

Backend & API

tRPC 10.36.0

  • Role: Type-safe API layer
  • Features: End-to-end type safety, auto-generated clients
  • Benefits: No code generation, excellent DX, runtime safety
  • Structure:
    • Router definitions in src/trpc/routers/
    • Client setup in src/trpc/api.ts

Prisma 5.1.0

  • Role: Database ORM and query builder
  • Features: Type-safe queries, migrations, schema management
  • Benefits: Auto-generated client, great TypeScript integration
  • Schema: prisma/schema.prisma

NextAuth.js 4.24.11

  • Role: Authentication solution
  • Features: Multiple providers, session management, security
  • Provider: GitHub OAuth
  • Configuration: src/lib/auth.ts

Database

CockroachDB

  • Role: Distributed SQL database
  • Features: Horizontal scaling, ACID compliance, PostgreSQL compatibility
  • Benefits: High availability, strong consistency, cloud-native
  • Connection: Via Prisma with PostgreSQL protocol

Data & Validation

Zod 3.25.76

  • Role: Schema validation library
  • Usage:
    • API input validation
    • Environment variable validation
    • Form validation
  • Location: src/schema/ and src/env/

Superjson 1.13.1

  • Role: JSON serialization with additional types
  • Features: Date, undefined, BigInt, Map, Set support
  • Usage: tRPC data transformation
  • Benefits: Preserves complex JavaScript types over network

React Hook Form 7.45.2

  • Role: Form state management
  • Features: Minimal re-renders, easy validation integration
  • Benefits: Performance, DX, Zod integration

Development Tools

ESLint 8.46.0

  • Role: Code linting and style enforcement
  • Configuration: .eslintrc.json
  • Extends: Next.js recommended config

Prettier 3.0.0

  • Role: Code formatting
  • Features: Opinionated formatting, Tailwind class sorting
  • Plugins: prettier-plugin-tailwindcss
  • Configuration: prettier.config.cjs

TypeScript ESNext

  • Target: ES5 for broad compatibility
  • Module: ESNext for optimal bundling
  • Features: Strict mode, path mapping (@/*)

Additional Libraries

UI Enhancement

  • Sonner 0.6.2: Toast notifications
  • Canvas Confetti 1.6.0: Celebration animations
  • @uiball/loaders 1.3.0: Loading indicators
  • Iconoir: SVG icon library

Utilities

  • nanoid 5.1.5: Unique ID generation
  • next-seo 6.1.0: SEO optimization
  • nextjs-progressbar 0.0.16: Page loading indicator

Package Manager

The project supports multiple package managers:

  • npm (default)
  • pnpm (recommended for speed)
  • yarn (classic)

All package managers work with the same package.json configuration.

Build Pipeline

Development

npm run dev          # Start dev server
npm run lint         # Run ESLint
npm run ts:check     # TypeScript compilation check
npm run format       # Format code with Prettier

Production

npm run build        # Build for production
npm run start        # Start production server

Database

npx prisma migrate dev    # Apply schema changes
npx prisma generate      # Generate Prisma client
npm run studio          # Open Prisma Studio

Architecture Benefits

  1. Type Safety: Full-stack TypeScript eliminates entire classes of runtime errors
  2. Developer Experience: Hot reloading, auto-completion, and excellent tooling
  3. Performance: Optimized builds, code splitting, and efficient rendering
  4. Maintainability: Clear separation of concerns and modular architecture
  5. Scalability: Serverless-ready components and efficient database layer

Next Steps

Project Structure

Understanding the project structure is essential for effective development and maintenance.

Root Directory

hackcontrol/
├── prisma/                 # Database schema and migrations
├── public/                 # Static assets (images, icons)
├── src/                    # Application source code
├── scripts/                # Build and utility scripts
├── .env                    # Environment variables (local)
├── .env.example           # Environment template
├── package.json           # Dependencies and scripts
├── next.config.mjs        # Next.js configuration
├── tailwind.config.cjs    # Tailwind CSS configuration
├── tsconfig.json          # TypeScript configuration
└── vercel.json           # Vercel deployment settings

Source Directory (src/)

Application Structure

src/
├── animations/            # Framer Motion animations
├── components/           # Reusable React components
├── env/                 # Environment validation
├── layout/              # App layout components
├── lib/                 # Utility libraries and configurations
├── pages/               # Next.js pages and API routes
├── schema/              # Zod validation schemas
├── styles/              # Global CSS and Tailwind styles
├── trpc/                # tRPC API setup and routers
├── types/               # TypeScript type definitions
└── ui/                  # UI component library

Detailed Breakdown

/prisma

prisma/
├── migrations/          # Auto-generated migration files
└── schema.prisma       # Database schema definition

Purpose: Database schema management and migrations

  • schema.prisma: Defines all models, relationships, and database configuration
  • migrations/: Version-controlled database changes

/src/animations

animations/
└── show.tsx            # Page transition animations

Purpose: Framer Motion animation components

  • Page transitions and micro-interactions
  • Consistent animation patterns across the app

/src/components

components/
├── announcementDisplay.tsx    # Announcement rendering
├── announcementManager.tsx    # Admin announcement tools
├── card.tsx                   # Generic card component
├── copy.tsx                   # Copy-to-clipboard utility
├── createNew.tsx              # New hackathon creation
├── deleteHackathon.tsx        # Hackathon deletion
├── editHackathon.tsx          # Hackathon editing
├── finishHackathon.tsx        # Event completion
├── hackathonCard.tsx          # Hackathon display card
├── hackathonInfo.tsx          # Hackathon details
├── judgeManager.tsx           # Judge management
├── judgesDashboard.tsx        # Judge interface
├── leaderboardDisplay.tsx     # Results display
├── loading.tsx                # Loading states
├── participationCard.tsx      # Participation display
├── scoringInterface.tsx       # Scoring tools
├── sendProject.tsx            # Project submission
├── userSearch.tsx             # User lookup
└── viewProject.tsx            # Project viewing

Purpose: Feature-specific React components

  • Business logic components
  • Form handling and data submission
  • Admin and user interfaces

/src/env

env/
└── index.mjs           # Environment variable validation

Purpose: Runtime environment validation using Zod

  • Validates all required environment variables
  • Type-safe environment access
  • Development vs production configurations

/src/layout

layout/
├── header.tsx          # Application header
└── footer.tsx          # Application footer (if exists)

Purpose: Layout components used across all pages

  • Navigation and branding
  • User authentication state
  • Consistent UI structure

/src/lib

lib/
├── auth.ts             # NextAuth configuration
└── prisma.ts           # Prisma client setup

Purpose: Core library configurations and utilities

  • auth.ts: NextAuth providers, callbacks, and session handling
  • prisma.ts: Database client initialization and global instance

/src/pages

pages/
├── api/
│   ├── auth/
│   │   └── [...nextauth].ts    # NextAuth API route
│   └── trpc/
│       └── [trpc].ts           # tRPC API handler
├── app/
│   ├── index.tsx               # Dashboard/app home
│   └── [url].tsx               # Individual hackathon pages
├── auth/
│   └── index.tsx               # Authentication page
├── send/
│   └── [url].tsx               # Project submission page
├── _app.tsx                    # App wrapper with providers
├── _document.tsx               # HTML document structure
└── index.tsx                   # Landing page

Purpose: Next.js file-based routing and API endpoints

  • pages/api/: Server-side API routes
  • pages/app/: Main application pages
  • pages/auth/: Authentication flow
  • pages/send/: Project submission

/src/schema

schema/
├── announcement.ts     # Announcement validation schemas
├── hackathon.ts       # Hackathon validation schemas
├── participation.ts   # Participation validation schemas
└── scoring.ts         # Scoring validation schemas

Purpose: Zod validation schemas for API inputs

  • Type-safe form validation
  • API request/response validation
  • Shared validation logic

/src/trpc

trpc/
├── routers/
│   ├── announcement.router.ts  # Announcement API
│   ├── hackathon.router.ts     # Hackathon API
│   ├── judge.router.ts         # Judge API
│   ├── participation.router.ts # Participation API
│   └── scoring.router.ts       # Scoring API
├── api.ts              # Client-side tRPC setup
├── index.ts            # tRPC initialization
└── root.ts             # Main router composition

Purpose: tRPC API layer with type-safe endpoints

  • routers/: Feature-specific API endpoints
  • api.ts: Client configuration with React Query
  • root.ts: Router composition and type exports

/src/types

types/
├── hackathon.ts        # Hackathon type definitions
├── participation.ts    # Participation type definitions
└── next-auth.d.ts      # NextAuth type extensions

Purpose: TypeScript type definitions and extensions

  • Database model types
  • API response types
  • Third-party library extensions

/src/ui

ui/
├── icons/              # SVG icon components
└── [components].tsx    # Reusable UI components

Purpose: Design system and UI component library

  • Built with Radix UI primitives
  • Styled with Tailwind CSS
  • Consistent design patterns

File Naming Conventions

Components

  • PascalCase for component files: HackathonCard.tsx
  • camelCase for utility functions: getUserSession.ts
  • kebab-case for pages: [hackathon-url].tsx

Directories

  • camelCase for source directories: src/components/
  • kebab-case for page directories: pages/api/auth/

Import Patterns

Path Aliases

// Configured in tsconfig.json
{
  "paths": {
    "@/*": ["./src/*"]
  }
}

Usage Examples

// Absolute imports using alias
import { api } from "@/trpc/api";
import { authOptions } from "@/lib/auth";
import { HackathonCard } from "@/components/hackathonCard";

// Relative imports for nearby files
import { hackathonSchema } from "../schema/hackathon";

Configuration Files

Core Configuration

  • next.config.mjs: Next.js framework settings
  • tailwind.config.cjs: Tailwind CSS customization
  • tsconfig.json: TypeScript compiler options
  • prisma/schema.prisma: Database schema

Development Tools

  • .eslintrc.json: Linting rules
  • prettier.config.cjs: Code formatting
  • .prettierignore: Files to skip formatting

Deployment

  • vercel.json: Vercel deployment configuration
  • .env.example: Environment variable template

Development Workflow

Adding New Features

  1. Define types in /src/types
  2. Create Zod schemas in /src/schema
  3. Build tRPC routers in /src/trpc/routers
  4. Develop UI components in /src/components
  5. Create pages in /src/pages

Database Changes

  1. Modify prisma/schema.prisma
  2. Run npx prisma migrate dev --name feature-name
  3. Update related types and schemas
  4. Test with npm run studio

Next Steps

Next.js Framework

Next.js serves as the foundation of Hackcontrol, providing the React framework with powerful features for production applications.

Next.js in Hackcontrol

Version & Configuration

  • Version: Next.js 15.4.6
  • Router: Pages Router (not App Router)
  • Configuration: next.config.mjs

Key Features Used

File-Based Routing

src/pages/
├── index.tsx              # / (Landing page)
├── auth/
│   └── index.tsx          # /auth (Sign in page)
├── app/
│   ├── index.tsx          # /app (Dashboard)
│   └── [url].tsx          # /app/[hackathon-url]
└── send/
    └── [url].tsx          # /send/[hackathon-url]

API Routes

src/pages/api/
├── auth/
│   └── [...nextauth].ts  # /api/auth/* (NextAuth)
└── trpc/
    └── [trpc].ts         # /api/trpc/* (tRPC handler)

Configuration

Next.js Config

// next.config.mjs
!process.env.SKIP_ENV_VALIDATION && (await import("./src/env/index.mjs"));

const nextConfig = {
  reactStrictMode: true,
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "avatars.githubusercontent.com",
        port: "",
        pathname: "/u/**",
      },
    ],
  },
};

export default nextConfig;

Key settings:

  • Environment validation: Loads and validates env vars at build time
  • React Strict Mode: Enabled for development safety
  • Image optimization: Configured for GitHub avatars

TypeScript Integration

// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "strict": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

App Structure

Root App Component

// src/pages/_app.tsx
const App: AppType<{ session: Session | null }> = ({
  Component,
  pageProps: { session, ...pageProps },
  router,
}) => {
  return (
    <SessionProvider session={session}>
      <DefaultSeo {...nextSeoConfig} />
      <NextNProgress />
      <main className="font-sans">
        <Header />
        <Show routerKey={router.route}>
          <Component {...pageProps} />
        </Show>
        <Toaster />
      </main>
    </SessionProvider>
  );
};

export default api.withTRPC(App);

Features integrated:

  • SessionProvider: NextAuth session context
  • DefaultSeo: SEO configuration
  • NextNProgress: Page loading indicator
  • Show: Page transition animations
  • Toaster: Toast notifications (Sonner)
  • api.withTRPC: tRPC client wrapper

Document Structure

// src/pages/_document.tsx
// Custom document for HTML structure and font loading

Page Examples

Dynamic Routes

// src/pages/app/[url].tsx
import { type GetServerSideProps } from "next";
import { getServerAuthSession } from "@/lib/auth";

export default function HackathonPage() {
  const router = useRouter();
  const hackathonUrl = router.query.url as string;
  
  const { data: hackathon, isLoading } = api.hackathon.getByUrl.useQuery({
    url: hackathonUrl,
  });

  if (isLoading) return <Loading />;
  if (!hackathon) return <NotFound />;

  return <HackathonDetails hackathon={hackathon} />;
}

export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const session = await getServerAuthSession(ctx);
  
  return {
    props: {
      session,
    },
  };
};

API Route Handler

// src/pages/api/trpc/[trpc].ts
import { createNextApiHandler } from "@trpc/server/adapters/next";
import { appRouter } from "@/trpc/root";
import { createTRPCContext } from "@/trpc";

export default createNextApiHandler({
  router: appRouter,
  createContext: createTRPCContext,
  onError: env.NODE_ENV === "development"
    ? ({ path, error }) => {
        console.error(`❌ tRPC failed on ${path}: ${error.message}`);
      }
    : undefined,
});

Features & Integrations

Server-Side Rendering (SSR)

// For pages that need server-side data
export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const session = await getServerAuthSession(ctx);
  
  // Pre-fetch data on server
  const helpers = createServerSideHelpers({
    router: appRouter,
    ctx: { session, prisma },
    transformer: superjson,
  });

  await helpers.hackathon.getByUrl.prefetch({ url: ctx.params?.url as string });

  return {
    props: {
      trpcState: helpers.dehydrate(),
      session,
    },
  };
};

Image Optimization

// GitHub avatar optimization
<Image
  src={user.image}
  alt={user.name}
  width={40}
  height={40}
  className="rounded-full"
/>

SEO Configuration

// next-seo.config.ts
export const nextSeoConfig: DefaultSeoProps = {
  title: "Hackcontrol",
  description: "Open-source hackathon management system",
  openGraph: {
    type: "website",
    locale: "en_US",
    url: "https://hackcontrol.vercel.app/",
    siteName: "Hackcontrol",
  },
};

Performance Features

Automatic Code Splitting

  • Each page is automatically split into separate bundles
  • Reduces initial bundle size
  • Improves load times

Static Optimization

  • Static pages are pre-rendered at build time
  • Dynamic pages use SSR when needed
  • Optimal performance for different content types

Image Optimization

  • Automatic image optimization and resizing
  • WebP format support
  • Lazy loading by default

Development Features

Hot Reloading

  • Instant feedback during development
  • Preserves component state when possible
  • Fast refresh for React components

Error Overlay

  • Detailed error information in development
  • Source map support for debugging
  • Stack trace with exact file locations

TypeScript Integration

  • Zero-config TypeScript support
  • Fast compilation with SWC
  • Built-in type checking

Build Process

Development Build

npm run dev
  • Fast refresh enabled
  • Source maps for debugging
  • Detailed error reporting

Production Build

npm run build
npm run start
  • Optimized bundles
  • Static asset optimization
  • Performance optimizations

Environment Handling

Environment Variables

// Automatic environment validation
!process.env.SKIP_ENV_VALIDATION && (await import("./src/env/index.mjs"));

Runtime Configuration

  • Client vs server environment separation
  • Secure environment variable access
  • Build-time validation

Deployment

Vercel Integration

// vercel.json
{
  "functions": {
    "src/pages/api/**/*.ts": {
      "maxDuration": 30
    }
  }
}

Static Assets

  • Automatic optimization
  • CDN distribution
  • Caching headers

Best Practices

Page Organization

  • Group related pages in directories
  • Use meaningful file names
  • Implement proper error boundaries

Performance

  • Minimize client-side JavaScript
  • Use dynamic imports for large components
  • Optimize images and assets

SEO

  • Implement proper meta tags
  • Use semantic HTML structure
  • Optimize for Core Web Vitals

Routing Patterns

Dynamic Routes

// [url].tsx - Single dynamic segment
// [...slug].tsx - Catch-all routes
// [[...slug]].tsx - Optional catch-all

Programmatic Navigation

import { useRouter } from "next/router";

const router = useRouter();

// Navigate to different pages
router.push("/app");
router.push(`/app/${hackathon.url}`);
router.replace("/auth"); // Replace instead of push

Next Steps

tRPC API Layer

tRPC provides end-to-end type safety for Hackcontrol's API layer, eliminating the need for manual API type definitions.

What is tRPC?

tRPC is a library that allows you to build fully type-safe APIs without schemas or code generation. It leverages TypeScript's type system to provide:

  • End-to-end type safety: From database to UI
  • Auto-completion: IDE support for API calls
  • Runtime safety: Type validation at runtime
  • Excellent DX: No manual type synchronization

API Architecture

Frontend → API Flow

  1. Frontend calls tRPC: api.hackathon.getAll.useQuery()
  2. tRPC routes to Next.js: All calls go to /api/trpc/[trpc].ts
  3. Single handler routes internally: The [trpc].ts file routes to the correct tRPC router
  4. Router executes: The specific router (e.g., hackathon.router.ts) handles the request
Frontend Component
       ↓
api.hackathon.getAll.useQuery()
       ↓
/api/trpc/[trpc].ts (Next.js API route)
       ↓
hackathon.router.ts (tRPC router)
       ↓
Prisma → CockroachDB

Router Structure

// src/trpc/root.ts
export const appRouter = createTRPCRouter({
  hackathon: hackathonRouter,      // Hackathon management
  participation: participationRouter, // Project submissions
  announcement: announcementRouter,   // Event announcements
  judge: judgeRouter,                // Judge management
  scoring: scoringRouter,            // Scoring system
});

Client Configuration

tRPC Client Setup

// src/trpc/api.ts
export const api = createTRPCNext<AppRouter>({
  config() {
    return {
      transformer: superjson,  // Handle Date, undefined, etc.
      links: [
        loggerLink({
          enabled: (opts) =>
            process.env.NODE_ENV === "development" ||
            (opts.direction === "down" && opts.result instanceof Error),
        }),
        httpBatchLink({
          url: `${getBaseUrl()}/api/trpc`,
        }),
      ],
    };
  },
  ssr: false,  // Client-side rendering
});

App Integration

// src/pages/_app.tsx
const App: AppType<{ session: Session | null }> = ({ Component, pageProps }) => {
  return (
    <SessionProvider session={pageProps.session}>
      <Component {...pageProps} />
    </SessionProvider>
  );
};

export default api.withTRPC(App);  // Wrap app with tRPC

Router Examples

Public Procedures

// Anyone can call these
export const hackathonRouter = createTRPCRouter({
  getAll: publicProcedure
    .query(async ({ ctx }) => {
      return ctx.prisma.hackathon.findMany({
        include: {
          participations: true,
          owner: true,
        },
      });
    }),

  getByUrl: publicProcedure
    .input(z.object({ url: z.string() }))
    .query(async ({ ctx, input }) => {
      return ctx.prisma.hackathon.findUnique({
        where: { url: input.url },
      });
    }),
});

Protected Procedures

// Requires authentication
create: protectedProcedure
  .input(hackathonSchema)
  .mutation(async ({ ctx, input }) => {
    return ctx.prisma.hackathon.create({
      data: {
        ...input,
        creatorId: ctx.session.user.id,
      },
    });
  }),

delete: protectedProcedure
  .input(z.object({ id: z.string() }))
  .mutation(async ({ ctx, input }) => {
    // Check ownership or admin role
    const hackathon = await ctx.prisma.hackathon.findUnique({
      where: { id: input.id },
    });
    
    if (hackathon?.creatorId !== ctx.session.user.id && 
        ctx.session.user.role !== "ADMIN") {
      throw new TRPCError({ code: "FORBIDDEN" });
    }

    return ctx.prisma.hackathon.delete({
      where: { id: input.id },
    });
  }),

Usage in Components

Queries (Data Fetching)

// Simple query
const { data: hackathons, isLoading, error } = api.hackathon.getAll.useQuery();

// Query with parameters
const { data: hackathon } = api.hackathon.getByUrl.useQuery({ 
  url: router.query.url as string 
});

// Conditional queries
const { data: scores } = api.scoring.getScores.useQuery(
  { participationId: participation.id },
  { enabled: !!participation.id }  // Only run if ID exists
);

Mutations (Data Changes)

const utils = api.useContext();

const createHackathon = api.hackathon.create.useMutation({
  onSuccess: (newHackathon) => {
    // Invalidate and refetch
    utils.hackathon.getAll.invalidate();
    
    // Or optimistic update
    utils.hackathon.getAll.setData(undefined, (old) => 
      old ? [...old, newHackathon] : [newHackathon]
    );

    toast.success("Hackathon created!");
    router.push(`/app/${newHackathon.url}`);
  },
  onError: (error) => {
    toast.error(error.message);
  },
});

// Usage in form handler
const handleSubmit = (data: HackathonInput) => {
  createHackathon.mutate(data);
};

Authentication Context

Context Creation

// src/trpc/index.ts
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
  const { req, res } = opts;
  
  // Get session from NextAuth
  const session = await getServerAuthSession({ req, res });

  return {
    session,
    prisma,
  };
};

Using Session in Procedures

// Access current user
const currentUser = ctx.session?.user;

// Check authentication
if (!ctx.session) {
  throw new TRPCError({ code: "UNAUTHORIZED" });
}

// Check admin role
if (ctx.session.user.role !== "ADMIN") {
  throw new TRPCError({ code: "FORBIDDEN" });
}

Data Validation

Input Schemas

// Using Zod schemas from src/schema/
import { hackathonSchema } from "@/schema/hackathon";

create: protectedProcedure
  .input(hackathonSchema)  // Validates input automatically
  .mutation(async ({ ctx, input }) => {
    // input is fully typed and validated
    return ctx.prisma.hackathon.create({ data: input });
  }),

Custom Validation

getParticipations: protectedProcedure
  .input(z.object({
    hackathonId: z.string().cuid(),
    limit: z.number().min(1).max(100).default(10),
    cursor: z.string().optional(),
  }))
  .query(async ({ ctx, input }) => {
    // Pagination example
    return ctx.prisma.participation.findMany({
      where: { hackathonId: input.hackathonId },
      take: input.limit + 1,
      cursor: input.cursor ? { id: input.cursor } : undefined,
    });
  }),

Error Handling

Server Errors

// Standard tRPC error codes
throw new TRPCError({ code: "NOT_FOUND" });      // 404
throw new TRPCError({ code: "UNAUTHORIZED" });    // 401
throw new TRPCError({ code: "FORBIDDEN" });       // 403
throw new TRPCError({ code: "BAD_REQUEST" });     // 400
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" }); // 500

// With custom messages
throw new TRPCError({
  code: "NOT_FOUND",
  message: "Hackathon not found or access denied",
});

Client Error Handling

const { data, error, isError, isLoading } = api.hackathon.getById.useQuery({ id });

if (isError) {
  // Handle specific error types
  if (error.data?.code === "NOT_FOUND") {
    return <NotFoundPage />;
  }
  if (error.data?.code === "UNAUTHORIZED") {
    router.push("/auth");
    return null;
  }
  return <ErrorDisplay message={error.message} />;
}

Performance Optimization

Request Batching

tRPC automatically batches multiple requests:

// These will be batched into a single HTTP request
const hackathons = api.hackathon.getAll.useQuery();
const announcements = api.announcement.getAll.useQuery();
const userProfile = api.user.getProfile.useQuery();

Caching Strategies

// Cache indefinitely for static data
const { data } = api.hackathon.getById.useQuery(
  { id },
  { 
    staleTime: Infinity,
    cacheTime: Infinity,
  }
);

// Background refetch for dynamic data
const { data } = api.participation.getAll.useQuery(
  undefined,
  { 
    refetchInterval: 30000,  // Refetch every 30 seconds
    refetchOnWindowFocus: true,
  }
);

Type Inference

Router Types

// Auto-generated type helpers
import type { RouterInputs, RouterOutputs } from "@/trpc/api";

// Input types
type HackathonCreateInput = RouterInputs["hackathon"]["create"];
type ParticipationUpdateInput = RouterInputs["participation"]["update"];

// Output types  
type HackathonData = RouterOutputs["hackathon"]["getById"];
type ParticipationList = RouterOutputs["participation"]["getAll"];

Best Practices

Router Organization

  • One router per feature domain
  • Keep procedures focused and single-purpose
  • Use consistent naming conventions

Input Validation

  • Always validate inputs with Zod
  • Reuse schemas between routers
  • Provide meaningful error messages

Authentication

  • Use middleware for common auth logic
  • Implement role-based access control
  • Handle unauthorized access gracefully

Error Handling

  • Use appropriate tRPC error codes
  • Provide user-friendly messages
  • Log errors for debugging

Next Steps

Prisma ORM

Prisma serves as the database layer for Hackcontrol, providing type-safe database operations and schema management.

What is Prisma?

Prisma is a next-generation ORM that provides:

  • Type-safe database access
  • Auto-generated client
  • Schema-first development
  • Migration management
  • Query optimization

Database Configuration

Schema Definition

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "cockroachdb"
  url      = env("DATABASE_URL")
}

Client Setup

// src/lib/prisma.ts
import { PrismaClient } from "@prisma/client";
import { env } from "@/env/index.mjs";

export const prisma =
  global.prisma ||
  new PrismaClient({
    log: env.NODE_ENV === "development" 
      ? ["query", "error", "warn"] 
      : ["error"],
  });

if (env.NODE_ENV !== "production") {
  global.prisma = prisma;
}

Core Models

Hackathon Model

model Hackathon {
  id                   String          @id @default(cuid())
  name                 String
  url                  String          @unique
  description          String?
  rules                String?
  criteria             String?
  owner                User?           @relation(fields: [creatorId], references: [id])
  creatorId            String
  updatedAt            DateTime        @default(now()) @updatedAt
  is_finished          Boolean         @default(false)
  verified             Boolean         @default(false)
  min_judges_required  Int             @default(2)
  participations       Participation[]
  announcements        Announcement[]
  Judge                Judge[]

  @@unique([url, creatorId])
  @@map(name: "hackathons")
}

User Model

model User {
  id               String          @id @default(cuid())
  name             String?
  username         String?         @unique
  email            String?         @unique
  emailVerified    DateTime?
  image            String?
  role             Role            @default(USER)
  access           Boolean         @default(true)
  accounts         Account[]
  sessions         Session[]
  hackathons       Hackathon[]
  participations   Participation[]
  judgedHackathons Judge[]
  invitedJudges    Judge[]         @relation("JudgeInviter")

  @@map(name: "users")
}

Participation Model

model Participation {
  id             String     @id @default(cuid())
  is_reviewed    Boolean    @default(false)
  is_winner      Boolean    @default(false)
  title          String
  description    String
  project_url    String
  hackathon_name String
  hackathon_url  String
  createdBy      User?      @relation(fields: [creatorId], references: [id])
  creatorId      String
  creatorName    String
  createdAt      DateTime   @default(now())
  updatedAt      DateTime   @updatedAt
  hackathon      Hackathon? @relation(fields: [hackathon_url], references: [url], onDelete: Cascade)
  scores         Score[]
  team_members   Json?

  @@unique([creatorId, hackathon_url])
  @@map(name: "participations")
}

Usage in tRPC Routers

Basic Queries

// src/trpc/routers/hackathon.router.ts
export const hackathonRouter = createTRPCRouter({
  getAll: publicProcedure
    .query(async ({ ctx }) => {
      return ctx.prisma.hackathon.findMany({
        include: {
          participations: {
            include: {
              createdBy: true,
            },
          },
          owner: true,
        },
        orderBy: {
          updatedAt: "desc",
        },
      });
    }),

  getByUrl: publicProcedure
    .input(z.object({ url: z.string() }))
    .query(async ({ ctx, input }) => {
      const hackathon = await ctx.prisma.hackathon.findUnique({
        where: { url: input.url },
        include: {
          participations: {
            include: {
              createdBy: true,
              scores: true,
            },
          },
          announcements: {
            orderBy: { createdAt: "desc" },
          },
          Judge: {
            include: {
              user: true,
            },
          },
        },
      });

      if (!hackathon) {
        throw new TRPCError({
          code: "NOT_FOUND",
          message: "Hackathon not found",
        });
      }

      return hackathon;
    }),
});

Mutations with Validation

create: protectedProcedure
  .input(hackathonSchema)
  .mutation(async ({ ctx, input }) => {
    // Check if URL is unique
    const existing = await ctx.prisma.hackathon.findUnique({
      where: { url: input.url },
    });

    if (existing) {
      throw new TRPCError({
        code: "CONFLICT",
        message: "Hackathon URL already exists",
      });
    }

    return ctx.prisma.hackathon.create({
      data: {
        ...input,
        creatorId: ctx.session.user.id,
      },
      include: {
        owner: true,
      },
    });
  }),

Advanced Prisma Features

Transactions

// Atomic operations
updateWithScores: protectedProcedure
  .input(participationUpdateSchema)
  .mutation(async ({ ctx, input }) => {
    return ctx.prisma.$transaction(async (tx) => {
      // Update participation
      const participation = await tx.participation.update({
        where: { id: input.id },
        data: input.data,
      });

      // Update related scores
      await tx.score.updateMany({
        where: { participationId: input.id },
        data: { updatedAt: new Date() },
      });

      return participation;
    });
  }),

Aggregations

getStats: publicProcedure
  .input(z.object({ hackathonId: z.string() }))
  .query(async ({ ctx, input }) => {
    const stats = await ctx.prisma.participation.aggregate({
      where: { hackathon: { id: input.hackathonId } },
      _count: { id: true },
      _avg: { scores: { score: true } },
    });

    return {
      totalParticipations: stats._count.id,
      averageScore: stats._avg.scores?.score || 0,
    };
  }),

Raw Queries

// For complex queries not easily expressed in Prisma
getLeaderboard: publicProcedure
  .input(z.object({ hackathonUrl: z.string() }))
  .query(async ({ ctx, input }) => {
    return ctx.prisma.$queryRaw`
      SELECT 
        p.id,
        p.title,
        p."creatorName",
        AVG(s.score)::float as average_score,
        COUNT(s.id) as judge_count
      FROM participations p
      LEFT JOIN scores s ON p.id = s."participationId"
      WHERE p.hackathon_url = ${input.hackathonUrl}
      GROUP BY p.id, p.title, p."creatorName"
      ORDER BY average_score DESC NULLS LAST
    `;
  }),

Migration Management

Development Workflow

# Modify prisma/schema.prisma
# Then create migration
npx prisma migrate dev --name add-team-system

# This will:
# 1. Generate migration SQL
# 2. Apply to database  
# 3. Regenerate Prisma client

Production Deployment

# Deploy migrations to production
npx prisma migrate deploy

# Generate client (usually in build process)
npx prisma generate

Schema Changes

When adding new fields:

model Participation {
  // ... existing fields
  team_members   Json?           // New field
  submission_url String?         // Another new field
}

Then run:

npx prisma migrate dev --name add-team-features

Database Tools

Prisma Studio

npm run studio

Visual database browser for:

  • Viewing and editing data
  • Testing queries
  • Managing relationships
  • Debugging data issues

Query Logging

Enable in development:

const prisma = new PrismaClient({
  log: ["query", "error", "warn"],  // See all SQL queries
});

Best Practices

Model Design

  • Use descriptive field names
  • Add appropriate indexes for performance
  • Define proper relationships
  • Use enums for constrained values

Query Optimization

  • Include only needed relations
  • Use select for specific fields
  • Implement pagination for large datasets
  • Consider database indexes

Error Handling

  • Check for null results
  • Handle unique constraint violations
  • Provide meaningful error messages
  • Use transactions for complex operations

Security

  • Validate all inputs
  • Check user permissions
  • Sanitize user data
  • Use parameterized queries (automatic with Prisma)

Common Patterns

Finding with Relations

const hackathonWithData = await prisma.hackathon.findUnique({
  where: { url: hackathonUrl },
  include: {
    participations: {
      include: {
        createdBy: true,
        scores: {
          include: {
            judge: {
              include: {
                user: true,
              },
            },
          },
        },
      },
    },
    announcements: true,
  },
});

Creating with Relations

const participation = await prisma.participation.create({
  data: {
    title: input.title,
    description: input.description,
    project_url: input.projectUrl,
    hackathon: {
      connect: { url: input.hackathonUrl },
    },
    createdBy: {
      connect: { id: ctx.session.user.id },
    },
  },
  include: {
    hackathon: true,
    createdBy: true,
  },
});

Next Steps

NextAuth.js Authentication

NextAuth.js provides secure, flexible authentication for Hackcontrol with GitHub OAuth integration.

Authentication Setup

Configuration

// src/lib/auth.ts
export const authOptions: NextAuthOptions = {
  // Session strategy
  session: {
    strategy: "jwt",
  },
  
  // Database adapter
  adapter: PrismaAdapter(prisma),
  
  // OAuth providers
  providers: [
    GithubProvider({
      clientId: env.GITHUB_CLIENT_ID,
      clientSecret: env.GITHUB_CLIENT_SECRET,
      profile(profile) {
        return {
          id: profile.id.toString(),
          name: profile.name || profile.login,
          username: profile.login,
          email: profile.email,
          image: profile.avatar_url,
        };
      },
    }),
  ],
  
  // Custom pages
  pages: {
    signIn: "/auth",
  },
};

API Route

// src/pages/api/auth/[...nextauth].ts
import { authOptions } from "@/lib/auth";
import NextAuth from "next-auth";

export default NextAuth(authOptions);

Authentication Flow

1. User Access

User visits protected page
       ↓
Check session status
       ↓
No session? → Redirect to /auth
       ↓
Session exists? → Allow access

2. GitHub OAuth Flow

Click "Sign in with GitHub"
       ↓
Redirect to GitHub OAuth
       ↓
User authorizes application
       ↓
GitHub redirects back with code
       ↓
NextAuth exchanges code for user data
       ↓
Create/update user in database
       ↓
Generate JWT session token
       ↓
Set session cookie

Session Management

JWT Strategy

// JWT callbacks
callbacks: {
  async jwt({ token, user }) {
    const dbUser = await prisma.user.findFirst({
      where: { email: token.email },
    });
    
    if (!dbUser) {
      token.id = user?.id;
      token.role = "USER";
      return token;
    }
    
    return {
      id: dbUser.id,
      name: dbUser.name,
      username: dbUser.username,
      email: dbUser.email,
      image: dbUser.image,
      role: dbUser.role || "USER",
    };
  },
  
  async session({ token, session }) {
    if (token) {
      session.user.id = token.id;
      session.user.name = token.name;
      session.user.username = token.username;
      session.user.email = token.email;
      session.user.image = token.image;
      session.user.role = token.role;
    }
    return session;
  },
},

Server-Side Session Access

// In API routes or getServerSideProps
export const getServerAuthSession = async (ctx: {
  req: GetServerSidePropsContext["req"];
  res: GetServerSidePropsContext["res"];
}) => {
  return await getServerSession(ctx.req, ctx.res, authOptions);
};

// Usage in pages
export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const session = await getServerAuthSession(ctx);
  
  if (!session) {
    return {
      redirect: {
        destination: "/auth",
        permanent: false,
      },
    };
  }
  
  return { props: { session } };
};

Client-Side Usage

Session Provider

// src/pages/_app.tsx
import { SessionProvider } from "next-auth/react";

export default function App({ session, ...appProps }) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  );
}

Using Sessions in Components

import { useSession, signIn, signOut } from "next-auth/react";

export default function Header() {
  const { data: session, status } = useSession();

  if (status === "loading") return <Loading />;

  if (session) {
    return (
      <div>
        <p>Signed in as {session.user.email}</p>
        <img src={session.user.image} alt="Profile" />
        <button onClick={() => signOut()}>Sign out</button>
      </div>
    );
  }

  return (
    <div>
      <p>Not signed in</p>
      <button onClick={() => signIn("github")}>Sign in with GitHub</button>
    </div>
  );
}

Protected Routes

Page-Level Protection

// Higher-order component for protection
export function withAuth<P extends {}>(Component: React.ComponentType<P>) {
  return function AuthenticatedComponent(props: P) {
    const { data: session, status } = useSession();
    const router = useRouter();

    useEffect(() => {
      if (status === "loading") return;
      if (!session) router.push("/auth");
    }, [session, status, router]);

    if (status === "loading") return <Loading />;
    if (!session) return null;

    return <Component {...props} />;
  };
}

// Usage
export default withAuth(function ProtectedPage() {
  return <div>This page requires authentication</div>;
});

API Route Protection

// tRPC protected procedure
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
  if (!ctx.session || !ctx.session.user) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }
  return next({
    ctx: {
      session: { ...ctx.session, user: ctx.session.user },
    },
  });
});

Role-Based Access Control

User Roles

// prisma/schema.prisma
enum Role {
  ADMIN
  ORGANIZER
  USER
}

model User {
  role Role @default(USER)
  // ... other fields
}

Role Checks

// tRPC admin procedure
export const adminProcedure = protectedProcedure.use(({ ctx, next }) => {
  if (ctx.session.user.role !== "ADMIN") {
    throw new TRPCError({ 
      code: "FORBIDDEN",
      message: "Admin access required" 
    });
  }
  return next({ ctx });
});

// Component role checking
function AdminPanel() {
  const { data: session } = useSession();
  
  if (session?.user.role !== "ADMIN") {
    return <div>Access denied</div>;
  }
  
  return <div>Admin content</div>;
}

Custom Auth Pages

Sign In Page

// src/pages/auth/index.tsx
import { getProviders, signIn, getSession } from "next-auth/react";
import { GetServerSideProps } from "next";

export default function SignIn({ providers }) {
  return (
    <div>
      <h1>Sign in to Hackcontrol</h1>
      {Object.values(providers).map((provider) => (
        <div key={provider.name}>
          <button onClick={() => signIn(provider.id)}>
            Sign in with {provider.name}
          </button>
        </div>
      ))}
    </div>
  );
}

export const getServerSideProps: GetServerSideProps = async (context) => {
  const session = await getSession(context);
  
  if (session) {
    return { redirect: { destination: "/app" } };
  }

  const providers = await getProviders();
  return { props: { providers } };
};

Database Integration

Prisma Adapter Models

// NextAuth required models
model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String?
  access_token      String?
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String?
  session_state     String?
  user              User    @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

GitHub OAuth Setup

1. Create GitHub OAuth App

  1. Go to GitHub Settings > Developer settings > OAuth Apps
  2. Click "New OAuth App"
  3. Fill in details:
    • Application name: "Hackcontrol"
    • Homepage URL: "http://localhost:3000" (dev) or your domain
    • Authorization callback URL: "http://localhost:3000/api/auth/callback/github"

2. Environment Variables

GITHUB_CLIENT_ID="your-client-id"
GITHUB_CLIENT_SECRET="your-client-secret"

3. Production Setup

For production, update:

  • Homepage URL to your domain
  • Callback URL to https://yourdomain.com/api/auth/callback/github

Security Features

CSRF Protection

  • Built-in CSRF protection
  • Automatic token validation
  • Secure cookie handling

Session Security

// Secure session configuration
session: {
  strategy: "jwt",
  maxAge: 30 * 24 * 60 * 60, // 30 days
  updateAge: 24 * 60 * 60,    // 24 hours
},
cookies: {
  sessionToken: {
    name: "next-auth.session-token",
    options: {
      httpOnly: true,
      sameSite: "lax",
      path: "/",
      secure: process.env.NODE_ENV === "production",
    },
  },
},

Troubleshooting

Common Issues

  1. Callback URL Mismatch

    • Ensure GitHub OAuth app callback URL matches exactly
    • Include protocol (http/https)
  2. Environment Variables

    • Verify GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET
    • Check NEXTAUTH_SECRET is set
  3. Database Issues

    • Ensure Prisma schema includes NextAuth models
    • Run database migrations

Debug Mode

NEXTAUTH_DEBUG=true  # Enable detailed logging

Best Practices

Security

  • Always use HTTPS in production
  • Set secure environment variables
  • Implement proper role-based access
  • Regularly rotate secrets

User Experience

  • Provide clear sign-in/out flows
  • Handle loading states gracefully
  • Show appropriate error messages
  • Implement session persistence

Performance

  • Use JWT for stateless sessions
  • Cache user roles appropriately
  • Minimize database queries in callbacks

Next Steps