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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: CI

# Runs on PRs and pushes to the default branch. We use `pull_request` (never
# `pull_request_target`), so secrets are NOT exposed to pull requests from forks.
# Runs on macOS because the SDK depends on Alamofire, which is Apple-only.
on:
pull_request:
push:
branches: [main, master]

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: swift build
- name: Run tests
run: swift test
env:
FASTCOMMENTS_API_KEY: ${{ secrets.FASTCOMMENTS_API_KEY }}
FASTCOMMENTS_TENANT_ID: ${{ secrets.FASTCOMMENTS_TENANT_ID }}
53 changes: 44 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ Or in Xcode:

The FastComments Swift SDK consists of several modules:

- **Client Module** - Auto-generated API client for FastComments REST APIs
- **Client Module** - API client for FastComments REST APIs
- Complete type definitions for all API models
- Both authenticated (`DefaultAPI`) and public (`PublicAPI`) endpoints
- Authenticated (`DefaultAPI`), public (`PublicAPI`), and moderation (`ModerationAPI`) methods
- Full async/await support
- See [client/README.md](client/README.md) for detailed API documentation

Expand Down Expand Up @@ -90,6 +90,29 @@ do {
}
```

### Using the Moderation API

```swift
import FastCommentsSwift

// Moderation methods are authorized with an `sso` token for the acting moderator
// (generate it with FastCommentsSSO, see the SSO section above).
do {
let response = try await ModerationAPI.getApiComments(
page: 0,
count: 30,
sso: ssoToken
)

print("Found \(response.comments.count) comments to moderate")
for comment in response.comments {
print("Comment ID: \(comment.id), Text: \(comment.commentHTML)")
}
} catch {
print("Error: \(error)")
}
```

### Using SSO for Authentication

#### Secure SSO (Recommended for Production)
Expand Down Expand Up @@ -141,29 +164,41 @@ do {
}
```

## Public vs Secured APIs
## API Clients

The FastComments SDK provides two types of API endpoints:
The FastComments SDK provides three API clients:

### PublicAPI - Client-Safe Endpoints
### PublicAPI - Client-Safe Methods

The `PublicAPI` contains endpoints that are safe to call from client-side code (iOS/macOS apps). These endpoints:
The `PublicAPI` contains methods that are safe to call from client-side code (iOS/macOS apps). These methods:
- Do not require an API key
- Can use SSO tokens for authentication
- Are rate-limited per user/device
- Are suitable for end-user facing applications

**Example use case**: Fetching and creating comments in your iOS app

### DefaultAPI - Server-Side Endpoints
### DefaultAPI - Server-Side Methods

The `DefaultAPI` contains authenticated endpoints that require an API key. These endpoints:
The `DefaultAPI` contains authenticated methods that require an API key. These methods:
- Require your FastComments API key
- Should ONLY be called from server-side code
- Provide full access to your FastComments data
- Are rate-limited per tenant

**Example use case**: Administrative operations, bulk data export, moderation tools
**Example use case**: Administrative operations, bulk data export, user management

### ModerationAPI - Moderator Dashboard Methods

The `ModerationAPI` contains methods that power the moderator dashboard. These methods cover:
- **Comment moderation** - list, count, search, retrieve logs, and export comments
- **Moderation actions** - remove/restore comments, flag, set review/spam/approval status, manage votes, and reopen/close threads
- **Bans** - ban a user from a comment, undo bans, fetch pre-ban summaries, check ban status and preferences, and read banned-user counts
- **Badges & trust** - award/remove badges, list manual badges, get/set a user's trust factor, and read a user's internal profile

Every `ModerationAPI` method accepts an `sso` parameter so moderators can be authenticated via SSO.

**Example use case**: Building a moderation experience for moderators of your community

**IMPORTANT**: Never expose your API key in client-side code. API keys should only be used server-side.

Expand Down
14 changes: 8 additions & 6 deletions Tests/FastCommentsSwiftTests/PubSubTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ final class PubSubTests: XCTestCase {
XCTAssertEqual(badge.backgroundColor, "#gold")
}

// MARK: - EventLogResponse
// MARK: - GetEventLogResponse

func testEventLogResponseDecoding() throws {
let innerEventJson = """
Expand All @@ -304,6 +304,7 @@ final class PubSubTests: XCTestCase {
"events": [
{
"_id": "entry-1",
"createdAt": "2023-11-14T22:13:20Z",
"tenantId": "t1",
"urlId": "u1",
"broadcastId": "bc1",
Expand All @@ -312,15 +313,16 @@ final class PubSubTests: XCTestCase {
]
}
"""
let response = try JSONDecoder().decode(EventLogResponse.self, from: json.data(using: .utf8)!)
XCTAssertEqual(response.status, "success")
XCTAssertEqual(response.events?.count, 1)
// Use the SDK decoder so the ISO8601 `createdAt` Date decodes correctly.
let response = try CodableHelper().jsonDecoder.decode(GetEventLogResponse.self, from: json.data(using: .utf8)!)
XCTAssertEqual(response.status, .success)
XCTAssertEqual(response.events.count, 1)

let entry = response.events![0]
let entry = response.events[0]
XCTAssertEqual(entry.id, "entry-1")

// Parse the nested data string
let eventData = entry.data!.data(using: .utf8)!
let eventData = entry.data.data(using: .utf8)!
let event = try JSONDecoder().decode(LiveEvent.self, from: eventData)
XCTAssertEqual(event.type, .newComment)
XCTAssertEqual(event.comment?.id, "c1")
Expand Down
26 changes: 13 additions & 13 deletions Tests/FastCommentsSwiftTests/SSOIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ final class SSOIntegrationTests: XCTestCase {

XCTAssertEqual(createResponse.status, .success, "Create comment should succeed")
XCTAssertNotNil(createResponse.comment, "Response should include the created comment")
XCTAssertEqual(createResponse.comment?.commenterName, user.username)
XCTAssertTrue(createResponse.comment?.commentHTML.contains(commentText) ?? false, "Comment HTML should contain original text")
XCTAssertEqual(createResponse.comment.commenterName, user.username)
XCTAssertTrue(createResponse.comment.commentHTML.contains(commentText), "Comment HTML should contain original text")

// Verify the create response returns the authenticated user
XCTAssertNotNil(createResponse.user, "Create response should include user session info")
Expand All @@ -74,12 +74,12 @@ final class SSOIntegrationTests: XCTestCase {
sso: token
)

XCTAssertEqual(getResponse.status, .success, "Get comments should succeed")
XCTAssertEqual(getResponse.status, "success", "Get comments should succeed")
XCTAssertNotNil(getResponse.comments, "Should have comments array")
XCTAssertGreaterThanOrEqual(getResponse.comments?.count ?? 0, 1, "Should have at least one comment")
XCTAssertGreaterThanOrEqual(getResponse.comments.count, 1, "Should have at least one comment")

// Verify the fetched comment matches what we created
let fetchedComment = getResponse.comments?.first
let fetchedComment = getResponse.comments.first
XCTAssertNotNil(fetchedComment)
XCTAssertEqual(fetchedComment?.commenterName, user.username)
XCTAssertTrue(fetchedComment?.commentHTML.contains(commentText) ?? false, "Fetched comment should contain original text")
Expand Down Expand Up @@ -143,11 +143,11 @@ final class SSOIntegrationTests: XCTestCase {
sso: token
)

XCTAssertEqual(getResponse.status, .success)
XCTAssertEqual(getResponse.status, "success")
XCTAssertNotNil(getResponse.comments)
XCTAssertGreaterThanOrEqual(getResponse.comments?.count ?? 0, 1)
XCTAssertGreaterThanOrEqual(getResponse.comments.count, 1)

let fetchedComment = getResponse.comments?.first
let fetchedComment = getResponse.comments.first
XCTAssertEqual(fetchedComment?.displayLabel, "Swift Tester")
// When displayName is set, commenterName resolves to the displayName
XCTAssertEqual(fetchedComment?.commenterName, user.displayName)
Expand Down Expand Up @@ -196,8 +196,8 @@ final class SSOIntegrationTests: XCTestCase {

XCTAssertEqual(createResponse.status, .success, "Create comment with simple SSO should succeed")
XCTAssertNotNil(createResponse.comment)
XCTAssertEqual(createResponse.comment?.commenterName, user.username)
XCTAssertTrue(createResponse.comment?.commentHTML.contains(commentText) ?? false)
XCTAssertEqual(createResponse.comment.commenterName, user.username)
XCTAssertTrue(createResponse.comment.commentHTML.contains(commentText))

// Verify user session is returned
XCTAssertNotNil(createResponse.user, "Create response should include user session info")
Expand All @@ -211,12 +211,12 @@ final class SSOIntegrationTests: XCTestCase {
sso: token
)

XCTAssertEqual(getResponse.status, .success, "Get comments with simple SSO should succeed")
XCTAssertEqual(getResponse.status, "success", "Get comments with simple SSO should succeed")
XCTAssertNotNil(getResponse.comments)
XCTAssertGreaterThanOrEqual(getResponse.comments?.count ?? 0, 1, "Should have at least one comment")
XCTAssertGreaterThanOrEqual(getResponse.comments.count, 1, "Should have at least one comment")

// Verify the fetched comment
let fetchedComment = getResponse.comments?.first
let fetchedComment = getResponse.comments.first
XCTAssertNotNil(fetchedComment)
XCTAssertEqual(fetchedComment?.commenterName, user.username)
XCTAssertTrue(fetchedComment?.commentHTML.contains(commentText) ?? false)
Expand Down
3 changes: 0 additions & 3 deletions client/.openapi-generator-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# Preserve manually-patched infrastructure files from being overwritten
FastCommentsSwift/Infrastructure/URLSessionImplementations.swift

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
Expand Down
Loading
Loading