Barazo default frontend barazo.forum
at main 1576 lines 42 kB view raw
1/** 2 * Type-safe API client for barazo-api. 3 * Server-side: uses fetch directly with the internal API URL. 4 * Client-side: uses fetch with the public API URL. 5 */ 6 7import type { 8 AgeDeclarationResponse, 9 AuthorProfile, 10 AuthSession, 11 AuthUser, 12 CategoriesResponse, 13 CategoryTreeNode, 14 CategoryWithTopicCount, 15 CommunityPreferencesResponse, 16 CommunitySettings, 17 CommunityStats, 18 CommunityPreferenceOverride, 19 CreatePageInput, 20 CreateTopicInput, 21 CreateTopicResponse, 22 InitializeCommunityInput, 23 InitializeResponse, 24 Page, 25 PagesResponse, 26 PublicSettings, 27 SetupStatus, 28 Topic, 29 TopicsResponse, 30 UpdateCommunityPreferenceInput, 31 UpdatePageInput, 32 UpdatePreferencesInput, 33 UpdateTopicInput, 34 UserPreferences, 35 Reply, 36 RepliesResponse, 37 CreateReplyInput, 38 CreateReplyResponse, 39 UpdateReplyInput, 40 SearchResponse, 41 NotificationsResponse, 42 PaginationParams, 43 ModerationReportsResponse, 44 FirstPostQueueResponse, 45 ModerationLogResponse, 46 ModerationThresholds, 47 ReportedUsersResponse, 48 AdminUsersResponse, 49 MaturityRating, 50 Plugin, 51 PluginsResponse, 52 RegistrySearchResponse, 53 OnboardingField, 54 AdminOnboardingFieldsResponse, 55 CreateOnboardingFieldInput, 56 UpdateOnboardingFieldInput, 57 OnboardingStatus, 58 SubmitOnboardingInput, 59 MyReport, 60 MyReportsResponse, 61 UserProfile, 62 CommunityProfile, 63 UpdateCommunityProfileInput, 64 UploadResponse, 65 SybilClustersResponse, 66 SybilClusterDetail, 67 SybilCluster, 68 TrustSeedsResponse, 69 TrustSeed, 70 CreateTrustSeedInput, 71 PdsTrustFactorsResponse, 72 PdsTrustFactor, 73 TrustGraphStatus, 74 BehavioralFlagsResponse, 75 BehavioralFlag, 76 ReactionsResponse, 77 CommunityRule, 78 CommunityRulesResponse, 79 CreateRuleInput, 80 UpdateRuleInput, 81 ReorderRulesInput, 82} from './types' 83 84/** Client: relative URLs (empty string). Server: internal Docker network URL. */ 85const API_URL = 86 typeof window === 'undefined' ? (process.env.API_INTERNAL_URL ?? 'http://localhost:3000') : '' 87 88interface FetchOptions { 89 headers?: Record<string, string> 90 signal?: AbortSignal 91 method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' 92 body?: unknown 93} 94 95class ApiError extends Error { 96 /** The parsed error code from the API response body (e.g., "Onboarding required") */ 97 public readonly errorCode: string | undefined 98 99 constructor( 100 public readonly status: number, 101 message: string, 102 errorCode?: string 103 ) { 104 super(message) 105 this.name = 'ApiError' 106 this.errorCode = errorCode 107 } 108} 109 110async function throwApiError(response: Response): Promise<never> { 111 const body = await response.text().catch(() => 'Unknown error') 112 let message = `Request failed (${response.status})` 113 let errorCode: string | undefined 114 115 try { 116 const parsed = JSON.parse(body) as { error?: string } 117 if (parsed.error) { 118 message = parsed.error 119 errorCode = parsed.error 120 } 121 } catch { 122 if (body && body !== 'Unknown error') { 123 message = body 124 } 125 } 126 127 throw new ApiError(response.status, message, errorCode) 128} 129 130async function apiFetch<T>(path: string, options: FetchOptions = {}): Promise<T> { 131 const url = `${API_URL}${path}` 132 const hasBody = options.body !== undefined 133 const response = await fetch(url, { 134 method: options.method ?? 'GET', 135 headers: { 136 ...(hasBody ? { 'Content-Type': 'application/json' } : {}), 137 ...options.headers, 138 }, 139 signal: options.signal, 140 ...(hasBody ? { body: JSON.stringify(options.body) } : {}), 141 }) 142 143 if (!response.ok) { 144 await throwApiError(response) 145 } 146 147 const contentLength = response.headers.get('content-length') 148 if (response.status === 204 || contentLength === '0') { 149 return undefined as T 150 } 151 152 const text = await response.text() 153 if (!text) { 154 return undefined as T 155 } 156 157 return JSON.parse(text) as T 158} 159 160function buildQuery(params: Record<string, string | number | undefined>): string { 161 const entries = Object.entries(params).filter( 162 (entry): entry is [string, string | number] => entry[1] !== undefined 163 ) 164 if (entries.length === 0) return '' 165 return '?' + new URLSearchParams(entries.map(([k, v]) => [k, String(v)])).toString() 166} 167 168// --- Auth endpoints --- 169 170export async function initiateLogin(handle: string): Promise<{ url: string }> { 171 const query = buildQuery({ handle }) 172 const result = await apiFetch<{ url: string }>(`/api/auth/login${query}`) 173 if (!result?.url) { 174 throw new ApiError(502, 'Login endpoint did not return a redirect URL') 175 } 176 return result 177} 178 179export async function initiateCrossPostAuth(token: string): Promise<{ url: string }> { 180 const result = await apiFetch<{ url: string }>('/api/auth/crosspost-authorize', { 181 headers: { Authorization: `Bearer ${token}` }, 182 }) 183 if (!result?.url) { 184 throw new ApiError(502, 'Cross-post auth endpoint did not return a redirect URL') 185 } 186 return result 187} 188 189export function handleCallback(code: string, state: string): Promise<AuthSession> { 190 const query = buildQuery({ code, state }) 191 return apiFetch<AuthSession>(`/api/auth/callback${query}`) 192} 193 194export async function refreshSession(): Promise<AuthSession> { 195 const url = `${API_URL}/api/auth/refresh` 196 const response = await fetch(url, { 197 method: 'POST', 198 credentials: 'include', 199 }) 200 201 if (!response.ok) { 202 await throwApiError(response) 203 } 204 205 return response.json() as Promise<AuthSession> 206} 207 208export async function logout(accessToken: string): Promise<void> { 209 const url = `${API_URL}/api/auth/session` 210 const response = await fetch(url, { 211 method: 'DELETE', 212 headers: { Authorization: `Bearer ${accessToken}` }, 213 credentials: 'include', 214 }) 215 216 if (!response.ok && response.status !== 204) { 217 await throwApiError(response) 218 } 219} 220 221export function getCurrentUser(accessToken: string): Promise<AuthUser> { 222 return apiFetch<AuthUser>('/api/auth/me', { 223 headers: { Authorization: `Bearer ${accessToken}` }, 224 }) 225} 226 227// --- Category endpoints --- 228 229export function getCategories(options?: FetchOptions): Promise<CategoriesResponse> { 230 return apiFetch<CategoriesResponse>('/api/categories', options) 231} 232 233export function getCategoryBySlug( 234 slug: string, 235 options?: FetchOptions 236): Promise<CategoryWithTopicCount> { 237 return apiFetch<CategoryWithTopicCount>(`/api/categories/${encodeURIComponent(slug)}`, options) 238} 239 240// --- Topic endpoints --- 241 242export interface GetTopicsParams extends PaginationParams { 243 category?: string 244 sort?: 'latest' | 'popular' 245} 246 247export function getTopics( 248 params: GetTopicsParams = {}, 249 options?: FetchOptions 250): Promise<TopicsResponse> { 251 const query = buildQuery({ 252 limit: params.limit, 253 cursor: params.cursor, 254 category: params.category, 255 sort: params.sort, 256 }) 257 return apiFetch<TopicsResponse>(`/api/topics${query}`, options) 258} 259 260/** @deprecated Use getTopicByAuthorAndRkey for author-scoped lookup */ 261export function getTopicByRkey(rkey: string, options?: FetchOptions): Promise<Topic> { 262 return apiFetch<Topic>(`/api/topics/by-rkey/${encodeURIComponent(rkey)}`, options) 263} 264 265export function getTopicByAuthorAndRkey( 266 handle: string, 267 rkey: string, 268 options?: FetchOptions 269): Promise<Topic> { 270 return apiFetch<Topic>( 271 `/api/topics/by-author-rkey/${encodeURIComponent(handle)}/${encodeURIComponent(rkey)}`, 272 options 273 ) 274} 275 276export function createTopic( 277 input: CreateTopicInput, 278 accessToken: string, 279 options?: FetchOptions 280): Promise<CreateTopicResponse> { 281 return apiFetch<CreateTopicResponse>('/api/topics', { 282 ...options, 283 method: 'POST', 284 headers: { 285 ...options?.headers, 286 Authorization: `Bearer ${accessToken}`, 287 }, 288 body: input, 289 }) 290} 291 292export function updateTopic( 293 rkey: string, 294 input: UpdateTopicInput, 295 accessToken: string, 296 options?: FetchOptions 297): Promise<Topic> { 298 return apiFetch<Topic>(`/api/topics/${encodeURIComponent(rkey)}`, { 299 ...options, 300 method: 'PUT', 301 headers: { 302 ...options?.headers, 303 Authorization: `Bearer ${accessToken}`, 304 }, 305 body: input, 306 }) 307} 308 309// --- Reply endpoints --- 310 311export function getReplies( 312 topicUri: string, 313 params: PaginationParams & { depth?: number } = {}, 314 options?: FetchOptions 315): Promise<RepliesResponse> { 316 const query = buildQuery({ 317 limit: params.limit, 318 cursor: params.cursor, 319 depth: params.depth, 320 }) 321 return apiFetch<RepliesResponse>( 322 `/api/topics/${encodeURIComponent(topicUri)}/replies${query}`, 323 options 324 ) 325} 326 327export function createReply( 328 topicUri: string, 329 input: CreateReplyInput, 330 accessToken: string, 331 options?: FetchOptions 332): Promise<CreateReplyResponse> { 333 return apiFetch<CreateReplyResponse>(`/api/topics/${encodeURIComponent(topicUri)}/replies`, { 334 ...options, 335 method: 'POST', 336 headers: { 337 ...options?.headers, 338 Authorization: `Bearer ${accessToken}`, 339 }, 340 body: input, 341 }) 342} 343 344export function updateReply( 345 uri: string, 346 input: UpdateReplyInput, 347 accessToken: string, 348 options?: FetchOptions 349): Promise<Reply> { 350 return apiFetch<Reply>(`/api/replies/${encodeURIComponent(uri)}`, { 351 ...options, 352 method: 'PUT', 353 headers: { 354 ...options?.headers, 355 Authorization: `Bearer ${accessToken}`, 356 }, 357 body: input, 358 }) 359} 360 361export function getReplyByAuthorAndRkey( 362 handle: string, 363 rkey: string, 364 options?: FetchOptions 365): Promise<Reply> { 366 return apiFetch<Reply>( 367 `/api/replies/by-author-rkey/${encodeURIComponent(handle)}/${encodeURIComponent(rkey)}`, 368 options 369 ) 370} 371 372export function deleteReply( 373 uri: string, 374 accessToken: string, 375 options?: FetchOptions 376): Promise<void> { 377 return apiFetch<void>(`/api/replies/${encodeURIComponent(uri)}`, { 378 ...options, 379 method: 'DELETE', 380 headers: { 381 ...options?.headers, 382 Authorization: `Bearer ${accessToken}`, 383 }, 384 }) 385} 386 387// --- Search endpoints --- 388 389export interface SearchParams extends PaginationParams { 390 q: string 391} 392 393export function searchContent( 394 params: SearchParams, 395 options?: FetchOptions 396): Promise<SearchResponse> { 397 const query = buildQuery({ 398 q: params.q, 399 limit: params.limit, 400 cursor: params.cursor, 401 }) 402 return apiFetch<SearchResponse>(`/api/search${query}`, options) 403} 404 405// --- Notification endpoints --- 406 407export function getNotifications( 408 accessToken: string, 409 params: PaginationParams = {}, 410 options?: FetchOptions 411): Promise<NotificationsResponse> { 412 const query = buildQuery({ 413 limit: params.limit, 414 cursor: params.cursor, 415 }) 416 return apiFetch<NotificationsResponse>(`/api/notifications${query}`, { 417 ...options, 418 headers: { 419 ...options?.headers, 420 Authorization: `Bearer ${accessToken}`, 421 }, 422 }) 423} 424 425export function markNotificationsRead( 426 accessToken: string, 427 ids: string[], 428 options?: FetchOptions 429): Promise<void> { 430 return apiFetch<void>('/api/notifications/read', { 431 ...options, 432 method: 'PUT', 433 headers: { 434 ...options?.headers, 435 Authorization: `Bearer ${accessToken}`, 436 }, 437 body: { ids }, 438 }) 439} 440 441// --- Community endpoints --- 442 443export function getCommunitySettings( 444 accessToken: string, 445 options?: FetchOptions 446): Promise<CommunitySettings> { 447 return apiFetch<CommunitySettings>('/api/admin/settings', { 448 ...options, 449 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 450 }) 451} 452 453export function getPublicSettings(options?: FetchOptions): Promise<PublicSettings> { 454 return apiFetch<PublicSettings>('/api/settings/public', options) 455} 456 457export function getCommunityStats( 458 accessToken: string, 459 options?: FetchOptions 460): Promise<CommunityStats> { 461 return apiFetch<CommunityStats>('/api/admin/stats', { 462 ...options, 463 headers: { 464 ...options?.headers, 465 Authorization: `Bearer ${accessToken}`, 466 }, 467 }) 468} 469 470// --- Admin category endpoints --- 471 472export function createCategory( 473 input: { 474 name: string 475 slug: string 476 description: string | null 477 parentId: string | null 478 sortOrder: number 479 maturityRating: MaturityRating 480 }, 481 accessToken: string, 482 options?: FetchOptions 483): Promise<CategoryTreeNode> { 484 return apiFetch<CategoryTreeNode>('/api/admin/categories', { 485 ...options, 486 method: 'POST', 487 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 488 body: input, 489 }) 490} 491 492export function updateCategory( 493 id: string, 494 input: Partial<{ 495 name: string 496 slug: string 497 description: string | null 498 parentId: string | null 499 sortOrder: number 500 maturityRating: MaturityRating 501 }>, 502 accessToken: string, 503 options?: FetchOptions 504): Promise<CategoryTreeNode> { 505 return apiFetch<CategoryTreeNode>(`/api/admin/categories/${encodeURIComponent(id)}`, { 506 ...options, 507 method: 'PUT', 508 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 509 body: input, 510 }) 511} 512 513export function deleteCategory( 514 id: string, 515 accessToken: string, 516 options?: FetchOptions 517): Promise<void> { 518 return apiFetch<void>(`/api/admin/categories/${encodeURIComponent(id)}`, { 519 ...options, 520 method: 'DELETE', 521 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 522 }) 523} 524 525// --- Moderation endpoints --- 526 527export function getModerationReports( 528 accessToken: string, 529 params: PaginationParams = {}, 530 options?: FetchOptions 531): Promise<ModerationReportsResponse> { 532 const query = buildQuery({ limit: params.limit, cursor: params.cursor }) 533 return apiFetch<ModerationReportsResponse>(`/api/moderation/reports${query}`, { 534 ...options, 535 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 536 }) 537} 538 539export function resolveReport( 540 id: string, 541 resolution: string, 542 accessToken: string, 543 options?: FetchOptions 544): Promise<void> { 545 return apiFetch<void>(`/api/moderation/reports/${encodeURIComponent(id)}`, { 546 ...options, 547 method: 'PUT', 548 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 549 body: { resolution }, 550 }) 551} 552 553export function getFirstPostQueue( 554 accessToken: string, 555 params: PaginationParams = {}, 556 options?: FetchOptions 557): Promise<FirstPostQueueResponse> { 558 const query = buildQuery({ limit: params.limit, cursor: params.cursor }) 559 return apiFetch<FirstPostQueueResponse>(`/api/moderation/queue${query}`, { 560 ...options, 561 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 562 }) 563} 564 565export function resolveFirstPost( 566 id: string, 567 action: 'approved' | 'rejected', 568 accessToken: string, 569 options?: FetchOptions 570): Promise<void> { 571 return apiFetch<void>(`/api/moderation/queue/${encodeURIComponent(id)}`, { 572 ...options, 573 method: 'PUT', 574 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 575 body: { action }, 576 }) 577} 578 579export function getModerationLog( 580 accessToken: string, 581 params: PaginationParams = {}, 582 options?: FetchOptions 583): Promise<ModerationLogResponse> { 584 const query = buildQuery({ limit: params.limit, cursor: params.cursor }) 585 return apiFetch<ModerationLogResponse>(`/api/moderation/log${query}`, { 586 ...options, 587 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 588 }) 589} 590 591export function getModerationThresholds( 592 accessToken: string, 593 options?: FetchOptions 594): Promise<ModerationThresholds> { 595 return apiFetch<ModerationThresholds>('/api/admin/moderation/thresholds', { 596 ...options, 597 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 598 }) 599} 600 601export function updateModerationThresholds( 602 thresholds: Partial<ModerationThresholds>, 603 accessToken: string, 604 options?: FetchOptions 605): Promise<ModerationThresholds> { 606 return apiFetch<ModerationThresholds>('/api/admin/moderation/thresholds', { 607 ...options, 608 method: 'PUT', 609 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 610 body: thresholds, 611 }) 612} 613 614export function getReportedUsers( 615 accessToken: string, 616 options?: FetchOptions 617): Promise<ReportedUsersResponse> { 618 return apiFetch<ReportedUsersResponse>('/api/admin/reports/users', { 619 ...options, 620 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 621 }) 622} 623 624// --- Admin settings endpoints --- 625 626export function updateCommunitySettings( 627 settings: Partial<CommunitySettings>, 628 accessToken: string, 629 options?: FetchOptions 630): Promise<CommunitySettings> { 631 return apiFetch<CommunitySettings>('/api/admin/settings', { 632 ...options, 633 method: 'PUT', 634 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 635 body: settings, 636 }) 637} 638 639// --- Admin user endpoints --- 640 641export function getAdminUsers( 642 accessToken: string, 643 params: PaginationParams = {}, 644 options?: FetchOptions 645): Promise<AdminUsersResponse> { 646 const query = buildQuery({ limit: params.limit, cursor: params.cursor }) 647 return apiFetch<AdminUsersResponse>(`/api/admin/users${query}`, { 648 ...options, 649 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 650 }) 651} 652 653export function banUser( 654 did: string, 655 reason: string, 656 accessToken: string, 657 options?: FetchOptions 658): Promise<void> { 659 return apiFetch<void>('/api/moderation/ban', { 660 ...options, 661 method: 'POST', 662 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 663 body: { did, action: 'ban', reason }, 664 }) 665} 666 667export function unbanUser(did: string, accessToken: string, options?: FetchOptions): Promise<void> { 668 return apiFetch<void>('/api/moderation/ban', { 669 ...options, 670 method: 'POST', 671 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 672 body: { did, action: 'unban' }, 673 }) 674} 675 676// --- Plugin endpoints --- 677 678export function getPlugins(accessToken: string, options?: FetchOptions): Promise<PluginsResponse> { 679 return apiFetch<PluginsResponse>('/api/plugins', { 680 ...options, 681 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 682 }) 683} 684 685export function togglePlugin( 686 id: string, 687 enabled: boolean, 688 accessToken: string, 689 options?: FetchOptions 690): Promise<void> { 691 return apiFetch<void>( 692 `/api/plugins/${encodeURIComponent(id)}/${enabled ? 'enable' : 'disable'}`, 693 { 694 ...options, 695 method: 'PATCH', 696 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 697 } 698 ) 699} 700 701export function updatePluginSettings( 702 id: string, 703 settings: Record<string, boolean | string | number>, 704 accessToken: string, 705 options?: FetchOptions 706): Promise<void> { 707 return apiFetch<void>(`/api/plugins/${encodeURIComponent(id)}/settings`, { 708 ...options, 709 method: 'PATCH', 710 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 711 body: settings, 712 }) 713} 714 715export function uninstallPlugin( 716 id: string, 717 accessToken: string, 718 options?: FetchOptions 719): Promise<void> { 720 return apiFetch<void>(`/api/plugins/${encodeURIComponent(id)}`, { 721 ...options, 722 method: 'DELETE', 723 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 724 }) 725} 726 727export function installPlugin( 728 packageName: string, 729 version: string | undefined, 730 accessToken: string, 731 options?: FetchOptions 732): Promise<{ plugin: Plugin }> { 733 return apiFetch<{ plugin: Plugin }>('/api/plugins/install', { 734 ...options, 735 method: 'POST', 736 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 737 body: { packageName, ...(version ? { version } : {}) }, 738 }) 739} 740 741export function searchPluginRegistry( 742 params: { q?: string; category?: string; source?: string }, 743 options?: FetchOptions 744): Promise<RegistrySearchResponse> { 745 const searchParams = new URLSearchParams() 746 if (params.q) searchParams.set('q', params.q) 747 if (params.category) searchParams.set('category', params.category) 748 if (params.source) searchParams.set('source', params.source) 749 const query = searchParams.toString() 750 return apiFetch<RegistrySearchResponse>( 751 `/api/plugins/registry/search${query ? `?${query}` : ''}`, 752 { 753 ...options, 754 } 755 ) 756} 757 758export function getFeaturedPlugins(options?: FetchOptions): Promise<RegistrySearchResponse> { 759 return apiFetch<RegistrySearchResponse>('/api/plugins/registry/featured', { 760 ...options, 761 }) 762} 763 764// --- User Preference endpoints --- 765 766export function getPreferences( 767 accessToken: string, 768 options?: FetchOptions 769): Promise<UserPreferences> { 770 return apiFetch<UserPreferences>('/api/users/me/preferences', { 771 ...options, 772 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 773 }) 774} 775 776export function updatePreferences( 777 input: UpdatePreferencesInput, 778 accessToken: string, 779 options?: FetchOptions 780): Promise<UserPreferences> { 781 return apiFetch<UserPreferences>('/api/users/me/preferences', { 782 ...options, 783 method: 'PUT', 784 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 785 body: input, 786 }) 787} 788 789export function declareAge( 790 declaredAge: number, 791 accessToken: string, 792 options?: FetchOptions 793): Promise<AgeDeclarationResponse> { 794 return apiFetch<AgeDeclarationResponse>('/api/users/me/age-declaration', { 795 ...options, 796 method: 'POST', 797 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 798 body: { declaredAge }, 799 }) 800} 801 802export function resolveHandles( 803 handles: string[], 804 accessToken: string, 805 options?: FetchOptions 806): Promise<{ users: AuthorProfile[] }> { 807 const qs = encodeURIComponent(handles.join(',')) 808 return apiFetch<{ users: AuthorProfile[] }>(`/api/users/resolve-handles?handles=${qs}`, { 809 ...options, 810 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 811 }) 812} 813 814// --- Per-Community Preference endpoints --- 815 816export function getCommunityPreferences( 817 accessToken: string, 818 options?: FetchOptions 819): Promise<CommunityPreferencesResponse> { 820 return apiFetch<CommunityPreferencesResponse>('/api/users/me/preferences/communities', { 821 ...options, 822 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 823 }) 824} 825 826export function updateCommunityPreference( 827 communityDid: string, 828 input: UpdateCommunityPreferenceInput, 829 accessToken: string, 830 options?: FetchOptions 831): Promise<CommunityPreferenceOverride> { 832 return apiFetch<CommunityPreferenceOverride>( 833 `/api/users/me/preferences/communities/${encodeURIComponent(communityDid)}`, 834 { 835 ...options, 836 method: 'PUT', 837 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 838 body: input, 839 } 840 ) 841} 842 843// --- Block/Mute endpoints --- 844 845export function blockUser( 846 did: string, 847 accessToken: string, 848 options?: FetchOptions 849): Promise<{ success: boolean }> { 850 return apiFetch<{ success: boolean }>(`/api/users/me/block/${encodeURIComponent(did)}`, { 851 ...options, 852 method: 'POST', 853 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 854 }) 855} 856 857export function unblockUser( 858 did: string, 859 accessToken: string, 860 options?: FetchOptions 861): Promise<{ success: boolean }> { 862 return apiFetch<{ success: boolean }>(`/api/users/me/block/${encodeURIComponent(did)}`, { 863 ...options, 864 method: 'DELETE', 865 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 866 }) 867} 868 869export function muteUser( 870 did: string, 871 accessToken: string, 872 options?: FetchOptions 873): Promise<{ success: boolean }> { 874 return apiFetch<{ success: boolean }>(`/api/users/me/mute/${encodeURIComponent(did)}`, { 875 ...options, 876 method: 'POST', 877 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 878 }) 879} 880 881export function unmuteUser( 882 did: string, 883 accessToken: string, 884 options?: FetchOptions 885): Promise<{ success: boolean }> { 886 return apiFetch<{ success: boolean }>(`/api/users/me/mute/${encodeURIComponent(did)}`, { 887 ...options, 888 method: 'DELETE', 889 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 890 }) 891} 892 893// --- Admin onboarding field endpoints --- 894 895export function getOnboardingFields( 896 accessToken: string, 897 options?: FetchOptions 898): Promise<AdminOnboardingFieldsResponse> { 899 return apiFetch<AdminOnboardingFieldsResponse>('/api/admin/onboarding-fields', { 900 ...options, 901 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 902 }) 903} 904 905export function createOnboardingField( 906 input: CreateOnboardingFieldInput, 907 accessToken: string, 908 options?: FetchOptions 909): Promise<OnboardingField> { 910 return apiFetch<OnboardingField>('/api/admin/onboarding-fields', { 911 ...options, 912 method: 'POST', 913 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 914 body: input, 915 }) 916} 917 918export function updateOnboardingField( 919 id: string, 920 input: UpdateOnboardingFieldInput, 921 accessToken: string, 922 options?: FetchOptions 923): Promise<OnboardingField> { 924 return apiFetch<OnboardingField>(`/api/admin/onboarding-fields/${encodeURIComponent(id)}`, { 925 ...options, 926 method: 'PUT', 927 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 928 body: input, 929 }) 930} 931 932export function deleteOnboardingField( 933 id: string, 934 accessToken: string, 935 options?: FetchOptions 936): Promise<void> { 937 return apiFetch<void>(`/api/admin/onboarding-fields/${encodeURIComponent(id)}`, { 938 ...options, 939 method: 'DELETE', 940 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 941 }) 942} 943 944export function reorderOnboardingFields( 945 fields: Array<{ id: string; sortOrder: number }>, 946 accessToken: string, 947 options?: FetchOptions 948): Promise<{ success: boolean }> { 949 return apiFetch<{ success: boolean }>('/api/admin/onboarding-fields/reorder', { 950 ...options, 951 method: 'PUT', 952 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 953 body: fields, 954 }) 955} 956 957// --- User onboarding endpoints --- 958 959export function getOnboardingStatus( 960 accessToken: string, 961 options?: FetchOptions 962): Promise<OnboardingStatus> { 963 return apiFetch<OnboardingStatus>('/api/onboarding/status', { 964 ...options, 965 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 966 }) 967} 968 969export function submitOnboarding( 970 input: SubmitOnboardingInput, 971 accessToken: string, 972 options?: FetchOptions 973): Promise<{ success: boolean }> { 974 return apiFetch<{ success: boolean }>('/api/onboarding/submit', { 975 ...options, 976 method: 'POST', 977 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 978 body: input.responses, 979 }) 980} 981 982// --- My Reports + Appeals endpoints --- 983 984export function getMyReports( 985 accessToken: string, 986 params: PaginationParams = {}, 987 options?: FetchOptions 988): Promise<MyReportsResponse> { 989 const query = buildQuery({ limit: params.limit, cursor: params.cursor }) 990 return apiFetch<MyReportsResponse>(`/api/moderation/my-reports${query}`, { 991 ...options, 992 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 993 }) 994} 995 996export function submitAppeal( 997 reportId: number, 998 reason: string, 999 accessToken: string, 1000 options?: FetchOptions 1001): Promise<MyReport> { 1002 return apiFetch<MyReport>( 1003 `/api/moderation/reports/${encodeURIComponent(String(reportId))}/appeal`, 1004 { 1005 ...options, 1006 method: 'POST', 1007 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1008 body: { reason }, 1009 } 1010 ) 1011} 1012 1013// --- User Profile endpoints --- 1014 1015export function getUserProfile( 1016 handle: string, 1017 communityDid?: string, 1018 options?: FetchOptions 1019): Promise<UserProfile> { 1020 const query = buildQuery({ communityDid }) 1021 return apiFetch<UserProfile>(`/api/users/${encodeURIComponent(handle)}${query}`, options) 1022} 1023 1024// --- Community Profile endpoints --- 1025 1026export function getCommunityProfile( 1027 communityDid: string, 1028 accessToken: string, 1029 options?: FetchOptions 1030): Promise<CommunityProfile> { 1031 return apiFetch<CommunityProfile>( 1032 `/api/communities/${encodeURIComponent(communityDid)}/profile`, 1033 { ...options, headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` } } 1034 ) 1035} 1036 1037export function updateCommunityProfile( 1038 communityDid: string, 1039 input: UpdateCommunityProfileInput, 1040 accessToken: string, 1041 options?: FetchOptions 1042): Promise<{ success: boolean }> { 1043 return apiFetch<{ success: boolean }>( 1044 `/api/communities/${encodeURIComponent(communityDid)}/profile`, 1045 { 1046 ...options, 1047 method: 'PUT', 1048 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1049 body: input, 1050 } 1051 ) 1052} 1053 1054export function resetCommunityProfile( 1055 communityDid: string, 1056 accessToken: string, 1057 options?: FetchOptions 1058): Promise<void> { 1059 return apiFetch<void>(`/api/communities/${encodeURIComponent(communityDid)}/profile`, { 1060 ...options, 1061 method: 'DELETE', 1062 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1063 }) 1064} 1065 1066// --- Upload endpoints (use FormData, not JSON) --- 1067 1068export async function uploadCommunityAvatar( 1069 communityDid: string, 1070 file: File, 1071 accessToken: string 1072): Promise<UploadResponse> { 1073 const form = new FormData() 1074 form.append('file', file) 1075 const url = `${API_URL}/api/communities/${encodeURIComponent(communityDid)}/profile/avatar` 1076 const response = await fetch(url, { 1077 method: 'POST', 1078 headers: { Authorization: `Bearer ${accessToken}` }, 1079 body: form, 1080 }) 1081 if (!response.ok) { 1082 await throwApiError(response) 1083 } 1084 return response.json() as Promise<UploadResponse> 1085} 1086 1087export async function uploadCommunityBanner( 1088 communityDid: string, 1089 file: File, 1090 accessToken: string 1091): Promise<UploadResponse> { 1092 const form = new FormData() 1093 form.append('file', file) 1094 const url = `${API_URL}/api/communities/${encodeURIComponent(communityDid)}/profile/banner` 1095 const response = await fetch(url, { 1096 method: 'POST', 1097 headers: { Authorization: `Bearer ${accessToken}` }, 1098 body: form, 1099 }) 1100 if (!response.ok) { 1101 await throwApiError(response) 1102 } 1103 return response.json() as Promise<UploadResponse> 1104} 1105 1106// --- Admin design upload endpoints (use FormData, not JSON) --- 1107 1108export async function uploadCommunityLogo( 1109 file: File, 1110 accessToken: string 1111): Promise<UploadResponse> { 1112 const form = new FormData() 1113 form.append('file', file) 1114 const url = `${API_URL}/api/admin/design/logo` 1115 const response = await fetch(url, { 1116 method: 'POST', 1117 headers: { Authorization: `Bearer ${accessToken}` }, 1118 body: form, 1119 }) 1120 if (!response.ok) { 1121 await throwApiError(response) 1122 } 1123 return response.json() as Promise<UploadResponse> 1124} 1125 1126export async function uploadHeaderLogo(file: File, accessToken: string): Promise<UploadResponse> { 1127 const form = new FormData() 1128 form.append('file', file) 1129 const url = `${API_URL}/api/admin/design/header-logo` 1130 const response = await fetch(url, { 1131 method: 'POST', 1132 headers: { Authorization: `Bearer ${accessToken}` }, 1133 body: form, 1134 }) 1135 if (!response.ok) { 1136 await throwApiError(response) 1137 } 1138 return response.json() as Promise<UploadResponse> 1139} 1140 1141export async function uploadCommunityFavicon( 1142 file: File, 1143 accessToken: string 1144): Promise<UploadResponse> { 1145 const form = new FormData() 1146 form.append('file', file) 1147 const url = `${API_URL}/api/admin/design/favicon` 1148 const response = await fetch(url, { 1149 method: 'POST', 1150 headers: { Authorization: `Bearer ${accessToken}` }, 1151 body: form, 1152 }) 1153 if (!response.ok) { 1154 await throwApiError(response) 1155 } 1156 return response.json() as Promise<UploadResponse> 1157} 1158 1159// --- Sybil Detection endpoints --- 1160 1161export function getSybilClusters( 1162 accessToken: string, 1163 params: { status?: string } = {}, 1164 options?: FetchOptions 1165): Promise<SybilClustersResponse> { 1166 const query = buildQuery({ status: params.status }) 1167 return apiFetch<SybilClustersResponse>(`/api/admin/sybil-clusters${query}`, { 1168 ...options, 1169 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1170 }) 1171} 1172 1173export function getSybilClusterDetail( 1174 id: number, 1175 accessToken: string, 1176 options?: FetchOptions 1177): Promise<SybilClusterDetail> { 1178 return apiFetch<SybilClusterDetail>(`/api/admin/sybil-clusters/${id}`, { 1179 ...options, 1180 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1181 }) 1182} 1183 1184export function updateSybilClusterStatus( 1185 id: number, 1186 status: string, 1187 accessToken: string, 1188 options?: FetchOptions 1189): Promise<SybilCluster> { 1190 return apiFetch<SybilCluster>(`/api/admin/sybil-clusters/${id}`, { 1191 ...options, 1192 method: 'PUT', 1193 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1194 body: { status }, 1195 }) 1196} 1197 1198export function getTrustSeeds( 1199 accessToken: string, 1200 options?: FetchOptions 1201): Promise<TrustSeedsResponse> { 1202 return apiFetch<TrustSeedsResponse>('/api/admin/trust-seeds', { 1203 ...options, 1204 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1205 }) 1206} 1207 1208export function createTrustSeed( 1209 input: CreateTrustSeedInput, 1210 accessToken: string, 1211 options?: FetchOptions 1212): Promise<TrustSeed> { 1213 return apiFetch<TrustSeed>('/api/admin/trust-seeds', { 1214 ...options, 1215 method: 'POST', 1216 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1217 body: input, 1218 }) 1219} 1220 1221export function deleteTrustSeed( 1222 id: number, 1223 accessToken: string, 1224 options?: FetchOptions 1225): Promise<void> { 1226 return apiFetch<void>(`/api/admin/trust-seeds/${id}`, { 1227 ...options, 1228 method: 'DELETE', 1229 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1230 }) 1231} 1232 1233export function getPdsTrustFactors( 1234 accessToken: string, 1235 options?: FetchOptions 1236): Promise<PdsTrustFactorsResponse> { 1237 return apiFetch<PdsTrustFactorsResponse>('/api/admin/pds-trust', { 1238 ...options, 1239 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1240 }) 1241} 1242 1243export function updatePdsTrustFactor( 1244 pdsHost: string, 1245 trustFactor: number, 1246 accessToken: string, 1247 options?: FetchOptions 1248): Promise<PdsTrustFactor> { 1249 return apiFetch<PdsTrustFactor>('/api/admin/pds-trust', { 1250 ...options, 1251 method: 'PUT', 1252 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1253 body: { pdsHost, trustFactor }, 1254 }) 1255} 1256 1257export function getTrustGraphStatus( 1258 accessToken: string, 1259 options?: FetchOptions 1260): Promise<TrustGraphStatus> { 1261 return apiFetch<TrustGraphStatus>('/api/admin/trust-graph/status', { 1262 ...options, 1263 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1264 }) 1265} 1266 1267export function recomputeTrustGraph( 1268 accessToken: string, 1269 options?: FetchOptions 1270): Promise<{ message: string }> { 1271 return apiFetch<{ message: string }>('/api/admin/trust-graph/recompute', { 1272 ...options, 1273 method: 'POST', 1274 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1275 }) 1276} 1277 1278export function getBehavioralFlags( 1279 accessToken: string, 1280 options?: FetchOptions 1281): Promise<BehavioralFlagsResponse> { 1282 return apiFetch<BehavioralFlagsResponse>('/api/admin/behavioral-flags', { 1283 ...options, 1284 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1285 }) 1286} 1287 1288export function updateBehavioralFlag( 1289 id: number, 1290 status: string, 1291 accessToken: string, 1292 options?: FetchOptions 1293): Promise<BehavioralFlag> { 1294 return apiFetch<BehavioralFlag>(`/api/admin/behavioral-flags/${id}`, { 1295 ...options, 1296 method: 'PUT', 1297 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1298 body: { status }, 1299 }) 1300} 1301 1302// --- Setup endpoints --- 1303 1304export function getSetupStatus(options?: FetchOptions): Promise<SetupStatus> { 1305 return apiFetch<SetupStatus>('/api/setup/status', options) 1306} 1307 1308export function initializeCommunity( 1309 input: InitializeCommunityInput, 1310 accessToken: string, 1311 options?: FetchOptions 1312): Promise<InitializeResponse> { 1313 return apiFetch<InitializeResponse>('/api/setup/initialize', { 1314 ...options, 1315 method: 'POST', 1316 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1317 body: input, 1318 }) 1319} 1320 1321// --- Reaction endpoints --- 1322 1323export interface CreateReactionInput { 1324 subjectUri: string 1325 subjectCid: string 1326 type: string 1327} 1328 1329export interface CreateReactionResponse { 1330 uri: string 1331 cid: string 1332 rkey: string 1333 type: string 1334 subjectUri: string 1335 createdAt: string 1336} 1337 1338export function createReaction( 1339 input: CreateReactionInput, 1340 accessToken: string, 1341 options?: FetchOptions 1342): Promise<CreateReactionResponse> { 1343 return apiFetch<CreateReactionResponse>('/api/reactions', { 1344 ...options, 1345 method: 'POST', 1346 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1347 body: input, 1348 }) 1349} 1350 1351export function deleteReaction( 1352 uri: string, 1353 accessToken: string, 1354 options?: FetchOptions 1355): Promise<void> { 1356 const url = `/api/reactions/${encodeURIComponent(uri)}` 1357 return apiFetch<void>(url, { 1358 ...options, 1359 method: 'DELETE', 1360 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1361 }) 1362} 1363 1364export function getReactions( 1365 subjectUri: string, 1366 params: { type?: string; cursor?: string; limit?: number } = {}, 1367 options?: FetchOptions 1368): Promise<ReactionsResponse> { 1369 const query = buildQuery({ 1370 subjectUri, 1371 type: params.type, 1372 cursor: params.cursor, 1373 limit: params.limit, 1374 }) 1375 return apiFetch<ReactionsResponse>(`/api/reactions${query}`, options) 1376} 1377 1378// --- Page endpoints (public) --- 1379 1380export function getPages(options?: FetchOptions): Promise<PagesResponse> { 1381 return apiFetch<PagesResponse>('/api/pages', options) 1382} 1383 1384export function getPageBySlug(slug: string, options?: FetchOptions): Promise<Page> { 1385 return apiFetch<Page>(`/api/pages/${encodeURIComponent(slug)}`, options) 1386} 1387 1388// --- Admin page endpoints --- 1389 1390export function getAdminPages(accessToken: string, options?: FetchOptions): Promise<PagesResponse> { 1391 return apiFetch<PagesResponse>('/api/admin/pages', { 1392 ...options, 1393 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1394 }) 1395} 1396 1397export function getAdminPage( 1398 id: string, 1399 accessToken: string, 1400 options?: FetchOptions 1401): Promise<Page> { 1402 return apiFetch<Page>(`/api/admin/pages/${encodeURIComponent(id)}`, { 1403 ...options, 1404 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1405 }) 1406} 1407 1408export function createPage( 1409 input: CreatePageInput, 1410 accessToken: string, 1411 options?: FetchOptions 1412): Promise<Page> { 1413 return apiFetch<Page>('/api/admin/pages', { 1414 ...options, 1415 method: 'POST', 1416 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1417 body: input, 1418 }) 1419} 1420 1421export function updatePage( 1422 id: string, 1423 input: UpdatePageInput, 1424 accessToken: string, 1425 options?: FetchOptions 1426): Promise<Page> { 1427 return apiFetch<Page>(`/api/admin/pages/${encodeURIComponent(id)}`, { 1428 ...options, 1429 method: 'PUT', 1430 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1431 body: input, 1432 }) 1433} 1434 1435export function deletePage(id: string, accessToken: string, options?: FetchOptions): Promise<void> { 1436 return apiFetch<void>(`/api/admin/pages/${encodeURIComponent(id)}`, { 1437 ...options, 1438 method: 'DELETE', 1439 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1440 }) 1441} 1442 1443// --- Moderation action endpoints --- 1444 1445export interface PinTopicResponse { 1446 uri: string 1447 isPinned: boolean 1448 pinnedScope: 'category' | 'forum' | null 1449 pinnedAt: string | null 1450} 1451 1452export function pinTopic( 1453 topicUri: string, 1454 params: { scope?: 'category' | 'forum'; reason?: string } = {}, 1455 accessToken: string, 1456 options?: FetchOptions 1457): Promise<PinTopicResponse> { 1458 return apiFetch<PinTopicResponse>(`/api/moderation/pin/${encodeURIComponent(topicUri)}`, { 1459 ...options, 1460 method: 'POST', 1461 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1462 body: params, 1463 }) 1464} 1465 1466export interface LockTopicResponse { 1467 uri: string 1468 isLocked: boolean 1469} 1470 1471export function lockTopic( 1472 topicUri: string, 1473 params: { reason?: string } = {}, 1474 accessToken: string, 1475 options?: FetchOptions 1476): Promise<LockTopicResponse> { 1477 return apiFetch<LockTopicResponse>(`/api/moderation/lock/${encodeURIComponent(topicUri)}`, { 1478 ...options, 1479 method: 'POST', 1480 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1481 body: params, 1482 }) 1483} 1484 1485export function deleteTopicMod( 1486 topicUri: string, 1487 params: { reason: string }, 1488 accessToken: string, 1489 options?: FetchOptions 1490): Promise<{ uri: string }> { 1491 return apiFetch<{ uri: string }>(`/api/moderation/delete/${encodeURIComponent(topicUri)}`, { 1492 ...options, 1493 method: 'POST', 1494 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1495 body: params, 1496 }) 1497} 1498 1499// --- Community Rules endpoints --- 1500 1501export function getCommunityRules( 1502 communityDid: string, 1503 options?: FetchOptions 1504): Promise<CommunityRulesResponse> { 1505 return apiFetch<CommunityRulesResponse>( 1506 `/api/communities/${encodeURIComponent(communityDid)}/rules`, 1507 options 1508 ) 1509} 1510 1511export function createCommunityRule( 1512 communityDid: string, 1513 input: CreateRuleInput, 1514 accessToken: string, 1515 options?: FetchOptions 1516): Promise<CommunityRule> { 1517 return apiFetch<CommunityRule>(`/api/communities/${encodeURIComponent(communityDid)}/rules`, { 1518 ...options, 1519 method: 'POST', 1520 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1521 body: input, 1522 }) 1523} 1524 1525export function updateCommunityRule( 1526 communityDid: string, 1527 ruleId: number, 1528 input: UpdateRuleInput, 1529 accessToken: string, 1530 options?: FetchOptions 1531): Promise<CommunityRule> { 1532 return apiFetch<CommunityRule>( 1533 `/api/communities/${encodeURIComponent(communityDid)}/rules/${ruleId}`, 1534 { 1535 ...options, 1536 method: 'PUT', 1537 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1538 body: input, 1539 } 1540 ) 1541} 1542 1543export function deleteCommunityRule( 1544 communityDid: string, 1545 ruleId: number, 1546 accessToken: string, 1547 options?: FetchOptions 1548): Promise<{ success: boolean }> { 1549 return apiFetch<{ success: boolean }>( 1550 `/api/communities/${encodeURIComponent(communityDid)}/rules/${ruleId}`, 1551 { 1552 ...options, 1553 method: 'DELETE', 1554 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1555 } 1556 ) 1557} 1558 1559export function reorderCommunityRules( 1560 communityDid: string, 1561 input: ReorderRulesInput, 1562 accessToken: string, 1563 options?: FetchOptions 1564): Promise<{ success: boolean }> { 1565 return apiFetch<{ success: boolean }>( 1566 `/api/communities/${encodeURIComponent(communityDid)}/rules/reorder`, 1567 { 1568 ...options, 1569 method: 'PUT', 1570 headers: { ...options?.headers, Authorization: `Bearer ${accessToken}` }, 1571 body: input, 1572 } 1573 ) 1574} 1575 1576export { ApiError }