Files
dwroller/docs/developer-guide.md
alex a4cabbd2b8 Clean rules database and add documentation
- Remove OCR noise, credits, and duplicates from rules-database.json (288→255 rules)
- Add clean_rules.py script for rule cleanup
- Add CLAUDE.md, docs/, and update README with documentation links

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 17:35:36 +02:00

7.9 KiB

Developer Guide

Project Structure

dwroller/
├── backend/
│   ├── src/
│   │   ├── routes/      # API routes
│   │   ├── models/      # Data models
│   │   ├── controllers/ # Route controllers
│   │   ├── middleware/  # Auth, validation
│   │   └── utils/       # Helper functions
│   ├── server.js        # Entry point
│   └── package.json
├── frontend/
│   ├── src/
│   │   ├── components/  # React components
│   │   ├── pages/       # Route pages
│   │   ├── hooks/       # Custom hooks
│   │   └── store/       # State management
│   ├── public/          # Static assets
│   ├── package.json
│   └── vite.config.js
├── data/                # Static data files
├── database/            # Database files
└── docs/                # Documentation

Development Setup

Backend

cd backend
npm install
npm run dev

The backend starts on port 5000.

Frontend

cd frontend
npm install
npm run dev

The frontend starts on port 3000.

Code Style

JavaScript/TypeScript

  • Use TypeScript for new code
  • Follow Airbnb JavaScript style guide
  • Maximum 100 character line length
  • Use ESLint for linting
// Example code style
interface Player {
  id: number;
  name: string;
  xp: number;
  rp: number;
}

export class PlayerService {
  async getPlayer(id: number): Promise<Player> {
    const player = await this.repository.findById(id);
    return this.transform(player);
  }
}

React

  • Functional components with hooks
  • Use TypeScript for props
  • Follow React best practices
  • Use Context for global state
// Example React component
interface ShopItemProps {
  item: ShopItem;
  onPurchase: (itemId: number) => void;
}

const ShopItem: React.FC<ShopItemProps> = ({ item, onPurchase }) => {
  return (
    <div className="shop-item">
      <h3>{item.name}</h3>
      <p>Cost: {item.cost} RP</p>
      <button onClick={() => onPurchase(item.id)}>
        Purchase
      </button>
    </div>
  );
};

Database

Schema

The database uses SQLite by default. The schema is created automatically on first run.

Main tables:

  • players - Player accounts
  • items - Shop inventory
  • inventory - Player item ownership
  • sessions - Game session tracking
  • webhooks - Webhook configurations

Migrations

Migrations are stored in backend/migrations/.

# Run migrations
npm run migrate

# Create new migration
npx knex migrate:make create_new_table

API Development

Creating a New Endpoint

backend/src/routes/new-endpoints.ts
import express, { Request, Response } from 'express';

const router = express.Router();

// GET /new-endpoint
router.get('/', async (req: Request, res: Response) => {
  const data = await getNewData();
  res.json({ success: true, data });
});

export default router;

Add to server.js:

import newEndpoints from './src/routes/new-endpoints';

app.use('/api/new', newEndpoints);

Adding Authentication

import { requireAuth } from '../middleware/auth';

router.get(protectedRoute, requireAuth, async (req, res) => {
  // Protected route logic
});

Frontend Development

Creating a New Component

frontend/src/components/NewComponent.tsx
import React from 'react';

interface NewComponentProps {
  data: any;
  onAction: (action: string) => void;
}

export const NewComponent: React.FC<NewComponentProps> = ({
  data,
  onAction,
}) => {
  const handleClick = () => {
    onAction('click');
  };

  return <div className="new-component">{data.title}</div>;
};

State Management

Use React Context for global state:

// frontend/src/store/PlayerContext.tsx
import React, { createContext, useContext, useState } from 'react';

interface PlayerState {
  players: Player[];
  addPlayer: (player: Player) => void;
}

const PlayerContext = createContext<PlayerState | undefined>(undefined);

export const PlayerProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const [players, setPlayers] = useState<Player[]>([]);

  const addPlayer = (player: Player) => {
    setPlayers((prev) => [...prev, player]);
  };

  return (
    <PlayerContext.Provider value={{ players, addPlayer }}>
      {children}
    </PlayerContext.Provider>
  );
};

Testing

Backend Tests

backend/src/routes/__tests__/new-endpoints.test.ts
import request from 'supertest';
import app from '../../../server';

describe('New Endpoint', () => {
  it('should return 200 for GET', async () => {
    const res = await request(app).get('/new-endpoint');
    
    expect(res.statusCode).toBe(200);
    expect(res.body.success).toBe(true);
  });
});

Run tests:

npm test

Frontend Tests

frontend/src/components/__tests__/NewComponent.test.tsx
import { render, screen } from '@testing-library/react';
import NewComponent from '../NewComponent';

describe('NewComponent', () => {
  it('renders the title', () => {
    render(<NewComponent data={{ title: 'Test' }} />);
    
    expect(screen.getByText('Test')).toBeInTheDocument();
  });
});

Run tests:

npm test

Debugging

Backend Debugging

# Enable verbose logging
NODE_ENV=development npm run server

# View logs
tail -f backend.log

Frontend Debugging

# Open DevTools
npm run dev

# Check browser console
# Network tab for API calls

Deployment

Build

# Build frontend
npm run build

# Build backend
cd backend
npm run build

PM2 Setup

# Start with PM2
npm run pm2:start

# View logs
pm2 logs

# Stop
npm run pm2:stop

Production Environment Variables

# Create production env
cp .env.example .env.production

# Set required variables:
# - NODE_ENV=production
# - SESSION_SECRET
# - X_GM_SECRET
# - PORT

Performance

Optimization Tips

  1. Database: Use indexes for frequent queries
  2. Frontend: Enable code splitting
  3. API: Implement caching for static data
  4. Images: Use lazy loading

Caching

// Example: Cache shop data
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes

async function getShop() {
  const cached = getFromCache('shop');
  if (cached) return cached;
  
  const shop = await db.items.findAll();
  setCache('shop', shop, CACHE_DURATION);
  return shop;
}

Security

Authentication

All API endpoints use session-based authentication. Implement proper session management:

// Session middleware
import { verifyToken } from '../utils/auth';

export const requireAuth = async (req: Request, res: Response, next: NextFunction) => {
  const session = req.headers['cookie'];
  
  if (!session) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  
  try {
    req.user = await verifyToken(session);
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid session' });
  }
};

API Security

  1. Use HTTPS in production
  2. Rate limit API requests
  3. Validate all inputs
  4. Use prepared statements for SQL
  5. Never expose sensitive data

Contributing

Pull Request Process

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Update documentation
  6. Submit a pull request

Code Review

All PRs will be reviewed for:

  • Code quality
  • Test coverage
  • Documentation
  • Security
  • Performance

Commit Messages

Use conventional commits:

feat: add new shop item filter
fix: resolve authentication issue
docs: update API documentation
chore: update dependencies

Resources