Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1d933f5
docs: expand CLAUDE.md with package structure, commands, and version …
Palbahngmiyine Jan 27, 2026
8eb0072
chore: upgrade Gradle 9.3.0, Kotlin 2.3.0, and dependencies
Palbahngmiyine Jan 27, 2026
4befdba
docs: add Tidy First principles to CLAUDE.md
Palbahngmiyine Jan 27, 2026
207d272
refactor: migrate from java.time to kotlin.time for date/time handling
Palbahngmiyine Jan 27, 2026
618f129
test: add unit tests for kotlin.time migration
Palbahngmiyine Jan 27, 2026
9a1498f
docs: add AGENTS.md knowledge base for SDK navigation and patterns
Palbahngmiyine Jan 27, 2026
f3c2528
feat(kakao): add BMS_FREE message type and BMS storage types
Palbahngmiyine Jan 27, 2026
0ed40eb
feat(kakao): add BMS Free enum types (ChatBubbleType, ButtonType)
Palbahngmiyine Jan 27, 2026
97c2c4a
feat(kakao): add BMS Free data classes (Button, Commerce, Coupon, Video)
Palbahngmiyine Jan 27, 2026
f5d2c73
feat(kakao): add BMS Free WideItem and Carousel models
Palbahngmiyine Jan 27, 2026
f733370
feat(kakao): expand KakaoBmsOption with all BMS Free fields
Palbahngmiyine Jan 27, 2026
f37b7a6
test(kakao): add BMS Free serialization tests
Palbahngmiyine Jan 27, 2026
54cdec3
Update gitignore rule
Palbahngmiyine Jan 27, 2026
7175035
test(kakao): add BMS Free E2E test suite
Palbahngmiyine Jan 28, 2026
df8e0e3
test(e2e): improve error output to display failedMessageList details
Palbahngmiyine Jan 28, 2026
3ae6ba5
fix(e2e): correct BMS Free test field usage and StorageType mapping
Palbahngmiyine Jan 28, 2026
d7c974b
feat(dto): add LocalDateTime support for date fields
Palbahngmiyine Jan 28, 2026
e2889f6
build: add kotlinx.serialization to shadow JAR relocate list
Palbahngmiyine Jan 28, 2026
7078b20
Remove NurigoApp.kt
Palbahngmiyine Jan 29, 2026
d74385d
refactor: extract BaseE2ETest and E2ETestUtils for E2E test infrastru…
Palbahngmiyine Jan 29, 2026
756741a
test(e2e): add comprehensive E2E tests for solapi-kotlin SDK
Palbahngmiyine Jan 29, 2026
b20f05c
refactor: use stdlib toKotlinInstant extension for LocalDateTime conv…
Palbahngmiyine Jan 29, 2026
510b0ad
refactor: extract uploadImage utility to BaseE2ETest
Palbahngmiyine Jan 29, 2026
1c8f412
refactor: make BmsFreeE2ETest extend BaseE2ETest
Palbahngmiyine Jan 29, 2026
c170e1e
refactor: make ScheduledMessageE2ETest extend BaseE2ETest
Palbahngmiyine Jan 29, 2026
6e1937a
refactor: add multi-module auto-discovery to settings.gradle.kts
Palbahngmiyine Jan 29, 2026
31a8369
feat: add Java and Kotlin example modules
Palbahngmiyine Jan 29, 2026
3b1ce5d
docs: rewrite README with comprehensive SDK documentation
Palbahngmiyine Jan 30, 2026
c21dbb8
refactor: remove FriendTalk (CTA/CTI) support
Palbahngmiyine Jan 30, 2026
f365063
test: restore FriendTalk E2E tests for backward compatibility
Palbahngmiyine Jan 30, 2026
514528e
docs: prioritize Java examples in README
Palbahngmiyine Jan 30, 2026
b260cca
docs: add Java to README title
Palbahngmiyine Jan 30, 2026
13cd593
docs: update environment variables table formatting and description
Palbahngmiyine Jan 30, 2026
9cddef5
docs: add JDK 8 user guide and improve code examples
Palbahngmiyine Jan 30, 2026
83d6ce8
docs: add AI quick start guide section to README
Palbahngmiyine Jan 30, 2026
ce638aa
Update README.md
Palbahngmiyine Jan 30, 2026
f263440
Update README.md
Palbahngmiyine Jan 30, 2026
c5aa9f8
docs: add LLM guide and version sync hook
Palbahngmiyine Jan 30, 2026
82abfa1
docs: add JDK 8 compatible Java examples to LLM guide
Palbahngmiyine Jan 30, 2026
1fcf092
refactor: remove explicit ATA type from Kakao Alimtalk examples
Palbahngmiyine Jan 30, 2026
6614de8
docs: add Agent Workflow section and translate LLM guide to English
Palbahngmiyine Jan 30, 2026
ad1ace1
refactor: consolidate Kotlin compile tasks and update version require…
Palbahngmiyine Jan 30, 2026
e7d6838
build: auto-exclude example modules during publish tasks
Palbahngmiyine Jan 30, 2026
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
43 changes: 43 additions & 0 deletions .claude/scripts/sync-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
# 버전 동기화 스크립트
# build.gradle.kts의 버전이 변경되면 README.md와 LLM_GUIDE.md에 반영

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"

GRADLE_FILE="$PROJECT_ROOT/build.gradle.kts"
README_FILE="$PROJECT_ROOT/README.md"
LLM_GUIDE_FILE="$PROJECT_ROOT/LLM_GUIDE.md"

# build.gradle.kts가 수정된 경우에만 실행
if [[ -n "$CLAUDE_FILE_PATHS" ]]; then
if ! echo "$CLAUDE_FILE_PATHS" | grep -q "build.gradle.kts"; then
exit 0
fi
fi

# build.gradle.kts에서 버전 추출 (예: version = "1.1.0" -> 1.1.0)
VERSION=$(grep -E '^version\s*=' "$GRADLE_FILE" | head -1 | sed -E 's/.*"([0-9]+\.[0-9]+\.[0-9]+)".*/\1/')

if [[ -z "$VERSION" ]] || [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "유효한 버전을 찾을 수 없습니다: $VERSION"
exit 1
fi

# README.md 버전 업데이트
if [[ -f "$README_FILE" ]]; then
# com.solapi:sdk:X.X.X 패턴 업데이트
sed -i '' -E "s/(com\.solapi:sdk:)[0-9]+\.[0-9]+\.[0-9]+/\1$VERSION/g" "$README_FILE"
# <version>X.X.X</version> 패턴
sed -i '' -E "s|(<version>)[0-9]+\.[0-9]+\.[0-9]+(</version>)|\1$VERSION\2|g" "$README_FILE"
fi

# LLM_GUIDE.md 버전 업데이트
if [[ -f "$LLM_GUIDE_FILE" ]]; then
sed -i '' -E "s/(com\.solapi:sdk:)[0-9]+\.[0-9]+\.[0-9]+/\1$VERSION/g" "$LLM_GUIDE_FILE"
sed -i '' -E "s|(<version>)[0-9]+\.[0-9]+\.[0-9]+(</version>)|\1$VERSION\2|g" "$LLM_GUIDE_FILE"
fi

echo "버전 $VERSION 동기화 완료"
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# SOLAPI API Credentials
SOLAPI_API_KEY=
SOLAPI_API_SECRET=

# Kakao Business Channel
KAKAO_PF_ID=

# Phone Numbers (Optional)
SENDER_NUMBER=
TEST_PHONE_NUMBER=
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ signing.gpg
*.gpg
*.asc
secret.key
.env
.env.local

# Generated files
/docs/
manual/
manual/

# OMO, OMC
.sisyphus/
.omc/
187 changes: 187 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# SOLAPI Kotlin SDK - Knowledge Base

**Generated:** 2026-01-27 | **Commit:** 618f129 | **Branch:** main

## CRITICAL: Development Principles

**MUST follow `CLAUDE.md` development principles:**

| Principle | Rule |
|-----------|------|
| **Tidy First** | NEVER mix structural and behavioral changes in a single commit |
| **Commit Separation** | `refactor:` (structural) vs `feat:`/`fix:` (behavioral) in separate commits |
| **TDD** | Write tests first (Red → Green → Refactor) |
| **Single Responsibility** | Classes/methods have single responsibility only |
| **Tidy Code First** | Clean up target area code before adding features |

```bash
# Correct commit order
git commit -m "refactor: extract validation logic to separate method"
git commit -m "feat: add phone number format validation"

# Forbidden (mixed commit)
git commit -m "feat: add validation and refactor code" # ❌ FORBIDDEN
```

---

## OVERVIEW

Kotlin/Java SDK for SOLAPI messaging platform. Supports SMS, LMS, MMS, Kakao Alimtalk/Brand Message, Naver Smart Notification, RCS, Fax, and Voice messaging.

## STRUCTURE

```
src/main/java/com/solapi/sdk/
├── SolapiClient.kt # Entry point (use this)
├── NurigoApp.kt # DEPRECATED - do not use
└── message/
├── service/ # API operations (send, query, templates)
├── model/ # Domain models (Message, options)
│ └── kakao/ # 19 files - Kakao templates, buttons, options
├── dto/ # Request/Response DTOs
├── exception/ # Exception hierarchy (8 types)
└── lib/ # Internal utilities (auth, helpers)
```

## WHERE TO LOOK

| Task | Location | Notes |
|------|----------|-------|
| **Initialize SDK** | `SolapiClient.kt` | `createInstance(apiKey, secretKey)` |
| **Send messages** | `service/DefaultMessageService.kt` | `send(message)` or `send(messages)` |
| **Query messages** | `service/DefaultMessageService.kt` | `getMessageList(params)` |
| **Upload files** | `service/DefaultMessageService.kt` | `uploadFile(file, type)` for MMS/Fax |
| **Kakao Alimtalk** | `service/DefaultMessageService.kt` | 11 template methods |
| **Create Message** | `model/Message.kt` | Data class with all message options |
| **Kakao options** | `model/kakao/KakaoOption.kt` | Alimtalk/FriendTalk config |
| **Handle errors** | `exception/` | Catch specific `Solapi*Exception` types |
| **HTTP layer** | `service/MessageHttpService.kt` | Retrofit interface (internal) |
| **Auth** | `lib/Authenticator.kt` | HMAC-SHA256 (internal, auto-injected) |

## CODE PATTERNS

### Serialization
```kotlin
@Serializable
data class Message(
var to: String? = null,
var from: String? = null,
// All fields nullable with defaults for flexibility
)
```
- **ALWAYS** use `@Serializable` annotation
- **ALWAYS** use `kotlinx.serialization` (not Jackson/Gson)
- **ALWAYS** provide nullable fields with defaults

### Service Methods
```kotlin
@JvmOverloads // Java interop
@Throws(SolapiMessageNotReceivedException::class, ...)
fun send(messages: List<Message>, config: SendRequestConfig? = null): MultipleDetailMessageSentResponse
```
- **ALWAYS** annotate with `@JvmOverloads` for optional params
- **ALWAYS** declare `@Throws` for checked exceptions

### Exception Handling
```kotlin
// Internal: Map error codes to exceptions
when (errorResponse.errorCode) {
"ValidationError" -> throw SolapiBadRequestException(msg)
"InvalidApiKey" -> throw SolapiInvalidApiKeyException(msg)
else -> throw SolapiUnknownException(msg)
}
```
- Exceptions are `sealed interface` based (SolapiException)
- 8 specific exception types

### Phone Number Normalization
```kotlin
init {
from = from?.replace("-", "")
to = to?.replace("-", "")
}
```
- Dashes auto-stripped from phone numbers in `Message.init`

### Test Conventions
```kotlin
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.assertFailsWith

class AuthenticatorTest {
@Test
fun `generateAuthInfo returns HMAC-SHA256 format`() {
// Given
val authenticator = Authenticator("api-key", "secret")
// When
val result = authenticator.generateAuthInfo()
// Then
assertTrue(result.startsWith("HMAC-SHA256 "))
}
}
```
- **ALWAYS** use `kotlin.test` (NOT JUnit directly)
- **ALWAYS** use Given-When-Then comment structure
- **ALWAYS** use backtick method names for readability

## ANTI-PATTERNS

| Forbidden | Required |
|-----------|----------|
| `NurigoApp.initialize()` | `SolapiClient.createInstance()` |
| Direct `DefaultMessageService()` | Use factory via `SolapiClient` |
| Catch generic `Exception` | Catch specific `Solapi*Exception` |
| `net.nurigo.sdk` imports | `com.solapi.sdk` package only |
| Jackson/Gson serialization | `kotlinx.serialization` only |
| Mixed structural+behavioral commits | Separate commits per Tidy First |

## INTERNAL CLASSES (Do Not Use Directly)

- `Authenticator` - HMAC auth (auto-injected via interceptor)
- `ErrorResponse` - Internal error DTO
- `MessageHttpService` - Retrofit interface
- `JsonSupport` - Serialization config
- `MapHelper`, `Criterion` - Internal utilities

## EXCEPTION TYPES

| Exception | When Thrown |
|-----------|-------------|
| `SolapiApiKeyException` | Empty/missing API key |
| `SolapiInvalidApiKeyException` | Invalid credentials |
| `SolapiBadRequestException` | Validation error, bad input |
| `SolapiEmptyResponseException` | Server returned empty body |
| `SolapiFileUploadException` | File upload failed |
| `SolapiMessageNotReceivedException` | All messages failed (has `failedMessageList`) |
| `SolapiUnknownException` | Unclassified server error |

## KAKAO INTEGRATION

19 model files in `model/kakao/`:
- `KakaoOption` - Main config (pfId, templateId, variables)
- `KakaoAlimtalkTemplate*` - Template CRUD models
- `KakaoBrandMessageTemplate` - Brand message with carousels
- `KakaoButton`, `KakaoButtonType` - Button configurations

Template workflow: `getKakaoAlimtalkTemplateCategories()` → `createKakaoAlimtalkTemplate()` → `requestKakaoAlimtalkTemplateInspection()`

## BUILD & TEST

```bash
./gradlew clean build test # Full build
./gradlew test # Tests only
./gradlew shadowJar # Fat JAR with relocated deps
```

**Shadow JAR**: Dependencies relocated to `com.solapi.shadow.*` to prevent conflicts.

## NOTES

- **Java 8 target**: Code must work on JVM 1.8
- **Source location**: Kotlin in `src/main/java/` (unconventional but intentional)
- **Tests**: `src/test/kotlin/`, `kotlin.test` (Kotlin native), Given-When-Then style
- **Version**: Auto-generated at `build/generated/source/kotlin/com/solapi/sdk/Version.kt`
- **Docs**: Dokka output to `./docs/`, run `./gradlew dokkaGeneratePublicationHtml`
Loading