diff --git a/README.md b/README.md index 5032de1e..e0fc08b5 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ npx skills add pachca/openapi | [Go](sdk/go/README.md) | `github.com/pachca/go-sdk` | Go modules | | [Kotlin](sdk/kotlin/README.md) | `com.pachca:sdk` | JitPack | | [Swift](sdk/swift/README.md) | `PachcaSDK` | SPM | +| [C#](sdk/csharp/README.md) | `Pachca.Sdk` | NuGet | Все SDK следуют единому паттерну: `PachcaClient(token)` → `client.service.method(request)`. @@ -121,14 +122,14 @@ SDK генерируются из `openapi.yaml` и публикуются ав ```bash npx @pachca/generator --output ./generated --lang typescript -npx @pachca/generator --output ./generated --lang typescript,python,go +npx @pachca/generator --output ./generated --lang typescript,python,go,kotlin,swift,csharp ``` | Параметр | Описание | |----------|----------| | `--spec ` | Путь или URL к OpenAPI 3.0 YAML (по умолчанию: `https://dev.pachca.com/openapi.yaml`) | | `--output ` | Директория для сгенерированного кода | -| `--lang ` | Языки через запятую: `typescript`, `python`, `go`, `kotlin`, `swift` | +| `--lang ` | Языки через запятую: `typescript`, `python`, `go`, `kotlin`, `swift`, `csharp` | | `--examples` | Генерировать `examples.json` с примерами вызовов | **Документация**: https://dev.pachca.com/guides/sdk/overview @@ -192,15 +193,16 @@ bun turbo generate # TypeSpec → openapi.yaml + SDK │ └── docs/ # Next.js 16 сайт документации (@pachca/docs) ├── packages/ │ ├── spec/ # TypeSpec спецификация + workflows.ts (@pachca/spec) -│ ├── generator/ # SDK код-генератор для 5 языков (@pachca/generator) +│ ├── generator/ # SDK код-генератор для 6 языков (@pachca/generator) │ ├── openapi-parser/ # Парсер OpenAPI-спеки (@pachca/openapi-parser) │ └── cli/ # CLI для работы с API (@pachca/cli) -├── sdk/ # SDK для 5 языков (генерируются @pachca/generator) +├── sdk/ # SDK для 6 языков (генерируются @pachca/generator) │ ├── typescript/ # npm │ ├── python/ # PyPI │ ├── go/ # Go modules │ ├── kotlin/ # JitPack -│ └── swift/ # SPM +│ ├── swift/ # SPM +│ └── csharp/ # NuGet ├── skills/ # Agent Skills (генерируются → apps/docs/public/.well-known/skills/) ├── .github/workflows/ # CI/CD (check, sdk, deploy, gitlab) ├── Package.swift # Корневой Swift Package (копируется из sdk/swift при CI) @@ -221,7 +223,7 @@ typespec.tsp (packages/spec) openapi.yaml ──────────────────────────┐ │ │ ▼ ▼ -apps/docs sdk/* (5 языков) +apps/docs sdk/* (6 языков) │ │ │ generate-llms │ CI: generate + publish │ next build ▼ diff --git a/apps/docs/components/api/code-examples.tsx b/apps/docs/components/api/code-examples.tsx index 40b57f09..8908459f 100644 --- a/apps/docs/components/api/code-examples.tsx +++ b/apps/docs/components/api/code-examples.tsx @@ -23,7 +23,7 @@ interface CodeExamplesProps { className?: string; } -type Language = 'curl' | 'cli' | 'typescript' | 'python' | 'go' | 'kotlin' | 'swift'; +type Language = 'curl' | 'cli' | 'typescript' | 'python' | 'go' | 'kotlin' | 'swift' | 'csharp'; const STORAGE_KEY = 'pachca-docs-code-lang'; @@ -35,6 +35,7 @@ const languageLabels: Record = { go: 'Go', kotlin: 'Kotlin', swift: 'Swift', + csharp: 'C#', }; export function CodeExamples({ @@ -193,6 +194,7 @@ function getLanguageForHighlight(lang: Language): string { go: 'go', kotlin: 'kotlin', swift: 'swift', + csharp: 'csharp', }; return languageMap[lang]; } diff --git a/apps/docs/components/mdx/mdx-components.tsx b/apps/docs/components/mdx/mdx-components.tsx index 083f623f..dac3f62d 100644 --- a/apps/docs/components/mdx/mdx-components.tsx +++ b/apps/docs/components/mdx/mdx-components.tsx @@ -269,7 +269,7 @@ export async function ApiCards() { * params — override query parameter values; only specified + required params are included (curl/cli only) * responseMode — "full" (default): all fields with values; "minimal": null for nullable, [] for optional arrays * lang — single language: no dropdown/header, renders as simple code block - * langs — filter languages in multi-language dropdown (e.g. ["typescript", "python", "go", "kotlin", "swift"]) + * langs — filter languages in multi-language dropdown (e.g. ["typescript", "python", "go", "kotlin", "swift", "csharp"]) * operations — multiple operations in one block (single-language mode only) * showInit — whether to include Client_Init (default: true) */ @@ -280,9 +280,9 @@ interface ApiCodeExampleProps { show?: 'request' | 'response' | 'both'; params?: Record; responseMode?: 'full' | 'minimal'; - lang?: 'typescript' | 'python' | 'go' | 'kotlin' | 'swift' | 'curl' | 'cli'; - langs?: Array<'typescript' | 'python' | 'go' | 'kotlin' | 'swift' | 'curl' | 'cli'>; - defaultLang?: 'typescript' | 'python' | 'go' | 'kotlin' | 'swift' | 'curl' | 'cli'; + lang?: 'typescript' | 'python' | 'go' | 'kotlin' | 'swift' | 'csharp' | 'curl' | 'cli'; + langs?: Array<'typescript' | 'python' | 'go' | 'kotlin' | 'swift' | 'csharp' | 'curl' | 'cli'>; + defaultLang?: 'typescript' | 'python' | 'go' | 'kotlin' | 'swift' | 'csharp' | 'curl' | 'cli'; operations?: Array<{ id: string; comment?: string }>; showInit?: boolean; } diff --git a/apps/docs/components/mdx/sdk-commands.tsx b/apps/docs/components/mdx/sdk-commands.tsx index b1cdc69b..59516142 100644 --- a/apps/docs/components/mdx/sdk-commands.tsx +++ b/apps/docs/components/mdx/sdk-commands.tsx @@ -6,7 +6,7 @@ import { EndpointLink } from '@/components/api/endpoint-link'; const METHOD_ORDER: Record = { POST: 0, GET: 1, PUT: 2, PATCH: 3, DELETE: 4 }; -type SdkLanguage = 'typescript' | 'python' | 'go' | 'kotlin' | 'swift'; +type SdkLanguage = 'typescript' | 'python' | 'go' | 'kotlin' | 'swift' | 'csharp'; /** Tag name → service property: "Group tags" → "groupTags" */ function tagToProperty(tag: string): string { @@ -32,6 +32,11 @@ function camelToSnake(str: string): string { .toLowerCase(); } +/** camelCase → PascalCase */ +function camelToPascal(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} + function formatMethodCall(tag: string, operationId: string, lang: SdkLanguage): string { const service = tagToProperty(tag); const method = operationToMethod(operationId); @@ -40,7 +45,9 @@ function formatMethodCall(tag: string, operationId: string, lang: SdkLanguage): case 'python': return `client.${camelToSnake(service)}.${camelToSnake(method)}()`; case 'go': - return `client.${service.charAt(0).toUpperCase() + service.slice(1)}.${method.charAt(0).toUpperCase() + method.slice(1)}()`; + return `client.${camelToPascal(service)}.${camelToPascal(method)}()`; + case 'csharp': + return `client.${camelToPascal(service)}.${camelToPascal(method)}Async()`; default: return `client.${service}.${method}()`; } diff --git a/apps/docs/content/guides/sdk/csharp.mdx b/apps/docs/content/guides/sdk/csharp.mdx new file mode 100644 index 00000000..0bf2158e --- /dev/null +++ b/apps/docs/content/guides/sdk/csharp.mdx @@ -0,0 +1,281 @@ +--- +title: CSharp +description: Типизированный клиент для Pachca API на C# с async/await и автопагинацией +--- + +# CSharp + + + NuGet + + +Типизированный клиент для Pachca API. Работает в .NET 8+ с поддержкой `async/await` и `CancellationToken`. + +## Быстрый старт + + + + +```bash +dotnet add package Pachca.Sdk +``` + + + + +Получите API-токен в интерфейсе Пачки: **Настройки** > **Автоматизации** > **API** (подробнее — [Авторизация](/api/authorization)). + + + + + + + + + + + +## Инициализация + +```csharp +using Pachca.Sdk; + +// Стандартное подключение +using var client = new PachcaClient("YOUR_TOKEN"); + +// С кастомным базовым URL +using var client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.com/api/shared/v1"); +``` + +| Параметр | Тип | По умолчанию | Описание | +|----------|-----|-------------|----------| +| `token` | `string` | — | Bearer-токен для авторизации | +| `baseUrl` | `string` | `https://api.pachca.com/api/shared/v1` | Базовый URL API | + +Клиент реализует `IDisposable` — рекомендуется использовать `using` для автоматического освобождения ресурсов. + +## Все методы + + + +## Запросы + +Все методы — асинхронные и возвращают `Task`. + +**GET с параметрами:** + + + +**POST с телом запроса:** + + + +**Простой вызов по ID:** + + + +## Пагинация + +Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит поле `Meta.Paginate.NextPage` — курсор для следующей страницы. + +### Ручная пагинация + +```csharp +var chats = new List(); +string? cursor = null; + +do +{ + var response = await client.Chats.ListChatsAsync(cursor: cursor); + chats.AddRange(response.Data); + cursor = response.Meta?.Paginate?.NextPage; +} while (cursor != null); +``` + +### Автопагинация + +Для каждого метода с пагинацией есть `*AllAsync()` вариант, который автоматически обходит все страницы и возвращает плоский список: + +```csharp +// Все чаты одним списком +var chats = await client.Chats.ListChatsAllAsync(); +Console.WriteLine($"Всего: {chats.Count}"); +``` + +Доступные методы автопагинации: + +| Метод | Возвращает | +|-------|-----------| +| `Security.GetAuditEventsAllAsync()` | `List` | +| `Bots.GetWebhookEventsAllAsync()` | `List` | +| `Chats.ListChatsAllAsync()` | `List` | +| `GroupTags.ListTagsAllAsync()` | `List` | +| `GroupTags.GetTagUsersAllAsync()` | `List` | +| `Members.ListMembersAllAsync()` | `List` | +| `Messages.ListChatMessagesAllAsync()` | `List` | +| `Reactions.ListReactionsAllAsync()` | `List` | +| `Search.SearchChatsAllAsync()` | `List` | +| `Search.SearchMessagesAllAsync()` | `List` | +| `Search.SearchUsersAllAsync()` | `List` | +| `Tasks.ListTasksAllAsync()` | `List` | +| `Users.ListUsersAllAsync()` | `List` | + +## Обработка ошибок + +SDK выбрасывает два типа исключений: + +### ApiError + +Возникает при ошибках `400`, `403`, `404`, `409`, `410`, `422`: + +```csharp +using Pachca.Sdk; + +try +{ + await client.Chats.CreateChatAsync(request); +} +catch (ApiError e) +{ + foreach (var err in e.Errors) + { + Console.WriteLine($"{err.Key}: {err.Message}"); // "name", "не может быть пустым" + Console.WriteLine(err.Code); // "blank" + } +} +``` + +Поля `ApiErrorItem`: + +| Поле | Тип | Описание | +|------|-----|----------| +| `Key` | `string` | Поле, вызвавшее ошибку | +| `Value` | `string?` | Переданное значение | +| `Message` | `string` | Текст ошибки | +| `Code` | `ValidationErrorCode` | Код валидации (`Blank`, `Invalid`, `Taken`, ...) | +| `Payload` | `string?` | Дополнительные данные | + +### OAuthError + +Возникает при ошибке авторизации (`401`): + +```csharp +using Pachca.Sdk; + +try +{ + await client.Profile.GetProfileAsync(); +} +catch (OAuthError e) +{ + Console.WriteLine(e.ErrorDescription); // "Token not found" +} +``` + +## Повторные запросы + +SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx`: + +- До **3 повторов** на каждый запрос +- Если сервер вернул заголовок `Retry-After` — ждёт указанное время +- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек +- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов + +## Отмена запросов + +Все асинхронные методы поддерживают `CancellationToken`: + +```csharp +using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); +var users = await client.Users.ListUsersAsync(cancellationToken: cts.Token); +``` + +## Загрузка файлов + +Загрузка файла — трёхшаговый процесс: + +```csharp +// 1. Получить параметры загрузки +var uploadParams = await client.Common.GetUploadParamsAsync(); + +// 2. Загрузить файл на S3 +using var fileStream = File.OpenRead("photo.png"); +await client.Common.UploadFileAsync(uploadParams, fileStream, "photo.png"); + +// 3. Прикрепить к сообщению (используя key из uploadParams) +await client.Messages.CreateMessageAsync(new MessageCreateRequest +{ + Message = new MessageCreateRequestMessage + { + EntityId = chatId, + Content = "Фото", + Files = new List { new() { Key = uploadParams.Key, Name = "photo.png" } } + } +}); +``` + +## Сериализация + +SDK автоматически конвертирует имена полей между PascalCase (C#) и snake_case (API): + +```csharp +// Вы пишете: +new ChatCreateRequestChat { MemberIds = new List { 123 }, GroupTagIds = new List { 456 } } + +// SDK отправляет: +{ "member_ids": [123], "group_tag_ids": [456] } + +// API возвращает: +{ "last_message_at": "2025-01-01T00:00:00Z" } + +// Вы получаете: +chat.LastMessageAt // DateTimeOffset +``` + +## Типы + +Все типы доступны из пространства имён `Pachca.Sdk`: + +```csharp +using Pachca.Sdk; + +// Модели +Chat chat; +Message message; +User user; +Task task; +GroupTag groupTag; + +// Запросы +ChatCreateRequest chatRequest; +MessageCreateRequest messageRequest; +TaskCreateRequest taskRequest; + +// Перечисления +AuditEventKey eventKey; +ChatAvailability availability; +ChatMemberRole role; +TaskStatus status; +ValidationErrorCode errorCode; + +// Ошибки +ApiError apiError; +OAuthError oauthError; +``` + +## Примеры + + + diff --git a/apps/docs/content/guides/sdk/overview.mdx b/apps/docs/content/guides/sdk/overview.mdx index d0603fa8..9721819e 100644 --- a/apps/docs/content/guides/sdk/overview.mdx +++ b/apps/docs/content/guides/sdk/overview.mdx @@ -20,6 +20,7 @@ description: Типизированные клиентские библиоте go get · sync · net/http JitPack · coroutines · Ktor SPM · async throws · URLSession + NuGet · async/await · HttpClient Автоматически обновляются при каждом обновлении спецификации. [Исходный код на GitHub](https://github.com/pachca/openapi/tree/main/sdk). @@ -37,7 +38,7 @@ description: Типизированные клиентские библиоте npx @pachca/generator --output ./generated --lang typescript # Несколько языков -npx @pachca/generator --output ./generated --lang typescript,python,go +npx @pachca/generator --output ./generated --lang typescript,python,go,kotlin,swift,csharp # Из локального файла или произвольного URL npx @pachca/generator --spec openapi.yaml --output ./generated --lang typescript @@ -48,7 +49,7 @@ npx @pachca/generator --spec https://example.com/openapi.yaml --output ./generat |----------|----------| | `--spec ` | Путь или URL к OpenAPI 3.0 YAML (по умолчанию: `https://dev.pachca.com/openapi.yaml`) | | `--output ` | Директория для сгенерированного кода | -| `--lang ` | Языки через запятую: `typescript`, `python`, `go`, `kotlin`, `swift` | +| `--lang ` | Языки через запятую: `typescript`, `python`, `go`, `kotlin`, `swift`, `csharp` | | `--examples` | Генерировать `examples.json` с примерами вызовов | ## Возможности @@ -64,17 +65,17 @@ npx @pachca/generator --spec https://example.com/openapi.yaml --output ./generat ## Пример - + ## Сравнение языков -| | TypeScript | Python | Go | Kotlin | Swift | -|---|---|---|---|---|---| -| **Пакет** | `@pachca/sdk` | `pachca-sdk` | `go get ...` | `com.pachca:pachca-sdk` | `PachcaSDK` | -| **Менеджер** | npm | PyPI | Go modules | JitPack (Gradle) | SPM | -| **Async** | `await` | `await` (asyncio) | синхронный | `suspend` (coroutines) | `try await` | -| **HTTP** | fetch | httpx | net/http | Ktor | URLSession | -| **Naming** | camelCase | snake_case | PascalCase | camelCase | camelCase | -| **Cleanup** | — | `await client.close()` | — | `client.close()` | — | -| **Требования** | Node.js 18+ | Python 3.10+ | Go 1.24+ | Kotlin 2.2+, Java 11+ | Swift 5.9+, macOS 13+, iOS 16+ | +| | TypeScript | Python | Go | Kotlin | Swift | C# | +|---|---|---|---|---|---|---| +| **Пакет** | `@pachca/sdk` | `pachca-sdk` | `go get ...` | `com.pachca:pachca-sdk` | `PachcaSDK` | `Pachca.Sdk` | +| **Менеджер** | npm | PyPI | Go modules | JitPack (Gradle) | SPM | NuGet | +| **Async** | `await` | `await` (asyncio) | синхронный | `suspend` (coroutines) | `try await` | `await` | +| **HTTP** | fetch | httpx | net/http | Ktor | URLSession | HttpClient | +| **Naming** | camelCase | snake_case | PascalCase | camelCase | camelCase | PascalCase | +| **Cleanup** | — | `await client.close()` | — | `client.close()` | — | `client.Dispose()` | +| **Требования** | Node.js 18+ | Python 3.10+ | Go 1.24+ | Kotlin 2.2+, Java 11+ | Swift 5.9+, macOS 13+, iOS 16+ | .NET 8+ | diff --git a/apps/docs/content/updates.mdx b/apps/docs/content/updates.mdx index 4717af93..3b9139f2 100644 --- a/apps/docs/content/updates.mdx +++ b/apps/docs/content/updates.mdx @@ -9,6 +9,12 @@ useUpdatesComponent: true Автоматически отслеживайте обновления: подпишитесь на [RSS-ленту](/feed.xml) или используйте [markdown-версию этой страницы](/updates.md) для интеграции с инструментами и AI-агентами. + + +## C# SDK + +Добавлен официальный [C# SDK](/guides/sdk/csharp) для .NET 8+. Полная поддержка async/await, CancellationToken, автопагинация через `*AllAsync()` методы и автоматические повторы при `429`. Установка: `dotnet add package Pachca.Sdk`. + ## Конструктор форм diff --git a/apps/docs/lib/mdx-expander.ts b/apps/docs/lib/mdx-expander.ts index 68e0db00..c3fbc37e 100644 --- a/apps/docs/lib/mdx-expander.ts +++ b/apps/docs/lib/mdx-expander.ts @@ -478,6 +478,8 @@ export async function expandMdxComponents(content: string): Promise { return `client.${camelToSnake(service)}.${camelToSnake(method)}()`; case 'go': return `client.${service.charAt(0).toUpperCase() + service.slice(1)}.${method.charAt(0).toUpperCase() + method.slice(1)}()`; + case 'csharp': + return `client.${service.charAt(0).toUpperCase() + service.slice(1)}.${method.charAt(0).toUpperCase() + method.slice(1)}Async()`; default: return `client.${service}.${method}()`; } @@ -506,7 +508,7 @@ export async function expandMdxComponents(content: string): Promise { const apiCodeRegex = //g; const apiCodeMatches = [...result.matchAll(apiCodeRegex)]; - const SDK_LANGS = ['typescript', 'python', 'go', 'kotlin', 'swift']; + const SDK_LANGS = ['typescript', 'python', 'go', 'kotlin', 'swift', 'csharp']; for (const match of apiCodeMatches) { const [fullMatch, attrs] = match; diff --git a/apps/docs/lib/sdk-examples.ts b/apps/docs/lib/sdk-examples.ts index a275a25a..6c803501 100644 --- a/apps/docs/lib/sdk-examples.ts +++ b/apps/docs/lib/sdk-examples.ts @@ -12,7 +12,7 @@ interface ExampleEntry { type ExamplesJson = Record; -const SDK_LANGUAGES = ['typescript', 'python', 'go', 'kotlin', 'swift'] as const; +const SDK_LANGUAGES = ['typescript', 'python', 'go', 'kotlin', 'swift', 'csharp'] as const; type SdkLanguage = (typeof SDK_LANGUAGES)[number]; let cached: Record | null = null; @@ -43,6 +43,7 @@ const COMMENT_PREFIX: Record = { go: '//', kotlin: '//', swift: '//', + csharp: '//', }; function buildImportBlock( @@ -74,6 +75,9 @@ function buildImportBlock( case 'swift': { return `import PachcaSDK`; } + case 'csharp': { + return `using Pachca.Sdk;`; + } } } diff --git a/apps/docs/lib/tabs-config.ts b/apps/docs/lib/tabs-config.ts index f10b1954..e56c2808 100644 --- a/apps/docs/lib/tabs-config.ts +++ b/apps/docs/lib/tabs-config.ts @@ -65,6 +65,7 @@ export const GUIDE_SECTIONS: SidebarSection[] = [ { title: 'Go', path: '/guides/sdk/go' }, { title: 'Kotlin', path: '/guides/sdk/kotlin' }, { title: 'Swift', path: '/guides/sdk/swift' }, + { title: 'CSharp', path: '/guides/sdk/csharp' }, ], }, { title: 'Сценарии', path: '/guides/workflows' }, diff --git a/apps/docs/public/guides/sdk/csharp.md b/apps/docs/public/guides/sdk/csharp.md new file mode 100644 index 00000000..dcfc8a30 --- /dev/null +++ b/apps/docs/public/guides/sdk/csharp.md @@ -0,0 +1,430 @@ + +# CSharp + +[Pachca.Sdk](https://www.nuget.org/packages/Pachca.Sdk) NuGet + + +Типизированный клиент для Pachca API. Работает в .NET 8+ с поддержкой `async/await` и `CancellationToken`. + +## Быстрый старт + + + ### Шаг 1. Установка + +```bash +dotnet add package Pachca.Sdk +``` + + + ### Шаг 2. Создание клиента + +Получите API-токен в интерфейсе Пачки: **Настройки** > **Автоматизации** > **API** (подробнее — [Авторизация](/api/authorization)). + +```csharp +using Pachca.Sdk; + +using var client = new PachcaClient("YOUR_TOKEN"); +``` + + + ### Шаг 3. Первый запрос + +```csharp +using Pachca.Sdk; + +// Получение профиля +var response = await client.Profile.GetProfileAsync(); +// → User(Id: int, FirstName: string, LastName: string, Nickname: string, Email: string, PhoneNumber: string, Department: string, Title: string, Role: UserRole, Suspended: bool, InviteStatus: InviteStatus, ListTags: List, CustomProperties: List, UserStatus: UserStatus(Emoji: string, Title: string, ExpiresAt: DateTimeOffset?, IsAway: bool, AwayMessage: UserStatusAwayMessage(Text: string)?)?, Bot: bool, Sso: bool, CreatedAt: DateTimeOffset, LastActivityAt: DateTimeOffset, TimeZone: string, ImageUrl: string?) +``` + + +## Инициализация + +```csharp +using Pachca.Sdk; + +// Стандартное подключение +using var client = new PachcaClient("YOUR_TOKEN"); + +// С кастомным базовым URL +using var client = new PachcaClient("YOUR_TOKEN", "https://custom-api.example.com/api/shared/v1"); +``` + +| Параметр | Тип | По умолчанию | Описание | +|----------|-----|-------------|----------| +| `token` | `string` | — | Bearer-токен для авторизации | +| `baseUrl` | `string` | `https://api.pachca.com/api/shared/v1` | Базовый URL API | + +Клиент реализует `IDisposable` — рекомендуется использовать `using` для автоматического освобождения ресурсов. + +## Все методы + +| Метод | Метод API | +|-------|----------| +| `client.Common.RequestExportAsync()` | [Экспорт сообщений](/api/common/request-export) | +| `client.Common.UploadFileAsync()` | [Загрузка файла](/api/common/direct-url) | +| `client.Common.GetUploadParamsAsync()` | [Получение подписи, ключа и других параметров](/api/common/uploads) | +| `client.Common.DownloadExportAsync()` | [Скачать архив экспорта](/api/common/get-exports) | +| `client.Common.ListPropertiesAsync()` | [Список дополнительных полей](/api/common/custom-properties) | +| `client.Profile.GetTokenInfoAsync()` | [Информация о токене](/api/profile/get-info) | +| `client.Profile.GetProfileAsync()` | [Информация о профиле](/api/profile/get) | +| `client.Profile.GetStatusAsync()` | [Текущий статус](/api/profile/get-status) | +| `client.Profile.UpdateStatusAsync()` | [Новый статус](/api/profile/update-status) | +| `client.Profile.DeleteStatusAsync()` | [Удаление статуса](/api/profile/delete-status) | +| `client.Users.CreateUserAsync()` | [Создать сотрудника](/api/users/create) | +| `client.Users.ListUsersAsync()` | [Список сотрудников](/api/users/list) | +| `client.Users.GetUserAsync()` | [Информация о сотруднике](/api/users/get) | +| `client.Users.GetUserStatusAsync()` | [Статус сотрудника](/api/users/get-status) | +| `client.Users.UpdateUserAsync()` | [Редактирование сотрудника](/api/users/update) | +| `client.Users.UpdateUserStatusAsync()` | [Новый статус сотрудника](/api/users/update-status) | +| `client.Users.DeleteUserAsync()` | [Удаление сотрудника](/api/users/delete) | +| `client.Users.DeleteUserStatusAsync()` | [Удаление статуса сотрудника](/api/users/remove-status) | +| `client.GroupTags.CreateTagAsync()` | [Новый тег](/api/group-tags/create) | +| `client.GroupTags.ListTagsAsync()` | [Список тегов сотрудников](/api/group-tags/list) | +| `client.GroupTags.GetTagAsync()` | [Информация о теге](/api/group-tags/get) | +| `client.GroupTags.GetTagUsersAsync()` | [Список сотрудников тега](/api/group-tags/list-users) | +| `client.GroupTags.UpdateTagAsync()` | [Редактирование тега](/api/group-tags/update) | +| `client.GroupTags.DeleteTagAsync()` | [Удаление тега](/api/group-tags/delete) | +| `client.Chats.CreateChatAsync()` | [Новый чат](/api/chats/create) | +| `client.Chats.ListChatsAsync()` | [Список чатов](/api/chats/list) | +| `client.Chats.GetChatAsync()` | [Информация о чате](/api/chats/get) | +| `client.Chats.UpdateChatAsync()` | [Обновление чата](/api/chats/update) | +| `client.Chats.ArchiveChatAsync()` | [Архивация чата](/api/chats/archive) | +| `client.Chats.UnarchiveChatAsync()` | [Разархивация чата](/api/chats/unarchive) | +| `client.Members.AddTagsAsync()` | [Добавление тегов](/api/members/add-group-tags) | +| `client.Members.AddMembersAsync()` | [Добавление пользователей](/api/members/add) | +| `client.Members.ListMembersAsync()` | [Список участников чата](/api/members/list) | +| `client.Members.UpdateMemberRoleAsync()` | [Редактирование роли](/api/members/update) | +| `client.Members.RemoveTagAsync()` | [Исключение тега](/api/members/remove-group-tag) | +| `client.Members.LeaveChatAsync()` | [Выход из беседы или канала](/api/members/leave) | +| `client.Members.RemoveMemberAsync()` | [Исключение пользователя](/api/members/remove) | +| `client.Threads.CreateThreadAsync()` | [Новый тред](/api/threads/add) | +| `client.Threads.GetThreadAsync()` | [Информация о треде](/api/threads/get) | +| `client.Messages.CreateMessageAsync()` | [Новое сообщение](/api/messages/create) | +| `client.Messages.PinMessageAsync()` | [Закрепление сообщения](/api/messages/pin) | +| `client.Messages.ListChatMessagesAsync()` | [Список сообщений чата](/api/messages/list) | +| `client.Messages.GetMessageAsync()` | [Информация о сообщении](/api/messages/get) | +| `client.Messages.UpdateMessageAsync()` | [Редактирование сообщения](/api/messages/update) | +| `client.Messages.DeleteMessageAsync()` | [Удаление сообщения](/api/messages/delete) | +| `client.Messages.UnpinMessageAsync()` | [Открепление сообщения](/api/messages/unpin) | +| `client.ReadMembers.ListReadMembersAsync()` | [Список прочитавших сообщение](/api/read-member/list-readers) | +| `client.Reactions.AddReactionAsync()` | [Добавление реакции](/api/reactions/add) | +| `client.Reactions.ListReactionsAsync()` | [Список реакций](/api/reactions/list) | +| `client.Reactions.RemoveReactionAsync()` | [Удаление реакции](/api/reactions/remove) | +| `client.LinkPreviews.CreateLinkPreviewsAsync()` | [Unfurl (разворачивание ссылок)](/api/link-previews/add) | +| `client.Search.SearchChatsAsync()` | [Поиск чатов](/api/search/list-chats) | +| `client.Search.SearchMessagesAsync()` | [Поиск сообщений](/api/search/list-messages) | +| `client.Search.SearchUsersAsync()` | [Поиск сотрудников](/api/search/list-users) | +| `client.Tasks.CreateTaskAsync()` | [Новое напоминание](/api/tasks/create) | +| `client.Tasks.ListTasksAsync()` | [Список напоминаний](/api/tasks/list) | +| `client.Tasks.GetTaskAsync()` | [Информация о напоминании](/api/tasks/get) | +| `client.Tasks.UpdateTaskAsync()` | [Редактирование напоминания](/api/tasks/update) | +| `client.Tasks.DeleteTaskAsync()` | [Удаление напоминания](/api/tasks/delete) | +| `client.Views.OpenViewAsync()` | [Открытие представления](/api/views/open) | +| `client.Bots.GetWebhookEventsAsync()` | [История событий](/api/bots/list-events) | +| `client.Bots.UpdateBotAsync()` | [Редактирование бота](/api/bots/update) | +| `client.Bots.DeleteWebhookEventAsync()` | [Удаление события](/api/bots/remove-event) | +| `client.Security.GetAuditEventsAsync()` | [Журнал аудита событий](/api/security/list) | + + +## Запросы + +Все методы — асинхронные и возвращают `Task`. + +**GET с параметрами:** + +```csharp +using Pachca.Sdk; + +// Список чатов +var response = await client.Chats.ListChatsAsync(SortOrder.Desc, ChatAvailability.IsMember, "2025-01-01T00:00:00.000Z", "2025-02-01T00:00:00.000Z", false, 1, "eyJpZCI6MTAsImRpciI6ImFzYyJ9"); +// → ListChatsResponse(Data: List, Meta: PaginationMeta?) +``` + + +**POST с телом запроса:** + +```csharp +using Pachca.Sdk; + +// Создание чата +var request = new ChatCreateRequest +{ + Chat = new ChatCreateRequestChat + { + Name = "🤿 aqua", + MemberIds = new List { 123 }, + GroupTagIds = new List { 123 }, + Channel = true, + @Public = false + } +}; +var response = await client.Chats.CreateChatAsync(request); +// → Chat(Id: int, Name: string, CreatedAt: DateTimeOffset, OwnerId: int, MemberIds: List, GroupTagIds: List, Channel: bool, Personal: bool, @Public: bool, LastMessageAt: DateTimeOffset, MeetRoomUrl: string) +``` + + +**Простой вызов по ID:** + +```csharp +using Pachca.Sdk; + +// Получение чата +var response = await client.Chats.GetChatAsync(334); +// → Chat(Id: int, Name: string, CreatedAt: DateTimeOffset, OwnerId: int, MemberIds: List, GroupTagIds: List, Channel: bool, Personal: bool, @Public: bool, LastMessageAt: DateTimeOffset, MeetRoomUrl: string) +``` + + +## Пагинация + +Методы, возвращающие списки, используют cursor-based пагинацию. Ответ содержит поле `Meta.Paginate.NextPage` — курсор для следующей страницы. + +### Ручная пагинация + +```csharp +var chats = new List(); +string? cursor = null; + +do +{ + var response = await client.Chats.ListChatsAsync(cursor: cursor); + chats.AddRange(response.Data); + cursor = response.Meta?.Paginate?.NextPage; +} while (cursor != null); +``` + +### Автопагинация + +Для каждого метода с пагинацией есть `*AllAsync()` вариант, который автоматически обходит все страницы и возвращает плоский список: + +```csharp +// Все чаты одним списком +var chats = await client.Chats.ListChatsAllAsync(); +Console.WriteLine($"Всего: {chats.Count}"); +``` + +Доступные методы автопагинации: + +| Метод | Возвращает | +|-------|-----------| +| `Security.GetAuditEventsAllAsync()` | `List` | +| `Bots.GetWebhookEventsAllAsync()` | `List` | +| `Chats.ListChatsAllAsync()` | `List` | +| `GroupTags.ListTagsAllAsync()` | `List` | +| `GroupTags.GetTagUsersAllAsync()` | `List` | +| `Members.ListMembersAllAsync()` | `List` | +| `Messages.ListChatMessagesAllAsync()` | `List` | +| `Reactions.ListReactionsAllAsync()` | `List` | +| `Search.SearchChatsAllAsync()` | `List` | +| `Search.SearchMessagesAllAsync()` | `List` | +| `Search.SearchUsersAllAsync()` | `List` | +| `Tasks.ListTasksAllAsync()` | `List` | +| `Users.ListUsersAllAsync()` | `List` | + +## Обработка ошибок + +SDK выбрасывает два типа исключений: + +### ApiError + +Возникает при ошибках `400`, `403`, `404`, `409`, `410`, `422`: + +```csharp +using Pachca.Sdk; + +try +{ + await client.Chats.CreateChatAsync(request); +} +catch (ApiError e) +{ + foreach (var err in e.Errors) + { + Console.WriteLine($"{err.Key}: {err.Message}"); // "name", "не может быть пустым" + Console.WriteLine(err.Code); // "blank" + } +} +``` + +Поля `ApiErrorItem`: + +| Поле | Тип | Описание | +|------|-----|----------| +| `Key` | `string` | Поле, вызвавшее ошибку | +| `Value` | `string?` | Переданное значение | +| `Message` | `string` | Текст ошибки | +| `Code` | `ValidationErrorCode` | Код валидации (`Blank`, `Invalid`, `Taken`, ...) | +| `Payload` | `string?` | Дополнительные данные | + +### OAuthError + +Возникает при ошибке авторизации (`401`): + +```csharp +using Pachca.Sdk; + +try +{ + await client.Profile.GetProfileAsync(); +} +catch (OAuthError e) +{ + Console.WriteLine(e.ErrorDescription); // "Token not found" +} +``` + +## Повторные запросы + +SDK автоматически повторяет запрос при получении `429 Too Many Requests` и ошибок сервера `5xx`: + +- До **3 повторов** на каждый запрос +- Если сервер вернул заголовок `Retry-After` — ждёт указанное время +- Иначе — экспоненциальный backoff: 1 сек, 2 сек, 4 сек +- Ошибки клиента (4xx, кроме 429) возвращаются сразу без повторов + +## Отмена запросов + +Все асинхронные методы поддерживают `CancellationToken`: + +```csharp +using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); +var users = await client.Users.ListUsersAsync(cancellationToken: cts.Token); +``` + +## Загрузка файлов + +Загрузка файла — трёхшаговый процесс: + +```csharp +// 1. Получить параметры загрузки +var uploadParams = await client.Common.GetUploadParamsAsync(); + +// 2. Загрузить файл на S3 +using var fileStream = File.OpenRead("photo.png"); +await client.Common.UploadFileAsync(uploadParams, fileStream, "photo.png"); + +// 3. Прикрепить к сообщению (используя key из uploadParams) +await client.Messages.CreateMessageAsync(new MessageCreateRequest +{ + Message = new MessageCreateRequestMessage + { + EntityId = chatId, + Content = "Фото", + Files = new List { new() { Key = uploadParams.Key, Name = "photo.png" } } + } +}); +``` + +## Сериализация + +SDK автоматически конвертирует имена полей между PascalCase (C#) и snake_case (API): + +```csharp +// Вы пишете: +new ChatCreateRequestChat { MemberIds = new List { 123 }, GroupTagIds = new List { 456 } } + +// SDK отправляет: +{ "member_ids": [123], "group_tag_ids": [456] } + +// API возвращает: +{ "last_message_at": "2025-01-01T00:00:00Z" } + +// Вы получаете: +chat.LastMessageAt // DateTimeOffset +``` + +## Типы + +Все типы доступны из пространства имён `Pachca.Sdk`: + +```csharp +using Pachca.Sdk; + +// Модели +Chat chat; +Message message; +User user; +Task task; +GroupTag groupTag; + +// Запросы +ChatCreateRequest chatRequest; +MessageCreateRequest messageRequest; +TaskCreateRequest taskRequest; + +// Перечисления +AuditEventKey eventKey; +ChatAvailability availability; +ChatMemberRole role; +TaskStatus status; +ValidationErrorCode errorCode; + +// Ошибки +ApiError apiError; +OAuthError oauthError; +``` + +## Примеры + +```csharp +using Pachca.Sdk; + +using var client = new PachcaClient("YOUR_TOKEN"); + +// Отправка сообщения +var request = new MessageCreateRequest +{ + Message = new MessageCreateRequestMessage + { + EntityType = MessageEntityType.Discussion, + EntityId = 334, + Content = "Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье)", + Files = new List { new MessageCreateRequestFile + { + Key = "attaches/files/93746/e354fd79-4f3e-4b5a-9c8d-1a2b3c4d5e6f/logo.png", + Name = "logo.png", + FileType = FileType.Image, + Size = 12345, + Width = 800, + Height = 600 + } }, + Buttons = new List> { new List