Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .claude/skills/events-handler/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "events-handler",
"description": "Discord.js 이벤트 처리 참고 (현재 프로젝트 패턴 기반)",
"version": "1.0.0",
"author": "Blog Study",
"tags": ["discord", "events", "handlers", "reference"],
"keywords": ["discord.js", "events", "handlers", "patterns", "examples"],
"category": "reference",
"patterns": [
"event-handler-registration",
"error-handling",
"message-handling",
"reaction-handling"
]
}
149 changes: 149 additions & 0 deletions .claude/skills/events-handler/prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Events Handler Reference

Discord.js v14 이벤트 처리 예시 모음 - 현재 프로젝트 패턴 기반

## 사용 방법
새로운 이벤트 핸들러 구현 시 참고

## 기본 패턴 (현재 프로젝트 방식)

```typescript
import type { Client, Message } from 'discord.js';
import { Events } from 'discord.js';

/**
* 커스텀 이벤트 핸들러 등록
*/
export function setupCustomHandler(client: Client): void {
// 메시지 생성 이벤트
client.on(Events.MessageCreate, async (message: Message) => {
try {
// 봇 메시지 무시
if (message.author.bot) return;

// DM 무시
if (!message.guild) return;

// 처리 로직
console.log(`[CustomHandler] Message from ${message.author.id}`);
} catch (error) {
console.error('[CustomHandler] Error:', error);
}
});

console.log('✅ Custom handler registered');
}
```

## 주요 이벤트 타입

### 1. 봇 시작 (ClientReady)
```typescript
client.once(Events.ClientReady, (readyClient) => {
console.log(`✅ Bot logged in as ${readyClient.user.tag}`);
console.log(`📊 Serving ${readyClient.guilds.cache.size} guild(s)`);
});
```

### 2. 메시지 생성 (MessageCreate)
```typescript
client.on(Events.MessageCreate, async (message: Message) => {
if (message.author.bot) return;
if (!message.guild) return;

// 메시지 처리
});
```

### 3. 리액션 추가 (MessageReactionAdd)
```typescript
client.on(Events.MessageReactionAdd, async (reaction, user) => {
if (user.bot) return;

// partial인 경우 fetch
if (reaction.partial) {
await reaction.fetch();
}

// 리액션 처리
});
```

### 4. 리액션 제거 (MessageReactionRemove)
```typescript
client.on(Events.MessageReactionRemove, async (reaction, user) => {
if (user.bot) return;

// 리액션 제거 처리
});
```

### 5. 음성 상태 변경 (VoiceStateUpdate)
```typescript
client.on(Events.VoiceStateUpdate, (oldState, newState) => {
// 음성 채널 입장/퇴장 처리
});
```

### 6. 멤버 입장 (GuildMemberAdd)
```typescript
client.on(Events.GuildMemberAdd, (member) => {
// 새 멤버 환영
});
```

### 7. 에러 처리 (Error)
```typescript
client.on(Events.Error, (error) => {
console.error('❌ Discord client error:', error);
});
```

## 등록 방법 (index.ts)

```typescript
import { createBotClient, setupEventHandlers } from './bot';
import { setupActivityHandler } from './handlers/activity-handler';
import { setupCustomHandler } from './handlers/custom-handler';

async function main(): Promise<void> {
const client = createBotClient();

// 기본 이벤트 핸들러
setupEventHandlers(client);

// 활동 점수 핸들러
setupActivityHandler(client);

// 커스텀 핸들러
setupCustomHandler(client);

await startBot(client, env.DISCORD_TOKEN);
}
```

## 에러 처리 패턴

모든 이벤트 핸들러는 try-catch로 감싸야 합니다:

```typescript
client.on(Events.SomeEvent, async (...args) => {
try {
// 이벤트 처리 로직
} catch (error) {
console.error('[HandlerName] Error:', error);
// 필요시 로깅 또는 알림
}
});
```

## 프로젝트에서 사용 중인 핸들러

- `setupEventHandlers()` - 기본 이벤트 (ready, error, warn)
- `setupActivityHandler()` - 활동 점수 (message, reaction)
- `setupDMHandler()` - DM 처리 (벌금 납부 확인)

## 참고

- Discord.js v14.25.1 Events: https://discord.js.org/docs/packages/discord.js/14.25.1/Classes/Client
- 현재 프로젝트: `packages/bot/src/handlers/` 참고
15 changes: 15 additions & 0 deletions .claude/skills/scheduler-reference/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "scheduler-reference",
"description": "pg-boss 스케줄러 구현 참고 (현재 프로젝트 패턴 기반)",
"version": "1.0.0",
"author": "Blog Study",
"tags": ["scheduler", "pg-boss", "cron", "jobs", "reference"],
"keywords": ["pg-boss", "scheduler", "cron", "worker", "singleton"],
"category": "reference",
"patterns": [
"singleton-pattern",
"isRunning-flag",
"cron-scheduling",
"worker-registration"
]
}
183 changes: 183 additions & 0 deletions .claude/skills/scheduler-reference/prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# Scheduler Reference

pg-boss 기반 스케줄러 구현 참고 - 현재 프로젝트 패턴 기반

## 사용 방법
새로운 스케줄러 구현 시 참고

## 기본 패턴

```typescript
/**
* 스케줄러 클래스 템플릿
*/
export class MyScheduler {
private isRunning = false;

/**
* 실행 중인지 확인
*/
isChecking(): boolean {
return this.isRunning;
}

/**
* 작업 실행
*/
async run(): Promise<Result> {
if (this.isRunning) {
console.log('[MyScheduler] Already in progress, skipping');
return {
timestamp: new Date(),
errors: ['Already in progress'],
};
}

this.isRunning = true;

try {
// 작업 로직 구현
console.log('[MyScheduler] Running...');

return {
timestamp: new Date(),
success: true,
};
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
console.error(`[MyScheduler] Error: ${errorMsg}`);

return {
timestamp: new Date(),
errors: [errorMsg],
};
} finally {
this.isRunning = false;
}
}
}

// Singleton 인스턴스
let instance: MyScheduler | null = null;

export function getMyScheduler(): MyScheduler {
if (!instance) {
instance = new MyScheduler();
}
return instance;
}

export function resetMyScheduler(): void {
instance = null;
}
```

## scheduler-registry.ts에 등록

```typescript
import { getMyScheduler } from './schedulers/my-scheduler';

export async function registerAllJobs(boss: PgBoss, client: Client): Promise<void> {
const myScheduler = getMyScheduler();

// 1. Cron 잡 등록 (매일 09:00)
await boss.schedule('my-job', '0 9 * * *');
console.log(' 📅 Scheduled: my-job (0 9 * * *)');

// 2. 워커 등록
await boss.work('my-job', { batchSize: 1 }, async () => {
await myScheduler.run();
});

console.log(`✅ All jobs registered`);
}
```

## Discord 클라이언트가 필요한 경우

```typescript
export class MyScheduler {
private client: Client | null = null;

setClient(client: Client): void {
this.client = client;
}

async run(): Promise<Result> {
if (!this.client) {
console.error('[MyScheduler] Discord client not set');
return {
timestamp: new Date(),
errors: ['Discord client not set'],
};
}

// client 사용
const channel = await this.client.channels.fetch('CHANNEL_ID');
// ...
}
}

// 등록 시
const myScheduler = getMyScheduler();
myScheduler.setClient(client);
```

## Cron 표현식 예시

| 표현식 | 설명 |
|---------|------|
| `*/5 * * * *` | 5분마다 |
| `0 9 * * *` | 매일 09:00 |
| `0 0 * * 2` | 매주 화요일 00:00 |
| `0 23 * * *` | 매일 23:00 (KST 익일 08:00) |
| `0 0 1 * *` | 매월 1일 00:00 |

## pg-boss 설정

```typescript
import { PgBoss } from 'pg-boss';

// 시작
const boss = new PgBoss(connectionString);
boss.on('error', (error: Error) => console.error('[pg-boss] Error:', error));
await boss.start();

// Cron 잡 등록
await boss.schedule('job-name', 'cron-expression');

// 워커 등록
await boss.work('job-name', { batchSize: 1 }, async () => {
// 작업 실행
});

// 종료
await boss.stop({ graceful: true, timeout: 30000 });
```

## 결과 인터페이스

```typescript
interface Result {
timestamp: Date;
success?: boolean;
errors: string[];
// 기타 필요한 필드
}
```

## 프로젝트 예시 참고

- `rss-poller.ts` - RSS 폴링 (5분)
- `attendance-checker.ts` - 출석 체크 (주간)
- `fine-reminder.ts` - 벌금 리마인더 (일일)
- `round-reporter.ts` - 회차 리포트 (주간)
- `curation-crawler.ts` - 큐레이션 크롤링 (일일)

## 주의사항

1. **동시 실행 방지**: `isRunning` 플래그로 중복 실행 방지
2. **에러 처리**: 모든 에러를 catch하고 결과 객체에 포함
3. **로그**: 진행 상황을 console.log로 출력
4. **Singleton**: 한 인스턴스만 사용 (`getXxx()` 함수)
5. **테스트용 reset**: `resetXxx()` 함수 제공
16 changes: 16 additions & 0 deletions .claude/skills/service-pattern/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "service-pattern",
"description": "DB 서비스 계층 구현 참고 (현재 프로젝트 패턴 기반)",
"version": "1.0.0",
"author": "Blog Study",
"tags": ["service", "database", "drizzle", "orm", "reference"],
"keywords": ["drizzle-orm", "service-layer", "crud", "singleton", "repository"],
"category": "reference",
"patterns": [
"singleton-pattern",
"error-handling",
"crud-operations",
"complex-queries",
"transactions"
]
}
Loading