diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 74fe1f91e..68116b044 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,5 +70,6 @@ Try to keep CI green! To do this, please ensure the following: - Your code is formatted correctly (run `pnpm format`) - Your code passes linting (run `pnpm lint`) - Your code is type-checked (run `pnpm typecheck`) +- Your code is able to build (run `pnpm build`) Most importantly, make sure your code is well-documented and easy to understand! diff --git a/README.md b/README.md index 0efcc2487..b2e124bff 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,13 @@ Welcome to Knight Hacks' monorepo, Forge! It contains the source code for all applications created by our development team! +New to the project? Start with our [Getting Started Guide](/docs/GETTING-STARTED.md). + ## 🗺 Layout - [`.github`](/.github) Contains all of our GitHub-related files - [`apps`](/apps) Applications we've built and maintain +- [`docs`](/docs) Documentation for contributors and developers - [`packages`](/packages) Packages we share across our applications - [`tooling`](/tooling) Configuration files for tools we use when building our applications - [`turbo/generators`](/turbo/generators) A CLI tool to generate boilerplate for a new package under [`packages`](/packages) diff --git a/docs/API-AND-PERMISSIONS.md b/docs/API-AND-PERMISSIONS.md new file mode 100644 index 000000000..279571374 --- /dev/null +++ b/docs/API-AND-PERMISSIONS.md @@ -0,0 +1,470 @@ +# API / Permissions Development Guide + +This guide covers how to work with the tRPC API in Forge, including our permission system, procedure types, and common patterns. + +## tRPC Procedures + +We have four types of procedures. Choose the right one based on authentication and permission requirements. + +### `publicProcedure` + +Use when the endpoint doesn't require authentication. + +```typescript +export const myRouter = { + getPublicData: publicProcedure + .input(z.object({ id: z.string() })) + .query(async ({ input }) => { + // Anyone can call this + return await db.query.SomeTable.findFirst({ + where: eq(SomeTable.id, input.id), + }); + }), +}; +``` + +**When to use:** Public data that anyone can access without logging in. + +### `protectedProcedure` + +Use when the user must be signed in, but no specific permissions are required. + +```typescript +export const myRouter = { + getUserProfile: protectedProcedure + .input(z.object({ userId: z.string() })) + .query(async ({ input, ctx }) => { + // ctx.session.user is guaranteed to exist + return await db.query.User.findFirst({ + where: eq(User.id, input.userId), + }); + }), +}; +``` + +**When to use:** Any feature that requires authentication but is available to all logged-in users. + +### `permProcedure` + +Use when specific permissions are required. This procedure automatically loads the user's permissions into `ctx.session.permissions`. + +```typescript +import { controlPerms } from "../utils"; + +export const myRouter = { + deleteEvent: permProcedure + .input(z.object({ eventId: z.string() })) + .mutation(async ({ input, ctx }) => { + // Check if user has the required permission + controlPerms.or(["MANAGE_EVENTS"], ctx); + + return await db.delete(Events).where(eq(Events.id, input.eventId)); + }), +}; +``` + +**When to use:** Admin features or actions that require specific permissions. + +### `judgeProcedure` + +**Status:** Deprecated and will be removed in the future. Use `permProcedure` with appropriate permissions instead. + +## Permission System + +Forge uses a custom role-based permission system that syncs with Discord roles. + +### How Permissions Work + +Permissions are stored as a bit string (e.g., `"111010"`). Each position represents a specific permission: + +- `1` = user has the permission +- `0` = user doesn't have the permission + +The mapping is defined in `@forge/consts/knight-hacks` in the `PERMISSIONS` object. Each permission has a unique index number that is used to store the permission in the database. For example, the `IS_OFFICER` permission has an index of `0`. This means that a user with a permission string of `"10000000000000000000"` has the `IS_OFFICER` permission. + +### Permission Checking + +Use the `controlPerms` middleware from `@forge/api/src/utils`: + +#### `controlPerms.or()` + +Returns true if the user has **any** of the required permissions. + +```typescript +// User needs at least ONE of these permissions +controlPerms.or(["MANAGE_EVENTS", "MANAGE_MEMBERS"], ctx); +``` + +**Special behavior:** If the user has the `IS_OFFICER` permission, they automatically pass all permission checks. + +#### `controlPerms.and()` + +Returns true only if the user has **all** of the required permissions. + +```typescript +// User needs ALL of these permissions +controlPerms.and(["MANAGE_EVENTS", "DELETE_EVENTS"], ctx); +``` + +**Special behavior:** If the user has the `IS_OFFICER` permission, they automatically pass all permission checks. + +### Permission Gating for Pages + +For admin pages, use the permissions router to check if a user can access a page: + +```typescript +// Example: Gate a page with OR logic +// If someone has edit rights, they need to see the page +// Same is true for read-only access +export const pageRouter = { + canAccessEventsPage: permProcedure + .query(async ({ ctx }) => { + // Will throw UNAUTHORIZED if they don't have either permission + controlPerms.or(["VIEW_EVENTS", "MANAGE_EVENTS"], ctx); + return { canAccess: true }; + }), +}; +``` + +**Pattern:** We typically use OR logic for page gating. If someone can edit, they need to see the page. If someone can only read, they also need to see the page. + +### Discord Role Syncing + +Permissions are based on Discord roles: + +1. **Manual Assignment (Recommended):** Use the role assignment page in Blade. This immediately adds/removes Discord roles on the server. + +2. **Automatic Sync:** Runs daily at 8:00 AM to sync Discord roles with the database for users who had roles changed directly in Discord. + +**Best Practice:** Always assign roles through the Blade UI when possible for instant synchronization. + +## Form Integration Pattern + +When creating tRPC procedures that will be called from dynamic forms, you must include metadata for the form responder client. + +### Required Pattern + +```typescript +export const myRouter = { + submitApplication: protectedProcedure + .meta({ + id: "submitApplication", + inputSchema: z.object({ + name: z.string().min(1), + email: z.string().email(), + major: z.string().min(1), + }), + }) + .input( + z.object({ + name: z.string().min(1), + email: z.string().email(), + major: z.string().min(1), + }), + ) + .mutation(async ({ input, ctx }) => { + // Handle form submission + }), +}; +``` + +### Requirements + +1. **`.meta()` must include:** + - `id`: String identifier for the procedure (usually matches the procedure name) + - `inputSchema`: The Zod schema object (must match the `.input()` schema) + +2. **Both `.meta()` and `.input()` are required** with the same schema + +3. **The form responder client** consumes this metadata to validate input and submit form data sent through this procedure via the form connector. + +### Why Both? + +- `.input()` - Used by tRPC for runtime validation +- `.meta()` with `inputSchema` - Used by the form builder/responder to validate form input for the procedure on the client side + +## Logging Requirement + +**Every tRPC procedure that performs state changes (mutations) MUST log both success and failure.** + +We use Discord webhooks for logging to maintain an audit trail of all actions in the system. + +### The Log Function + +Import from utils: + +```typescript +import { log } from "../utils"; +``` + +Usage: + +```typescript +await log({ + title: "Action Title", + message: "Detailed description of what happened", + color: "success_green", // or "uhoh_red", "blade_purple", "tk_blue" + userId: ctx.session.user.discordUserId, +}); +``` + +### Color Guide + +- `success_green` - Successful operations +- `uhoh_red` - Errors and failures +- `blade_purple` - General informational logs +- `tk_blue` - Bot-related actions + +### Required Pattern for Mutations + +Every mutation must wrap its logic in a try-catch block with appropriate logging: + +```typescript +export const myRouter = { + updateMember: permProcedure + .input(z.object({ + memberId: z.string(), + name: z.string(), + })) + .mutation(async ({ input, ctx }) => { + try { + // Check permissions + controlPerms.or(["MANAGE_MEMBERS"], ctx); + + // Perform the action + const result = await db.update(Members) + .set({ name: input.name }) + .where(eq(Members.id, input.memberId)) + .returning(); + + // Log success + await log({ + title: "Member Updated", + message: `Updated member ${input.memberId}: name changed to "${input.name}"`, + color: "success_green", + userId: ctx.session.user.discordUserId, + }); + + return result; + } catch (error) { + // Log failure + await log({ + title: "Member Update Failed", + message: `Failed to update member ${input.memberId}: ${error instanceof Error ? error.message : "Unknown error"}`, + color: "uhoh_red", + userId: ctx.session.user.discordUserId, + }); + + // Re-throw to let tRPC handle the error response + throw error; + } + }), +}; +``` + +### Logging Best Practices + +1. **Be specific in titles** - "Member Updated" not "Success" +2. **Include relevant IDs** - Always include what was changed +3. **Log before and after values** for updates when relevant +4. **Don't log sensitive data** - No passwords, tokens, or PII details +5. **Keep messages concise** - The Discord embed has character limits + +### Read Operations (Queries) + +Queries typically don't require logging unless they're sensitive or expensive operations: + +```typescript +// Normal query - no logging needed +export const myRouter = { + getMember: protectedProcedure + .input(z.object({ id: z.string() })) + .query(async ({ input }) => { + return await db.query.Members.findFirst({ + where: eq(Members.id, input.id), + }); + }), + + // Sensitive query - should be logged + exportAllMemberData: permProcedure + .query(async ({ ctx }) => { + try { + controlPerms.or(["EXPORT_DATA"], ctx); + + const data = await db.query.Members.findMany(); + + await log({ + title: "Member Data Exported", + message: `Exported ${data.length} member records`, + color: "blade_purple", + userId: ctx.session.user.discordUserId, + }); + + return data; + } catch (error) { + await log({ + title: "Member Export Failed", + message: `Failed to export member data: ${error instanceof Error ? error.message : "Unknown error"}`, + color: "uhoh_red", + userId: ctx.session.user.discordUserId, + }); + + throw error; + } + }), +}; +``` + + +## Best Practices + +### 1. Always Log State Changes + +Every mutation must log both success and failure. No exceptions. + +```typescript +// ❌ Bad - no logging +.mutation(async ({ input, ctx }) => { + return await db.update(Something).set(input); +}); + +// ✅ Good - proper logging +.mutation(async ({ input, ctx }) => { + try { + const result = await db.update(Something).set(input); + + await log({ + title: "Something Updated", + message: `Updated something with ID ${input.id}`, + color: "success_green", + userId: ctx.session.user.discordUserId, + }); + + return result; + } catch (error) { + await log({ + title: "Update Failed", + message: `Failed to update: ${error instanceof Error ? error.message : "Unknown error"}`, + color: "uhoh_red", + userId: ctx.session.user.discordUserId, + }); + + throw error; + } +}); +``` + +### 2. Always Use the Right Procedure Type + +Don't use `protectedProcedure` when you need permissions. Use `permProcedure` instead. + +### 3. Use OR Logic for Page Access + +When gating pages, use `controlPerms.or()` so users with any relevant permission can access: + +```typescript +// Good: Users with read OR write can see the page +controlPerms.or(["VIEW_EVENTS", "MANAGE_EVENTS"], ctx); + +// Less common: Requiring multiple permissions +controlPerms.and(["VIEW_EVENTS", "MANAGE_EVENTS"], ctx); +``` + +### 4. Validate Input Thoroughly + +Always use Zod schemas for input validation: + +```typescript +.input( + z.object({ + email: z.string().email(), + age: z.number().min(0).max(150), + name: z.string().min(1).max(100), + }), +) +``` + +### 5. Handle Errors Gracefully + +Throw appropriate tRPC errors and always log them: + +```typescript +import { TRPCError } from "@trpc/server"; + +try { + const found = await db.query.Something.findFirst(); + + if (!found) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Resource not found", + }); + } + + // ... rest of logic +} catch (error) { + await log({ + title: "Operation Failed", + message: `Error: ${error instanceof Error ? error.message : "Unknown error"}`, + color: "uhoh_red", + userId: ctx.session.user.discordUserId, + }); + + throw error; +} +``` + +### 6. Keep Routers Organized + +Group related procedures into routers by domain: + +```text +routers/ +├── members.ts # Member management +├── events.ts # Event management +├── roles.ts # Role/permission management +├── misc.ts # Form integrations and misc +└── index.ts # Main router that combines all +``` + +### 7. Document Complex Procedures + +Add comments for non-obvious logic: + +```typescript +export const complexRouter = { + doComplexThing: permProcedure + .input(z.object({ id: z.string() })) + .mutation(async ({ input, ctx }) => { + // Check permissions first + controlPerms.or(["COMPLEX_PERMISSION"], ctx); + + // Step 1: Fetch related data + const data = await db.query.Something.findFirst(); + + // Step 2: Process based on business logic + // Note: We do X because of Y business requirement + + // Step 3: Update database + // ... + }), +}; +``` + +## Testing Your Procedures + +When developing locally: + +1. **Use the tRPC devtools** (if enabled) to inspect requests +2. **Check the console** - tRPC logs execution time for each procedure +3. **Test permission logic** - Create test roles with specific permissions +4. **Use Drizzle Studio** to verify database changes + +## Next Steps + +- Review existing routers in `packages/api/src/routers/` for examples +- Check `@forge/consts/knight-hacks` for available permissions +- Read the [Architecture Guide](./ARCHITECTURE.md) to understand data flow +- See [CONTRIBUTING.md](../CONTRIBUTING.md) for general contribution guidelines +- Read our [GitHub Etiquette](./GITHUB-ETIQUETTE.md) guide for how to contribute to the project +- Check out the [Getting Started](./GETTING-STARTED.md) guide for setup instructions if you haven't already diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 000000000..dc9660f78 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,287 @@ +# Architecture + +This document explains how Forge is structured, how the pieces fit together, and the technologies we use. + +## Monorepo Structure + +Forge is a Turborepo monorepo containing multiple applications and shared packages. + +``` +forge/ +├── apps/ # Standalone applications +│ ├── blade/ # Main full-stack app (the "backend") +│ ├── club/ # Club website (frontend only) +│ ├── 2025/ # Knight Hacks VIII 2025 site (frontend only) +│ ├── gemiknights/ # GemiKnights 2025 site (frontend only) +│ ├── guild/ # Member networking site (frontend only) +│ ├── tk/ # Discord bot +│ └── cron/ # Cron job server +├── packages/ # Shared packages +│ ├── api/ # tRPC router (API layer) +│ ├── auth/ # Authentication setup +│ ├── db/ # Database schema and client +│ ├── email/ # Email templates and sending +│ ├── ui/ # Shared UI components +│ └── consts/ # Shared constants +└── tooling/ # Shared configuration + ├── eslint/ # ESLint config + ├── prettier/ # Prettier config + ├── tailwind/ # Tailwind config + └── typescript/ # TypeScript config +``` + +## How Apps Communicate + +### Blade as the "Backend" + +While `blade` is technically a Next.js app, it serves as the backend because: + +- It contains all write operations (create, update, delete) +- It handles authentication +- It manages role-based permissions +- Other apps only have read access via tRPC + +### Frontend-Only Apps + +Apps like `club`, `guild`, `2025`, and `gemiknights` are frontend-only and interact with `blade` for data: + +- **club**: Reads member count and other club stats +- **guild**: Reads member profiles for the networking directory +- **2025/gemiknights**: Primarily static, minimal backend needs + +These apps use tRPC (via `@forge/api`) to make read-only API calls to `blade`. + +### Authentication Flow + +All authentication is centralized in `blade`: + +1. User clicks "Login" on any frontend app +2. They're redirected to `blade` with a callback URL +3. `blade` handles Discord OAuth via Better Auth +4. After authentication, user is redirected to the necessary functional page on Blade + +## Shared Packages + +### `@forge/api` + +The tRPC router that defines all API endpoints. This is the API layer that apps use to communicate with the backend. + +- Contains read and write procedures +- Only `blade` exposes write operations +- Other apps consume read-only endpoints + +To create a new API endpoint, you need to add a procedure in an existing router, or create a new router altogether. If creating a new router, you need to add it to the `appRouter` in `packages/api/src/root.ts`. Then, this new procedure will be available to all apps that use the `@forge/api` package. + +### `@forge/db` + +Database layer using Drizzle ORM with PostgreSQL. + +- Contains all database schemas +- Exports the database client +- Includes migration scripts +- Located in `packages/db/src/schemas/` + +### `@forge/auth` + +Authentication setup using Better Auth with Discord OAuth. + +- Currently only used in `blade` +- Separated as a package for potential future use in other apps +- Handles Discord OAuth flow and session management + +### `@forge/email` + +Email system using Listmonk. + +- Email templates (React Email) +- Email sending functions +- Used for member communications, hackathon notifications, etc. + +### `@forge/ui` + +Shared UI component library. + +- Mix of shadcn/ui components and custom in-house components +- Used across multiple apps for consistency +- Each app also has its own `components/` folder for app-specific components + +### `@forge/consts` + +Shared constants used across the repository. + +- Discord role IDs +- URLs and domains +- Majors of study +- Other static configuration values + +## Data Model + +### Core Entities + +**Users, Members, and Hackers:** + +- `User`: Discord OAuth profile (minimal info) +- `Member`: Full profile for club members (Orlando students) +- `Hacker`: Full profile for hackathon participants (national students) + +A single `User` can be both a `Member` and a `Hacker`. We separate these because Knight Hacks serves two distinct audiences: +- Local club members at UCF +- National students attending our 1000+ person annual hackathon + +**Events and Applications:** + +- `Event`: Both club events and hackathon events +- `Application`: Can be club membership applications or hackathon applications +- Applications link to either a `Member` or `Hacker` + +**Roles and Permissions:** + +- `Role`: Maps to Discord server roles +- `Permission`: Grants access to features in `blade` +- Only relevant in `blade` (the only app with admin features) + +**Other Entities:** + +- Dues and payments +- Sponsors and partnerships +- Check-ins and attendance tracking +- And more (see `packages/db/src/schemas/knight-hacks.ts`) + +## Technology Stack + +### Frontend + +- **Framework**: Next.js 14 with App Router +- **Styling**: Tailwind CSS +- **UI Components**: shadcn/ui + custom components +- **Animations**: Framer Motion, GSAP +- **State Management**: React Query (via tRPC) + +### Backend + +- **API Layer**: tRPC +- **Database**: PostgreSQL (Drizzle ORM) +- **Authentication**: Better Auth with Discord OAuth +- **Object Storage**: MinIO (S3-compatible) +- **Email**: Listmonk + +### Infrastructure + +- **Monorepo Tool**: Turborepo +- **Package Manager**: pnpm with workspaces +- **Discord Bot**: discord.js +- **Cron Jobs**: node-cron + +### Development Tools + +- **Linting**: ESLint +- **Formatting**: Prettier +- **Type Checking**: TypeScript +- **Database GUI**: Drizzle Studio + +## Development Patterns + +### Server-First Architecture + +We prioritize Server Components in Next.js: + +- Use Server Components by default +- Only use Client Components when necessary (interactivity, hooks, browser APIs) +- Keep data fetching on the server when possible + +### tRPC for API Communication + +All API communication uses tRPC: + +- Type-safe end-to-end +- Shared types between client and server +- No manual API route definitions needed + +### Shared Configuration + +Configuration is centralized in `tooling/`: + +- ESLint, Prettier, Tailwind, and TypeScript configs are shared +- Apps and packages extend these base configs +- Ensures consistency across the entire monorepo + +## Turborepo + +Turborepo orchestrates the monorepo, handling builds, dev servers, and caching. + +### Key Commands + +- `pnpm dev` - Run all apps in development mode +- `pnpm build` - Build all apps for production +- `pnpm lint` - Lint all packages +- `pnpm typecheck` - Type-check all packages + +### Filtering + +Run commands for specific apps using filters: + +```bash +pnpm dev --filter=@forge/blade +pnpm build --filter=@forge/club +``` + +### Pipeline + +Turborepo uses a pipeline defined in `turbo.json` that: + +- Determines task dependencies (e.g., build depends on typecheck) +- Caches task outputs for faster subsequent runs +- Runs tasks in parallel when possible + +## Deployment + +### Infrastructure + +- **Orchestration**: Coolify (self-hosted PaaS) +- **Hosting**: Azure VMs (Observer + Worker nodes) +- **Observer Node**: Runs Coolify control plane, PostgreSQL, and MinIO +- **Worker Node**: Runs all applications (blade, club, guild, etc.), tk bot, and cron server + +### Deployment Strategy + +Each app is deployed separately: + +- Apps have independent build, install, and start commands +- Each app has its own watch paths for automatic rebuilds +- Frontend apps are deployed as static sites or SSR depending on needs +- `tk` and `cron` run as long-lived processes + +### Environment Separation + +- **Development**: Local with Docker Compose for databases +- **Production**: Azure infrastructure with Coolify + +## Helpful Resources + +### Core Technologies + +- [Next.js Documentation](https://nextjs.org/docs) +- [tRPC Documentation](https://trpc.io/docs) +- [Drizzle ORM Documentation](https://orm.drizzle.team/docs/overview) +- [Turborepo Documentation](https://turbo.build/repo/docs) +- [Better Auth Documentation](https://www.better-auth.com/docs) + +### UI and Styling + +- [Tailwind CSS Documentation](https://tailwindcss.com/docs) +- [shadcn/ui Components](https://ui.shadcn.com/) +- [Framer Motion Documentation](https://www.framer.com/motion/) + +### Tools + +- [pnpm Documentation](https://pnpm.io/) +- [discord.js Guide](https://discordjs.guide/) + +## Next Steps + +Now that you understand the architecture: + +- Read the [Contribution Guide](../CONTRIBUTING.md) for contribution guidelines +- Setup your environment by following the [Getting Started Guide](./GETTING-STARTED.md) +- Check out the [API & Permissions](./API-AND-PERMISSIONS.md) guide for forge specific backend development guidelines +- Read our [GitHub Etiquette](./GITHUB-ETIQUETTE.md) guide for how to contribute to the project diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md new file mode 100644 index 000000000..69fc7011c --- /dev/null +++ b/docs/GETTING-STARTED.md @@ -0,0 +1,247 @@ +# Getting Started + +This guide will help you set up Forge locally and make your first contribution. + +## Who This Is For + +**Knight Hacks Dev Team Members:** You'll have access to shared environment variables and private channels. + +**External Contributors:** You're welcome to contribute! You'll need to set up your own credentials for certain features, but most of the app works without them. + +## Prerequisites + +Before you begin, ensure you have the following installed: + +- [Node.js LTS](https://nodejs.org/en/download/) (v20.16.0 or higher) +- [pnpm](https://pnpm.io/installation) (v9.6.0 or higher) +- [Docker Desktop](https://docs.docker.com/get-docker/) +- A POSIX/Unix terminal (Linux, macOS, or WSL for Windows users) + +## Initial Setup + +### 1. Clone the Repository + +```bash +git clone https://github.com/KnightHacks/forge.git +cd forge +``` + +### 2. Install Dependencies + +```bash +pnpm install +``` + +### 3. Set Up Environment Variables + +Create a `.env` file in the repository root. + +#### For Dev Team Members + +You have access to the shared environment variables document in Notion. Copy those values into your `.env` file. + +If you don't have access, you need a Knight Hacks email. Reach out to [secretary@knighthacks.org](mailto:secretary@knighthacks.org) to get access. + +#### For External Contributors + +You'll need to set up your own credentials. Use `.env.example` as a template. + +**Minimum Required Setup:** + +To open the app locally, you need Discord OAuth credentials: + +1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) +2. Create a new application +3. Get your Client ID and Client Secret +4. Add these to your `.env` file + +**Optional Services (for specific features):** + +You only need these if you're working on the corresponding features: + +- **Events Pages** (hack or club): Google Cloud Platform project with Calendar API enabled. Get the JSON credentials and add to `.env` +- **Email Functions**: Set up [Listmonk](https://listmonk.app/) (self-hosted or cloud) and get access keys +- **S3/Object Storage**: Set up [MinIO](https://min.io/) (self-hosted or cloud) and get access keys + +See `.env.example` for all required environment variable names. + +### 4. Set Up the Database + +Start the local Postgres database with Docker: + +```bash +docker compose up +``` + +> IMPORTANT! + +You must push the database schema to your local database before running the project. This is a common source of errors for new contributors. The most common error for this will be a "Failed to get current session" error on any page within Blade. + +```bash +pnpm db:push +``` + +**Optional:** View the database contents with Drizzle Studio: + +```bash +pnpm db:studio +``` + +This opens a GUI at [local.drizzle.studio](https://local.drizzle.studio). + +### 5. (Optional) Bootstrap Superadmin Access + +If you need to work on admin-level pages or test permission-based features, you'll need superadmin access. This requires a one-time bootstrap step. + +**Why is this needed?** + +Forge uses a role-based permission system. To access admin features, you need a role with permissions. But to create roles, you need admin access. This creates a chicken-and-egg problem for fresh databases. The bootstrap script solves this by directly inserting a superadmin role into the database. + +**How to bootstrap:** + +1. Start the database and run Blade at least once +2. Log in to Blade using Discord OAuth (this creates your user record) +3. Get your Discord user ID (right-click your profile in Discord with Developer Mode enabled) +4. Choose any Discord role ID to link to the superadmin role (this is just metadata stored in the database) +5. Run the bootstrap script: + +```bash +pnpm db:bootstrap +``` + +Example: + +```bash +pnpm db:bootstrap 1321955700540309645 238081392481665025 +``` + +After running this, you'll have full superadmin permissions and can manage other roles through the Blade UI. + +### 6. (Optional) Populate Test Data from Production + +**Dev Team Members Only:** If you have access to the shared MinIO instance, you can pull a sanitized copy of production data to test with realistic data locally. + +```bash +pnpm db:pull +``` + +This downloads and inserts production data into your local database without removing existing data (like your superadmin role). + +If you want to completely replace your local database with the production snapshot: + +```bash +pnpm db:pull --truncate +``` + +**Note:** You can run the superadmin bootstrap script after pulling production data if needed. + +## Running the Project + +### Run All Applications + +```bash +pnpm dev +``` + +This starts all apps concurrently. Each app will be available at its own port. + +### Run a Specific Application + +```bash +pnpm dev --filter=@forge/blade +``` + +Replace `@forge/blade` with any app name. + +### Application Ports + +When running the apps, they will be available at the following ports: + +| App | Package Name | Port | URL | Description | +|-----|-------------|------|-----|-------------| +| Blade | `@forge/blade` | 3000 | http://localhost:3000 | Main monolithic app (membership, hacker registration, dues, events) | +| Club | `@forge/club` | 3001 | http://localhost:3001 | Club site (frontend only) | +| 2025 | `@forge/2025` | 3002 | http://localhost:3002 | Knight Hacks VIII 2025 hackathon site (frontend only) | +| Guild | `@forge/guild` | 3003 | http://localhost:3003 | Member networking site (Knight Hacks LinkedIn) | +| GemiKnights | `@forge/gemiknights` | 3005 | http://localhost:3005 | GemiKnights 2025 hackathon site (frontend only) | +| TK | `@forge/tk` | N/A | N/A | Discord bot for Knight Hacks server | +| Cron | `@forge/cron` | N/A | N/A | Cron job server | + +## What Works Locally + +Most features work out of the box with just Discord OAuth configured. + +**Features that require additional setup:** + +- Events pages (hack or club) - requires Google Calendar API +- Email sending - requires Listmonk +- File uploads/S3 operations - requires MinIO + +## Making Your First Contribution + +### Find an Issue + +Look for issues labeled [`Onboarding`](https://github.com/KnightHacks/forge/labels/Onboarding) on GitHub. These are beginner-friendly tasks designed for new contributors. + +### Development Workflow + +0. Before getting started developing, make sure you understand our [GitHub Etiquette](./GITHUB-ETIQUETTE.md) +1. Create a new branch for your changes +2. Make your changes +3. Test your changes locally +4. Run checks before submitting: + ```bash + pnpm format + pnpm lint + pnpm typecheck + pnpm build + ``` +5. Commit your changes (use lowercase, descriptive commit messages) +6. Push your branch and open a pull request + +See [CONTRIBUTING.md](../CONTRIBUTING.md) for detailed guidelines on commits, pull requests, and testing. + +## Getting Help + +### Dev Team Members + +Ask questions in the private dev team Discord channel. + +### External Contributors + +Ask questions in the [Knight Hacks Discord server](https://discord.com/channels/486628710443778071/486631140552212491). + +You can also join the broader [Knight Hacks community Discord](https://discord.gg/ynr44H6KAY) for general support. + +## Common Issues + +### Database Connection Errors + +Make sure Docker is running and the Postgres container is up: + +```bash +docker compose ps +``` + +If the container isn't running, start it with `docker compose up`. + +### Port Already in Use + +If a port is already in use, stop the conflicting process or change the port in the app's configuration. + +### pnpm Installation Fails + +Make sure you're using the correct versions: + +```bash +node --version # Should be >= 20.16.0 +pnpm --version # Should be >= 9.6.0 +``` + +## Next Steps + +Now that you're set up, explore the codebase: + +- Check out the [Architecture Overview](./ARCHITECTURE.md) to understand how everything fits together +- Look at the [API & Permissions](./API-AND-PERMISSIONS.md) guide for backend development guidelines +- Read our [GitHub Etiquette](./GITHUB-ETIQUETTE.md) guide for how to contribute to the project diff --git a/docs/GITHUB-ETIQUETTE.md b/docs/GITHUB-ETIQUETTE.md new file mode 100644 index 000000000..c328547ac --- /dev/null +++ b/docs/GITHUB-ETIQUETTE.md @@ -0,0 +1,462 @@ +# GitHub Etiquette + +This guide covers how to work with GitHub in the Forge repository, including branch naming, commits, pull requests, and code review. + +## Branch Naming + +Branch names should follow the pattern: `app-or-package/descriptive-slug` + +### Format + +``` +/ +``` + +### Examples + +``` +blade/form-permissions +api/add-companies-router +2025/adds-sponsor-contact-button +db/user-profile-schema +ui/button-variant +guild/search-optimization +repo/deprecate-email-package +repo/update-dependencies +``` + +### Repository-Wide Changes + +For changes that affect the entire repository (refactors, dependency updates, tooling changes), use the `repo/` prefix: + +``` +repo/deprecate-email-package +repo/update-dependencies +repo/migrate-to-pnpm-9 +repo/add-prettier-config +``` + +### Guidelines + +- Use the app or package name that's being modified +- For repository-wide changes, use `repo/` prefix +- Use lowercase with hyphens +- Be descriptive but concise +- Focus on what the change does, not how +- Commits will be squashed when merged into main, but to resolve conflicts on your branch, you may use either a squash merge or a rebase merge. + +## Commit Messages + +Keep commit messages professional and lowercase. + +### Format + +``` +added user authentication flow +fixed event date rendering bug +updated member profile page layout +removed deprecated cron job +``` + +### Guidelines + +- Use lowercase +- Be descriptive and clear +- No need for prefixes (feat, fix, chore) +- Keep it professional - this is a public OSS repo for a reputable nonprofit +- One logical change per commit is ideal, but not required + +### Examples + +**Good:** +``` +added permission checks to events page +fixed typo in email template +updated dependencies to latest versions +``` + +**Avoid:** +``` +stuff +asdfasdf +fixes +WIP DO NOT MERGE +``` + +## Issues + +All work starts with an issue. + +### Who Can Create Issues + +Anyone can create issues: +- **Community members** can report bugs or request features +- **Developers** create issues for work items +- **External contributors** can create issues and pick them up + +### Issue Assignment + +- **During Standup**: Most issues are assigned to developers during team standups +- **Help Wanted**: Issues labeled `Help Wanted` are available for anyone to claim +- **Self-Assignment**: If you create an issue and want to work on it, assign yourself + +### Issue Requirements + +Every issue must have: +1. **At least one label** (see label guide below) +2. **An assignee** (if work has started or been claimed) + +### Claiming an Issue + +If you want to work on an issue: + +1. Comment on the issue saying you're working on it +2. Assign yourself (if you have permissions) or ask a maintainer to assign you +3. Create a branch and start working + +## Pull Requests + +Every PR must follow specific requirements to pass automated checks. + +### PR Title Format + +**Required format:** + +``` +[#123] Description of changes +``` + +The title must start with `[#XYZ]` where XYZ is the issue number. + +### Examples + +**Good:** +``` +[#45] Add event registration form +[#102] Fix member count display bug +[#87] Update permissions for club admins +``` + +**Bad:** +``` +Add event registration form (missing issue number) +[45] Fix bug (missing # symbol) +added stuff [#45] (issue number not at start) +``` + +### PR Requirements + +Every PR must have: + +1. **Issue reference in title** - `[#123]` format +2. **At least one label** - Use labels to categorize the change +3. **At least one assignee** - Usually yourself +4. **Link to issue(s)** - Reference the issue(s) in the PR description + +These are automatically validated by CI. Your PR will fail checks if any are missing. + +### PR Description + +A PR template is auto-populated when you create a PR. Fill it out completely: + +- **What changed** - Describe your changes +- **Why** - Explain the reasoning +- **How to test** - Steps for reviewers to verify +- **Screenshots** (if applicable) - For UI changes + +### Draft PRs + +Draft PRs are encouraged for: +- Early feedback on approach +- Work in progress that needs discussion +- Large changes where you want direction before continuing + +Mark your PR as "Ready for review" when it's complete. + +## Labels + +Use labels to categorize your PRs and issues. You must include at least one label. + +### Scope Labels + +Indicate which part of the codebase is affected: + +- `API` - Changes to the global API/tRPC package +- `Blade` - Changes to Blade app +- `CRON` - Changes to CRON app +- `Database` - Changes to the DB package +- `Guild` - Changes to Guild app +- `Hack Sites` - Changes to hackathon apps (2025, gemiknights, etc.) +- `T.K` - Changes to T.K Discord bot +- `UI` - Changes to the global UI package +- `Global` - Changes affecting the entire repository + +### Type Labels + +Indicate the type of change: + +- `Feature` - New feature or request +- `Bug` - Something isn't working +- `Documentation` - Improvements or additions to documentation + +### Size Labels + +Indicate the size of the change (affects number of reviewers needed): + +- `Minor` - Small change, 1 reviewer required +- `Major` - Big change, 2+ reviewers required + +### Status Labels + +- `Help Wanted` - Needs assignment from a developer +- `Onboarding` - Good first issue for onboarding developers +- `Duplicate` - Issue or PR already exists + +### Example Label Combinations + +``` +Issue: Add event form validation +Labels: Blade, Feature, Minor + +Issue: Fix database connection timeout +Labels: Database, Bug, Minor + +Issue: Refactor entire API router structure +Labels: API, Feature, Major + +Issue: Update getting started guide +Labels: Documentation, Minor +``` + +## Code Review Process + +### 1. CodeRabbit Review + +All PRs are first reviewed by CodeRabbit (automated code review). + +**Your responsibilities:** +- Read all CodeRabbit comments +- Either fix the issue or dismiss the comment with explanation +- Don't ignore CodeRabbit - resolve or dismiss all comments + +### 2. CI Checks + +All checks must pass before human review: + +- **Lint** - Code follows linting rules +- **Format** - Code is properly formatted +- **Typecheck** - No TypeScript errors +- **Build** - Code builds successfully +- **PR Validation** - PR title, labels, and assignee are correct + +**Run these locally before pushing:** + +```bash +pnpm format +pnpm lint +pnpm typecheck +pnpm build +``` + +### 3. Human Review + +Once CodeRabbit is resolved and CI passes, request review from the dev team. + +**Review requirements:** +- **Minor changes** - 1 reviewer approval required +- **Major changes** - 2+ reviewer approvals required + +**Review timeline:** +- Reviews typically take 1-3 days +- Be patient but feel free to follow up if it's been >3 days + +### 4. Addressing Feedback + +When reviewers request changes: + +- Make the requested changes +- Respond to each comment when addressed +- Push new commits (don't force push unless necessary) +- Re-request review when ready + +**Be receptive to feedback:** +- Assume good intent +- Ask questions if you don't understand +- Explain your reasoning if you disagree +- Remember: reviews make the code better + +### 5. Merging + +**Who can merge:** +- Maintainers can merge any PR +- Developers (including you) can merge their own PRs + +**Merge requirements:** +- All CI checks must pass (no exceptions) +- Required approvals received +- CodeRabbit comments resolved or dismissed +- Conflicts resolved + +**You cannot merge with failing checks.** + +## Communication + +### Where to Communicate + +**Use PR comments for:** +- Questions about the code +- Requesting clarification +- Discussing implementation details +- Asking for help on your PR +- Responding to review feedback + +**Avoid Discord for:** +- PR-specific discussions (these should be on GitHub) +- Code review feedback +- Technical implementation details + +Discord is fine for general questions, but keep PR discussions on GitHub to avoid miscommunication and maintain a record. + +### Tagging People + +Tagging is appropriate when: +- Requesting review: `@username can you review this?` +- Asking for specific expertise: `@username do you know how this works?` +- Following up after 3+ days: `@username gentle ping on this PR` + +Use tagging reasonably - don't spam tags on every comment. + +### Asking for Help + +If you're stuck on your PR: + +1. Comment on the PR explaining what you're stuck on +2. Tag a developer: `@username could you help with X?` +3. Be specific about what you need help with +4. If urgent, mention in the dev Discord (but keep discussion on GitHub) + +## Common Workflows + +### Starting New Work + +1. Find or create an issue for the work +2. Assign yourself to the issue +3. Create a branch: `blade/add-feature` +4. Make your changes +5. Run checks locally: `pnpm format && pnpm lint && pnpm typecheck && pnpm build` +6. Commit with descriptive message: `added feature X` +7. Push your branch + +### Creating a Pull Request + +1. Go to GitHub and create a PR +2. Title: `[#123] Add feature X` +3. Fill out the PR template completely +4. Add labels (at least one) +5. Add yourself as assignee +6. Reference the issue in description +7. Mark as draft if not ready for review + +### Ready for Review + +1. Mark PR as ready (if it was a draft) +2. Ensure all CI checks pass +3. Resolve all CodeRabbit comments +4. Request review from the team +5. Wait for feedback (1-3 days) + +### After Review + +1. Address all feedback +2. Respond to comments when fixed +3. Push new commits +4. Re-request review +5. Once approved and checks pass, merge! + +## Best Practices + +### Before Creating a PR + +- Run all checks locally +- Test your changes thoroughly +- Review your own code first +- Make sure the issue is properly linked + +### During Review + +- Respond to feedback promptly +- Don't take feedback personally +- Ask questions if unclear +- Update your PR as needed + +### After Merging + +- Delete your branch +- Close the linked issue (if fully resolved) +- Celebrate! 🎉 + +## Getting Help + +If you're unsure about any of this: + +- Ask in the dev team Discord +- Comment on your PR or issue +- Tag a maintainer for clarification +- Review existing PRs for examples + +## Common Mistakes + +### Missing Issue Number in Title + +**Wrong:** `Add event form` +**Right:** `[#45] Add event form` + +### No Labels + +Every PR needs at least one label. Add them before requesting review. + +### Merging with Failing Checks + +You cannot merge with failing checks. Fix the issues first. + +### Ignoring CodeRabbit + +Resolve or dismiss all CodeRabbit comments. Don't leave them unaddressed. + +### Wrong Branch Name + +**Wrong:** `fix-bug`, `my-feature` +**Right:** `blade/fix-event-bug`, `api/add-members-router` + +## Examples + +### Good PR + +``` +Title: [#123] Add event registration form to Blade +Labels: Blade, Feature, Minor +Assignee: @yourname +Description: [Filled out template completely with screenshots] +Status: All checks passing, CodeRabbit resolved +``` + +### Good Issue + +``` +Title: Add email validation to member signup +Labels: Blade, Feature, Minor +Assignee: @developer (or unassigned if Help Wanted) +Description: Clear description of the problem and expected behavior +``` + +### Good Commit + +``` +added email validation to member signup form +``` + +## Next Steps + +- Read [CONTRIBUTING.md](../CONTRIBUTING.md) for general contribution guidelines +- Review [Getting Started](./GETTING-STARTED.md) for setup instructions +- Check out [API & Permissions](./API-AND-PERMISSIONS.md) for forge specific backend development guidelines \ No newline at end of file diff --git a/package.json b/package.json index ada429b52..9797e1e99 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "clean:workspaces": "turbo run clean", "db:push": "turbo -F @forge/db push", "db:studio": "turbo -F @forge/db studio", + "db:pull": "pnpm --filter=@forge/db with-env tsx scripts/get_prod_db.ts", + "db:bootstrap": "pnpm --filter=@forge/db with-env tsx scripts/bootstrap-superadmin.ts", "dev": "turbo watch dev --continue", "dev:blade": "turbo watch dev -F @forge/blade...", "format": "turbo run format --continue -- --cache --cache-location .cache/.prettiercache", @@ -44,4 +46,4 @@ "@types/react-dom": "^18.3.1" } } -} +} \ No newline at end of file diff --git a/packages/db/src/schemas/knight-hacks.ts b/packages/db/src/schemas/knight-hacks.ts index e5a95612c..48653f563 100644 --- a/packages/db/src/schemas/knight-hacks.ts +++ b/packages/db/src/schemas/knight-hacks.ts @@ -79,7 +79,7 @@ export const Member = createTable( guildProfileVisible: t.boolean().notNull().default(true), tagline: t.varchar("tagline", { length: 80 }), about: t.varchar("about", { length: 500 }), - profilePictureUrl: t.varchar("profile_picture_url", { length: 255 }), + profilePictureUrl: t.varchar("profile_picture_url", { length: 512 }), shirtSize: shirtSizeEnum().notNull(), githubProfileUrl: t.varchar({ length: 255 }), linkedinProfileUrl: t.varchar({ length: 255 }),