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
36 changes: 36 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 organization statistics and metadata
- πŸ“ Fetch and validate location data with consistent schema
- πŸ‘€ Fetch user profiles and speaker information
- πŸ“„ Fetch file contents from repositories (text files, JSON, etc.)
Expand Down Expand Up @@ -220,6 +221,41 @@ console.log(team)

**Note:** Returns `null` if the team is not found.

### `getOrganization(org)`

Fetch organization statistics and metadata.

**Parameters:**

- `org` (string) - GitHub organization name

**Returns:** `Promise<Organization | null>`

Returns organization data or `null` if not found.

**Example:**

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

const org = await getOrganization('myorg')

console.log(org)
// {
// name: 'My Organization',
// login: 'myorg',
// description: 'We build amazing things',
// websiteUrl: 'https://myorg.com',
// avatarUrl: 'https://github.com/myorg.png',
// email: 'hello@myorg.com',
// location: 'San Francisco, CA',
// createdAt: Date('2020-01-01T00:00:00.000Z'),
// updatedAt: Date('2024-01-01T00:00:00.000Z'),
// memberCount: 42,
// publicRepoCount: 128
// }
```

### `getUser(login)`

Fetch a GitHub user profile (useful for speaker information).
Expand Down
21 changes: 21 additions & 0 deletions src/graphql/organization.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
query (
$organization: String!
) {
organization(login: $organization) {
name
login
description
websiteUrl
avatarUrl
email
location
createdAt
updatedAt
membersWithRole {
totalCount
}
repositories(privacy: PUBLIC) {
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 { getOrganization as getOrg } from './organization.js'
import { getLocations as fetchLocations } from './locations.js'
import { getUser as getUserProfile } from './users.js'
import { getFile as getFileContent } from './files.js'
Expand Down Expand Up @@ -110,3 +111,11 @@ export async function getLocations(org, repo, options) {
}
return fetchLocations(getGraphqlClient(), org, repo, options)
}

export async function getOrganization(org) {
// Validate parameters before creating auth
if (!org) {
throw new Error('Missing required parameters: org is required')
}
return getOrg(getGraphqlClient(), org)
}
2 changes: 2 additions & 0 deletions src/lib/parseGql.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ 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 organizationQuery from '../graphql/organization.gql?raw'
import userQuery from '../graphql/user.gql?raw'
import fileQuery from '../graphql/file.gql?raw'

const queries = {
events: eventsQuery,
event: eventQuery,
team: teamQuery,
organization: organizationQuery,
user: userQuery,
file: fileQuery
}
Expand Down
46 changes: 46 additions & 0 deletions src/organization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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(', ')}`)
}
}

export async function getOrganization(graphql, org) {
validateParams({ graphql, org })

try {
const query = await parseGql('organization')
const vars = {
organization: org
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable 'org' is used before its declaration.

Copilot uses AI. Check for mistakes.
}

const result = await graphql(query, vars)

if (!result.organization) {
return null
}

const orgData = result.organization

return {
name: orgData.name || null,
login: orgData.login || null,
description: orgData.description || null,
websiteUrl: orgData.websiteUrl || null,
avatarUrl: orgData.avatarUrl || null,
email: orgData.email || null,
location: orgData.location || null,
createdAt: orgData.createdAt ? new Date(orgData.createdAt) : null,
updatedAt: orgData.updatedAt ? new Date(orgData.updatedAt) : null,
memberCount: orgData.membersWithRole?.totalCount || 0,
publicRepoCount: orgData.repositories?.totalCount || 0
}
} catch (error) {
throw new Error(`Failed to fetch organization: ${error.message}`)
}
}
156 changes: 156 additions & 0 deletions test/organization.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import test from 'node:test'
import assert from 'node:assert'
import { getOrganization } from '../src/organization.js'

test('getOrganization - validates required parameters', async () => {
await assert.rejects(
async () => {
await getOrganization(null, 'org')
},
{
message: /Missing required parameters/
},
'Should validate graphql parameter'
)

await assert.rejects(
async () => {
const mockGraphql = () => {}
await getOrganization(mockGraphql, null)
},
{
message: /Missing required parameters/
},
'Should validate org parameter'
)
})

test('getOrganization - fetches organization successfully', async () => {
const mockGraphql = async () => ({
organization: {
name: 'Test Organization',
login: 'test-org',
description: 'A test organization',
websiteUrl: 'https://test-org.com',
avatarUrl: 'https://github.com/test-org.png',
email: 'hello@test-org.com',
location: 'San Francisco, CA',
createdAt: '2020-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z',
membersWithRole: {
totalCount: 42
},
repositories: {
totalCount: 128
}
}
})

const result = await getOrganization(mockGraphql, 'test-org')

assert.equal(result.name, 'Test Organization')
assert.equal(result.login, 'test-org')
assert.equal(result.description, 'A test organization')
assert.equal(result.websiteUrl, 'https://test-org.com')
assert.equal(result.avatarUrl, 'https://github.com/test-org.png')
assert.equal(result.email, 'hello@test-org.com')
assert.equal(result.location, 'San Francisco, CA')
assert.ok(result.createdAt instanceof Date)
assert.ok(result.updatedAt instanceof Date)
assert.equal(result.memberCount, 42)
assert.equal(result.publicRepoCount, 128)
})

test('getOrganization - handles missing optional fields', async () => {
const mockGraphql = async () => ({
organization: {
name: 'Test Org',
login: 'test-org',
description: null,
websiteUrl: null,
avatarUrl: 'https://github.com/test-org.png',
email: null,
location: null,
createdAt: '2020-01-01T00:00:00Z',
updatedAt: null,
membersWithRole: {
totalCount: 5
},
repositories: null
}
})

const result = await getOrganization(mockGraphql, 'test-org')

assert.equal(result.description, null)
assert.equal(result.websiteUrl, null)
assert.equal(result.email, null)
assert.equal(result.location, null)
assert.equal(result.updatedAt, null)
assert.equal(result.publicRepoCount, 0)
})

test('getOrganization - returns null if organization not found', async () => {
const mockGraphql = async () => ({
organization: null
})

const result = await getOrganization(mockGraphql, 'nonexistent-org')

assert.equal(result, null)
})

test('getOrganization - handles GraphQL errors', async () => {
const mockGraphql = async () => {
throw new Error('API rate limit exceeded')
}

await assert.rejects(
async () => {
await getOrganization(mockGraphql, 'test-org')
},
{
message: /Failed to fetch organization: API rate limit exceeded/
},
'Should wrap GraphQL errors'
)
})

// Integration test with real API
test(
'getOrganization - real API call',
{
skip: !process.env.GH_PAT && !process.env.GH_PRIVATE_KEY
},
async () => {
const { getOrganization: getOrgAPI } = await import('../src/index.js')

try {
// Fetch gitevents organization
const org = await getOrgAPI('gitevents')

assert.ok(org, 'Should return organization data')
assert.equal(org.login, 'gitevents')
assert.ok(org.name, 'Should have name')
assert.ok(typeof org.memberCount === 'number', 'Should have member count')
assert.ok(
typeof org.publicRepoCount === 'number',
'Should have public repo count'
)
assert.ok('description' in org, 'Should have description field')
assert.ok('websiteUrl' in org, 'Should have websiteUrl field')
assert.ok(org.avatarUrl, 'Should have avatar URL')
} catch (error) {
// GitHub App may not have permission to access organization data
if (error.message.includes('Resource not accessible by integration')) {
console.log(
'Note: GitHub App does not have permission to access organization data. This is expected in CI/CD environments.'
)
// Skip this assertion if permissions are insufficient
assert.ok(true, 'Skipped due to insufficient permissions')
} else {
throw error
}
}
}
)