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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ A Node.js library for fetching events and talks from GitEvents-based GitHub repo

- πŸš€ Fetch upcoming and past events from GitHub Issues
- 🎀 Retrieve event talks and speaker submissions (via sub-issues)
- πŸ’¬ Fetch GitHub Discussions (announcements, Q&A, etc.)
- 🏒 Fetch organization statistics and metadata
- πŸ“ Fetch and validate location data with consistent schema
- πŸ‘€ Fetch user profiles and speaker information
Expand Down Expand Up @@ -420,6 +421,52 @@ Each error includes:
- `id` - Location ID (if available)
- `errors` - Array of validation error messages

### `discussions(org, repo, options?)`

Fetch discussions from a repository, optionally filtered by category.

**Parameters:**

- `org` (string) - GitHub organization or user name
- `repo` (string) - Repository name
- `options` (object, optional) - Options
- `first` (number) - Number of discussions to fetch (default: 10)
- `categoryId` (string) - Filter by discussion category ID (optional)

**Returns:** `Promise<Discussion[]>`

**Example:**

```javascript
import { discussions } from 'gitevents-fetch'

// Fetch all discussions
const allDiscussions = await discussions('myorg', 'home', { first: 20 })

// Fetch discussions from specific category
const announcements = await discussions('myorg', 'home', {
categoryId: 'DIC_kwDOG41Ukc4CBSDX',
first: 10
})

console.log(announcements)
// [
// {
// id: 'D_123',
// number: 1,
// title: 'Important Announcement',
// url: 'https://github.com/org/repo/discussions/1',
// body: 'Discussion content...',
// createdAt: Date('2024-01-01T00:00:00.000Z'),
// updatedAt: Date('2024-01-02T00:00:00.000Z'),
// author: { login: 'user', name: 'User Name', ... },
// category: { id: 'CAT_123', name: 'Announcements', emoji: 'πŸ“’', ... },
// reactions: ['THUMBS_UP', 'HEART'],
// commentCount: 5
// }
// ]
```

### Fetching Talks from a Dedicated Repository

Talks stored as issues in a dedicated repository can be fetched using the existing event functions:
Expand Down Expand Up @@ -508,6 +555,34 @@ export DEFAULT_APPROVED_EVENT_LABEL="Approved Talk"
}
```

### Discussion Object Structure

```typescript
{
id: string // Discussion ID
number: number // Discussion number
title: string // Discussion title
url: string // GitHub discussion URL
body: string | null // Discussion body/content
createdAt: Date | null // Creation date
updatedAt: Date | null // Last update date
author: { // Discussion author (null if not available)
login: string // GitHub username
name: string | null // Display name
avatarUrl: string // Profile avatar URL
url: string // GitHub profile URL
} | null
category: { // Discussion category (null if not available)
id: string // Category ID
name: string // Category name
emoji: string // Category emoji
description: string | null // Category description
} | null
reactions: string[] // Array of reaction types
commentCount: number // Number of comments
}
```

## Error Handling

All API methods include comprehensive error handling and production-ready safety features:
Expand Down
68 changes: 68 additions & 0 deletions src/discussions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { parseGql } from './lib/parseGql.js'

function validateParams(params) {
const missing = []
for (const [key, value] of Object.entries(params)) {
if (!value) missing.push(key)
}
if (missing.length > 0) {
throw new Error(`Missing required parameters: ${missing.join(', ')}`)
}
}

function processDiscussionsPayload(edges) {
if (!edges || edges.length === 0) {
return []
}

return edges.map((edge) => {
const node = edge?.node || edge

return {
id: node.id,
number: node.number,
title: node.title,
url: node.url,
body: node.body || null,
createdAt: node.createdAt ? new Date(node.createdAt) : null,
updatedAt: node.updatedAt ? new Date(node.updatedAt) : null,
author: node.author
? {
login: node.author.login,
name: node.author.name || null,
avatarUrl: node.author.avatarUrl,
url: node.author.url
}
: null,
category: node.category
? {
id: node.category.id,
name: node.category.name,
emoji: node.category.emoji,
description: node.category.description || null
}
: null,
reactions: node.reactions?.nodes?.map((r) => r.content) || [],
commentCount: node.comments?.totalCount || 0
}
})
}

export async function listDiscussions(graphql, org, repo, options = {}) {
validateParams({ graphql, org, repo })

try {
const query = await parseGql('discussions')
const vars = {
organization: org,
repository: repo,
first: options.first || 10,
categoryId: options.categoryId || null
}

const result = await graphql(query, vars)
return processDiscussionsPayload(result.repository.discussions.edges)
} catch (error) {
throw new Error(`Failed to fetch discussions: ${error.message}`)
}
}
53 changes: 53 additions & 0 deletions src/graphql/discussions.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
query (
$organization: String!
$repository: String!
$categoryId: ID
$first: Int
) {
repository(owner: $organization, name: $repository) {
discussions(
first: $first
orderBy: { field: CREATED_AT, direction: DESC }
categoryId: $categoryId
) {
pageInfo {
hasNextPage
endCursor
}
edges {
cursor
node {
id
number
title
url
body
createdAt
updatedAt
author {
login
avatarUrl
url
... on User {
name
}
}
category {
id
name
emoji
description
}
reactions(first: 100) {
nodes {
content
}
}
comments(first: 1) {
totalCount
}
}
}
}
}
}
9 changes: 9 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { graphql } from '@octokit/graphql'
import { ghAppId, ghAppInstallationId, ghPrivateKey, ghPAT } from './config.js'
import { listUpcomingEvents, listPastEvents, getEvent } from './events.js'
import { getTeamById } from './teams.js'
import { listDiscussions } from './discussions.js'
import { getOrganization as getOrg } from './organization.js'
import { getLocations as fetchLocations } from './locations.js'
import { getUser as getUserProfile } from './users.js'
Expand Down Expand Up @@ -119,3 +120,11 @@ export async function getOrganization(org) {
}
return getOrg(getGraphqlClient(), org)
}

export async function discussions(org, repo, options) {
// Validate parameters before creating auth
if (!org || !repo) {
throw new Error('Missing required parameters: org and repo are required')
}
return listDiscussions(getGraphqlClient(), org, repo, options)
}
2 changes: 2 additions & 0 deletions src/lib/parseGql.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { defaultApprovedEventLabel } from '../config.js'
import eventsQuery from '../graphql/events.gql?raw'
import eventQuery from '../graphql/event.gql?raw'
import teamQuery from '../graphql/team.gql?raw'
import discussionsQuery from '../graphql/discussions.gql?raw'
import organizationQuery from '../graphql/organization.gql?raw'
import userQuery from '../graphql/user.gql?raw'
import fileQuery from '../graphql/file.gql?raw'
Expand All @@ -10,6 +11,7 @@ const queries = {
events: eventsQuery,
event: eventQuery,
team: teamQuery,
discussions: discussionsQuery,
organization: organizationQuery,
user: userQuery,
file: fileQuery
Expand Down
Loading