Skip to content
Open
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
50 changes: 47 additions & 3 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ GITHUB_CLIENT_ID=fakedata
GITHUB_CLIENT_SECRET=fakedata

## Hawk API public url (used in OAuth to redirect to callback, should match OAuth app callback URL)
API_URL=http://127.0.0.1:4000
API_URL=http://localhost:4000

## Garage url
GARAGE_URL=http://127.0.0.1:8080
GARAGE_URL=http://localhost:8080

## Garage login url
GARAGE_LOGIN_URL=http://127.0.0.1:8080/login
GARAGE_LOGIN_URL=http://localhost:8080/login

## Hawk Catcher token from hawk.so
HAWK_CATCHER_TOKEN=
Expand Down Expand Up @@ -93,3 +93,47 @@ SSO_SP_ENTITY_ID=urn:hawk:tracker:saml

## SAML state store type (memory or redis, default: redis)
SAML_STORE_TYPE=redis

# String generated when we create GitHub App, see task-managers-integration-implementation-plan.md -> 2.1.1
GITHUB_WEBHOOK_SECRET=623f6ed30b1f762803149893263a95cc2687fe3ce5a9f30648dcbf25712afc9e

# Id of GitHub app
GITHUB_APP_ID=1234567

# Client ID of GitHub app
GITHUB_APP_CLIENT_ID=Iv23li65HEIkWZXsm6qO

# GitHub App slug/name. Used to generate installation URLs
GITHUB_APP_SLUG=hawk-tracker-app

# Private key generated in GitHub app settings
GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0r16047BqAxFgbltKcNOt9RGMZ2COI2ui7Ujmn9vtMV83HHu
lN/ek4kLTz8nunUc0s21xWpW7mjoaO61qSzcEn7vDhgMOAnq+wq+iJsk5IM/MJNe
sNatymMVO6Q6UuVx4Nshac3T7M8dVWx3Oc9ef9nc/dXdXTQn73EZC5pKqKoFE+yv
NGHS0JAgUGa1zDt/TqXcypz06tyrFgZFFuBo01kS1xUU/J4bpwNnk9KNq7lakdAE
JeJ/BzwTRZUzSIjtLSDjFbcjI2iXsTOasrygXDTYYcnvQf4HjRxBuCwIuGo4mOHz
86l5Icq9dNWq1Nj+FJW9IMEOYW0927uws+dotwIDAQABAoIBAHfdRitmm0eWErEm
YqzKZc+xcWtvB05bZ9gW43VQ3pyXZ3mLZARRgSuxWzlr1pD7Y7WTQ7xRy7g2+1oT
zEe5OENc52PA0dJd8cVwSwcwFz/SVvKuH8G9mYPv73fI5VOZJbibatnfNJcRBsI7
u2SqSjm2FThbmFkW/U/3qCMtUyGy6atoUZvoeQ5aUKmW7Z4gaPVN3z+265qJwR3o
qnNUY2FNEA8tsJ05EzqBp8+SYghAiKY1QKqSb2pIfbBNmx5ItB2WeH3sTaE3Zdzy
Kk/5fDY/rlOouRg7kHQd9sPnXzy7LVhUaQG0fZdjBOpJFXHFqi1Gsf3HAMRHkhyJ
+EvgOuECgYEA/B85RCgVagwm9otXyj5JGIbj2iSAEwKnk2fxiZsGC3pirK85ZITp
bZiIvwMzCKiHlL0vjiVsFBL8tr7p5bP1g4GNTOCg2Dj+B/Or9IgxHxpDCIPrV+L0
GBgJEsNylJESQU/xf6qBs88FLFLyRtwxXkucsfk+OrF5IBUaEdEuo9UCgYEA1ftO
wiD4O3LTs5dZiUHBrLWGejfbltHILF4oA61O7pLlMWkBvN1H8pJOI9FbTHWlv4Vc
UFApUjm4wUGArCKu7AfcSZ2+xhySIC3ilReXbQp7WqdKp/T/RaKh38zEa6MVMqAJ
cYYoWj4/NJMN1a4+G/zU5adMVb0jITyJB1EYfFsCgYEApUSSdWsRHoL4x4Rv99L8
d2d01Po4Oj3zO11Xp6xHOh7vr+Ls7Edz/LOQcCXYvkQ7G/UnxzYgssf/gIuFJ13g
AmRaC5rz1MkHPI8umQztp3XAy0QucV4ERAb9a59S7LBsFwQgel96xjNeYL++sVSF
yBoojUGk2TSdAbrTa/qDaEECgYBCrqD5gBq7M+pjEew2CMbZEmyI07Vbh55QrTrd
AnoRgLdpsWZ4O6D7J7qwEMLZzePMDjwZTxHBbPl1R/tYKSrHpR9x1XWo+ShUXNg6
S/LFaTnNo0pxkrimM6ssOfyP6m9lqlenB/61OKartJPgHf9+60hRFNSF933mEp5F
KHFv9wKBgQCXaVz5sgtcWgkQYSn4XTwSYKaeYww4hnIel30pASrLujQ9FubAvdjZ
apxW9AHmx4aVRrmcIPq/BlMc6lGIgx2IwMBvJVpHVeUOUMAfNMRZV0XjY715xEyW
U/uCfmCh8rfyQ75rthD4mGzNmHBpWrP/bD3c/vdj0wAxFXVyR5bG/Q==
-----END RSA PRIVATE KEY-----"

# Generated in GitHub app settings
GITHUB_APP_CLIENT_SECRET=0663e20d484234e17b0871c1f070581739c14e04
15 changes: 8 additions & 7 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
version: "3.4"
services:
api:
build:
Expand All @@ -16,7 +15,6 @@ services:
- mongodb
- rabbitmq
- keycloak
# - accounting
stdin_open: true
tty: true

Expand Down Expand Up @@ -65,7 +63,7 @@ services:
retries: 5

keycloak:
image: quay.io/keycloak/keycloak:23.0
image: quay.io/keycloak/keycloak:22.0
environment:
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin
Expand All @@ -74,18 +72,21 @@ services:
- KC_HOSTNAME_STRICT_HTTPS=false
- KC_HTTP_ENABLED=true
- KC_HEALTH_ENABLED=true
- JAVA_OPTS_APPEND=-Djava.io.tmpdir=/opt/keycloak/data/tmp
ports:
- 8180:8180
command:
- start-dev
volumes:
- keycloak-test-data:/opt/keycloak/data
- ./test/integration/keycloak:/opt/keycloak/config
tmpfs:
- /tmp:size=128M
healthcheck:
test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8180;echo -e 'GET /health/ready HTTP/1.1\r\nhost: http://localhost\r\nConnection: close\r\n\r\n' >&3;if [ $? -eq 0 ]; then echo 'Healthcheck Successful';exit 0;else echo 'Healthcheck Failed';exit 1;fi;"]
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8180 && echo -e 'GET /health/ready HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3 && cat <&3 | grep -q '200 OK'"]
interval: 10s
timeout: 5s
retries: 10
timeout: 10s
retries: 15
start_period: 60s

# accounting:
# image: codexteamuser/codex-accounting:prod
Expand Down
10 changes: 10 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ module.exports = {
}],
},

/**
* Map node: prefixed imports to mock files
* Jest 27 supports node: prefix for ESM imports, but CommonJS require('node:crypto')
* in modules like argon2.cjs still needs explicit mapping to mocks
*/
moduleNameMapper: {
'^node:crypto$': '<rootDir>/test/__mocks__/node_crypto.js',
'^node:util$': '<rootDir>/test/__mocks__/node_util.js',
},

/**
* Ignore folders
*/
Expand Down
17 changes: 10 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hawk.api",
"version": "1.3.1",
"version": "1.4.0",
"main": "index.ts",
"license": "BUSL-1.1",
"scripts": {
Expand All @@ -22,16 +22,16 @@
"devDependencies": {
"@shelf/jest-mongodb": "^6.0.2",
"@swc/core": "^1.3.0",
"@types/jest": "^26.0.8",
"@types/jest": "^29.5.0",
"@types/xml2js": "^0.4.14",
"eslint": "^6.7.2",
"eslint-config-codex": "1.2.4",
"eslint-plugin-import": "^2.19.1",
"jest": "^26.2.2",
"jest": "^30.2.0",
"mongodb-memory-server": "^6.6.1",
"nodemon": "^2.0.2",
"redis-mock": "^0.56.3",
"ts-jest": "^26.1.4",
"ts-jest": "^29.4.0",
"ts-node": "^10.9.1",
"typescript": "^4.7.4",
"xml2js": "^0.6.2"
Expand All @@ -43,9 +43,12 @@
"@graphql-tools/schema": "^8.5.1",
"@graphql-tools/utils": "^8.9.0",
"@hawk.so/nodejs": "^3.1.1",
"@hawk.so/types": "^0.4.2",
"@hawk.so/types": "^0.5.6",
"@n1ru4l/json-patch-plus": "^0.2.0",
"@node-saml/node-saml": "^5.0.1",
"@octokit/oauth-methods": "^4.0.0",
"@octokit/rest": "^22.0.1",
"@octokit/types": "^16.0.0",
"@types/amqp-connection-manager": "^2.0.4",
"@types/debug": "^4.1.5",
"@types/escape-html": "^1.0.0",
Expand All @@ -62,7 +65,7 @@
"amqp-connection-manager": "^3.1.0",
"amqplib": "^0.5.5",
"apollo-server-express": "^3.10.0",
"argon2": "^0.28.7",
"argon2": "^0.44.0",
"aws-sdk": "^2.1174.0",
"axios": "^0.27.2",
"body-parser": "^1.19.0",
Expand All @@ -76,7 +79,7 @@
"graphql-scalars": "^1.17.0",
"graphql-type-json": "^0.3.0",
"graphql-upload": "^13",
"jsonwebtoken": "^8.5.1",
"jsonwebtoken": "^9.0.3",
"lodash": "^4.17.15",
"lodash.clonedeep": "^4.5.0",
"lodash.mergewith": "^4.6.2",
Expand Down
17 changes: 12 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { requestLogger } from './utils/logger';
import ReleasesFactory from './models/releasesFactory';
import RedisHelper from './redisHelper';
import { appendSsoRoutes } from './sso';
import { appendGitHubRoutes } from './integrations/github';

/**
* Option to enable playground
Expand Down Expand Up @@ -248,20 +249,26 @@ class HawkAPI {
await redis.initialize();

/**
* Setup shared factories for SSO routes
* SSO endpoints don't require per-request DataLoaders isolation,
* Setup shared factories for SSO and GitHub integration routes
* These endpoints don't require per-request DataLoaders isolation,
* so we can reuse the same factories instance
* Created here to avoid duplication with createContext
*/
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const ssoDataLoaders = new DataLoaders(mongo.databases.hawk!);
const ssoFactories = HawkAPI.setupFactories(ssoDataLoaders);
const sharedDataLoaders = new DataLoaders(mongo.databases.hawk!);
const sharedFactories = HawkAPI.setupFactories(sharedDataLoaders);

/**
* Append SSO routes to Express app using shared factories
* Note: This must be called after database connections are established
*/
appendSsoRoutes(this.app, ssoFactories);
appendSsoRoutes(this.app, sharedFactories);

/**
* Append GitHub integration routes to Express app using shared factories
* Note: This must be called after database connections are established
*/
appendGitHubRoutes(this.app, sharedFactories);

await this.server.start();
this.app.use(graphqlUploadExpress());
Expand Down
20 changes: 20 additions & 0 deletions src/integrations/github/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import express from 'express';
import { createGitHubRouter } from './routes';
import { ContextFactories } from '../../types/graphql';

/**
* Re-export types and service from service.ts for backward compatibility
*/
export { GitHubService, IssueData, GitHubIssue, Installation, Repository } from './service';

/**
* Append GitHub routes to Express app
*
* @param app - Express application instance
* @param factories - context factories for database access
*/
export function appendGitHubRoutes(app: express.Application, factories: ContextFactories): void {
const githubRouter = createGitHubRouter(factories);

app.use('/integration/github', githubRouter);
}
Loading
Loading