# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

```bash
# Development
bun --watch bot/index.ts         # Run bot + API with hot reload
docker compose up                # Start all services (bot, API, database)
docker compose up app            # Start just the app (bot + API)
docker compose up db             # Start just the database

# Testing
bun test                         # Run all tests
bun test path/to/file.test.ts    # Run a single test file
bun test shared/modules/economy  # Run tests in a directory
bun test --watch                 # Watch mode

# Database
bun run db:push:local            # Push schema changes (local)
bun run db:studio                # Open Drizzle Studio (localhost:4983)
bun run generate                 # Generate Drizzle migrations (Docker)
bun run migrate                  # Apply migrations (Docker)

# Admin Panel
bun run panel:dev                # Start Vite dev server for dashboard
bun run panel:build              # Build React dashboard for production
```

## Architecture

Aurora is a Discord RPG bot + REST API running as a **single Bun process**. The bot and API share the same database client and services.

```
bot/                    # Discord bot
├── commands/           # Slash commands by category (admin, economy, inventory, etc.)
├── events/             # Discord event handlers
├── lib/                # BotClient, handlers, loaders, embed helpers, commandUtils
├── modules/            # Feature modules (views, interactions per domain)
└── graphics/           # Canvas-based image generation (@napi-rs/canvas)

shared/                 # Shared between bot and API
├── db/                 # Drizzle ORM client + schema (users, economy, inventory, quests, etc.)
├── lib/                # env, config, errors, logger, types, utils
└── modules/            # Domain services (economy, user, inventory, quest, moderation, etc.)

api/                    # REST API (Bun HTTP server)
└── src/routes/         # Route handlers for each domain

panel/                  # React admin dashboard (Vite + Tailwind + Radix UI)
```

**Key architectural details:**
- Bot and API both import from `shared/` — do not duplicate logic.
- Services in `shared/modules/` are singleton objects, not classes.
- The database uses PostgreSQL 16+ via Drizzle ORM with `bigint` mode for Discord IDs and currency.
- Feature modules follow a strict file suffix convention (see below).

## Import Conventions

Use path aliases (defined in `tsconfig.json`). Order: external packages → aliases → relative.

```typescript
import { SlashCommandBuilder } from "discord.js";            // external
import { economyService } from "@shared/modules/economy/economy.service"; // alias
import { users } from "@db/schema";                          // alias
import { createErrorEmbed } from "@lib/embeds";             // alias
import { localHelper } from "./helper";                      // relative
```

**Aliases:**
- `@/*` → `bot/`
- `@shared/*` → `shared/`
- `@db/*` → `shared/db/`
- `@lib/*` → `bot/lib/`
- `@modules/*` → `bot/modules/`
- `@commands/*` → `bot/commands/`

## Code Patterns

### Module File Suffixes

- `*.view.ts` — Creates Discord embeds/components
- `*.interaction.ts` — Handles button/select/modal interactions
- `*.service.ts` — Business logic (lives in `shared/modules/`)
- `*.types.ts` — Module-specific TypeScript types
- `*.test.ts` — Tests (co-located with source)

### Command Definition

```typescript
export const commandName = createCommand({
  data: new SlashCommandBuilder().setName("name").setDescription("desc"),
  execute: async (interaction) => {
    await withCommandErrorHandling(interaction, async () => {
      const result = await service.method();
      await interaction.editReply({ embeds: [createSuccessEmbed(result)] });
    }, { ephemeral: true });
  },
});
```

`withCommandErrorHandling` (from `@lib/commandUtils`) handles `deferReply`, `UserError` display, and unexpected error logging automatically.

### Service Pattern

```typescript
export const serviceName = {
  methodName: async (params: ParamType): Promise<ReturnType> => {
    return await withTransaction(async (tx) => {
      // database operations
    });
  },
};
```

### Error Handling

```typescript
import { UserError, SystemError } from "@shared/lib/errors";

throw new UserError("You don't have enough coins!"); // shown to user
throw new SystemError("DB connection failed");        // logged, generic message shown
```

### Database Transactions

```typescript
import { withTransaction } from "@/lib/db";

return await withTransaction(async (tx) => {
  const user = await tx.query.users.findFirst({ where: eq(users.id, id) });
  await tx.update(users).set({ coins: newBalance }).where(eq(users.id, id));
  return user;
}, existingTx); // pass existing tx for nested transactions
```

### Testing

Mock modules **before** imports. Use `bun:test`.

```typescript
import { describe, it, expect, mock, beforeEach } from "bun:test";

mock.module("@shared/db/DrizzleClient", () => ({
  DrizzleClient: { query: mockQuery },
}));

describe("serviceName", () => {
  beforeEach(() => mockFn.mockClear());
  it("should handle expected case", async () => {
    mockFn.mockResolvedValue(testData);
    const result = await service.method(input);
    expect(result).toEqual(expected);
  });
});
```

## Naming Conventions

| Element          | Convention             | Example                          |
| ---------------- | ---------------------- | -------------------------------- |
| Files            | camelCase or kebab-case | `BotClient.ts`, `economy.service.ts` |
| Classes          | PascalCase             | `CommandHandler`, `UserError`    |
| Functions        | camelCase              | `createCommand`, `handleShopInteraction` |
| Constants        | UPPER_SNAKE_CASE       | `EVENTS`, `BRANDING`             |
| Enums            | PascalCase             | `TimerType`, `TransactionType`   |
| Services         | camelCase singleton    | `economyService`, `userService`  |
| Types/Interfaces | PascalCase             | `Command`, `Event`, `GameConfigType` |
| DB tables        | snake_case             | `users`, `moderation_cases`      |
| Custom IDs       | snake_case with prefix | `shop_buy_`, `trade_accept_`     |
| API routes       | kebab-case             | `/api/guild-settings`            |

## Key Files

| Purpose           | File                       |
| ----------------- | -------------------------- |
| Bot entry point   | `bot/index.ts`             |
| Discord client    | `bot/lib/BotClient.ts`     |
| DB schema index   | `shared/db/schema.ts`      |
| Error classes     | `shared/lib/errors.ts`     |
| Environment vars  | `shared/lib/env.ts`        |
| Config loader     | `shared/lib/config.ts`     |
| Embed helpers     | `bot/lib/embeds.ts`        |
| Command utils     | `bot/lib/commandUtils.ts`  |
| API server        | `api/src/server.ts`        |
