Skip to content

Commit 70c7974

Browse files
garrrikkotuaUroš Marolt
andauthored
Stack Overflow integration (#671)
Co-authored-by: Uroš Marolt <uros@crowd.dev>
1 parent b555860 commit 70c7974

76 files changed

Lines changed: 2245 additions & 266 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/.env.dist.composed

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ CROWD_SEARCH_ENGINE_HOST="http://search-engine:7700"
2121
# CubeJS settings
2222
CROWD_CUBEJS_URL="http://cubejs:4000/cubejs-api/v1"
2323

24-
# Pizzly settings
25-
CROWD_PIZZLY_URL=http://pizzly:3003
24+
# Nango settings
25+
CROWD_NANGO_URL=http://nango:3003

backend/.env.dist.local

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,16 @@ CROWD_GITHUB_CLIENT_SECRET=
117117
CROWD_GITHUB_PRIVATE_KEY=
118118
CROWD_GITHUB_WEBHOOK_SECRET=
119119

120-
# Pizzly settings
121-
CROWD_PIZZLY_URL=http://localhost:3003
122-
CROWD_PIZZLY_SECRET_KEY=424242
123-
CROWD_PIZZLY_INTEGRATIONS=reddit,linkedin
120+
# Stack Overflow settings
121+
CROWD_STACKEXCHANGE_CLIENT_ID=
122+
CROWD_STACKEXCHANGE_CLIENT_SECRET=
123+
CROWD_STACKEXCHANGE_SCOPES=
124+
CROWD_STACKEXCHANGE_KEY=
125+
126+
# Nango settings
127+
CROWD_NANGO_URL=http://localhost:3003
128+
CROWD_NANGO_SECRET_KEY=424242
129+
CROWD_NANGO_INTEGRATIONS=reddit,linkedin,stackexchange
124130

125131
# Cohere settings
126132
CROWD_COHERE_API_KEY=

backend/config/custom-environment-variables.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,12 @@
130130
"privateKey": "CROWD_GITHUB_PRIVATE_KEY",
131131
"webhookSecret": "CROWD_GITHUB_WEBHOOK_SECRET"
132132
},
133-
"pizzly": {
134-
"url": "CROWD_PIZZLY_URL",
135-
"secretKey": "CROWD_PIZZLY_SECRET_KEY"
133+
"stackexchange": {
134+
"key": "CROWD_STACKEXCHANGE_KEY"
135+
},
136+
"nango": {
137+
"url": "CROWD_NANGO_URL",
138+
"secretKey": "CROWD_NANGO_SECRET_KEY"
136139
},
137140
"enrichment": {
138141
"url": "CROWD_ENRICHMENT_URL",

backend/config/default.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"maxRetrospectInSeconds": 3600
3434
},
3535
"github": {},
36+
"stackexchange": {},
3637
"enrichment": {},
3738
"eagleEye": {},
3839
"unleash": {

backend/package-lock.json

Lines changed: 0 additions & 56 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
"@aws-sdk/util-format-url": "^3.226.0",
4646
"@cubejs-client/core": "^0.30.4",
4747
"@google-cloud/storage": "5.3.0",
48-
"@nangohq/pizzly-node": "^0.3.6",
4948
"@octokit/auth-app": "^3.6.1",
5049
"@octokit/graphql": "^4.8.0",
5150
"@octokit/request": "^5.6.3",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Permissions from '../../../security/permissions'
2+
import IntegrationService from '../../../services/integrationService'
3+
import PermissionChecker from '../../../services/user/permissionChecker'
4+
5+
export default async (req, res) => {
6+
new PermissionChecker(req).validateHas(Permissions.values.tenantEdit)
7+
const payload = await new IntegrationService(req).stackOverflowConnectOrUpdate(req.body)
8+
await req.responseHandler.success(req, res, payload)
9+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import axios from 'axios'
2+
import Error400 from '../../../errors/Error400'
3+
import Permissions from '../../../security/permissions'
4+
import PermissionChecker from '../../../services/user/permissionChecker'
5+
import track from '../../../segment/track'
6+
import { StackOverflowTagsResponse } from '../../../serverless/integrations/types/stackOverflowTypes'
7+
import { STACKEXCHANGE_CONFIG } from '../../../config'
8+
9+
export default async (req, res) => {
10+
new PermissionChecker(req).validateHasAny([
11+
Permissions.values.integrationCreate,
12+
Permissions.values.integrationEdit,
13+
])
14+
15+
if (req.query.tag) {
16+
try {
17+
const result = await axios.get(
18+
`https://api.stackexchange.com/2.3/tags/${req.query.tag}/info`,
19+
{
20+
params: {
21+
site: 'stackoverflow',
22+
key: STACKEXCHANGE_CONFIG.key,
23+
},
24+
},
25+
)
26+
const data = result.data as StackOverflowTagsResponse
27+
console.log(data)
28+
if (
29+
result.status === 200 &&
30+
data.items &&
31+
data.items.length > 0 &&
32+
data.items[0].is_moderator_only === false &&
33+
data.items[0].count > 0
34+
) {
35+
console.log('here')
36+
track(
37+
'Stack Overflow: tag input',
38+
{
39+
tag: req.query.tag,
40+
valid: true,
41+
},
42+
{ ...req },
43+
)
44+
return req.responseHandler.success(req, res, data)
45+
}
46+
} catch (e) {
47+
track(
48+
'Stack Overflow: tag input',
49+
{
50+
tag: req.query.tag,
51+
valid: false,
52+
},
53+
{ ...req },
54+
)
55+
return req.responseHandler.error(req, res, new Error400(req.language))
56+
}
57+
}
58+
track(
59+
'Stack Overflow: tag input',
60+
{
61+
tag: req.query.subreddit,
62+
valid: false,
63+
},
64+
{ ...req },
65+
)
66+
return req.responseHandler.error(req, res, new Error400(req.language))
67+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import axios from 'axios'
2+
import Error400 from '../../../errors/Error400'
3+
import Permissions from '../../../security/permissions'
4+
import PermissionChecker from '../../../services/user/permissionChecker'
5+
import { STACKEXCHANGE_CONFIG } from '../../../config'
6+
7+
export default async (req, res) => {
8+
new PermissionChecker(req).validateHasAny([
9+
Permissions.values.integrationCreate,
10+
Permissions.values.integrationEdit,
11+
])
12+
13+
if (req.query.keywords) {
14+
try {
15+
const promises = req.query.keywords.split(';').map((keyword) =>
16+
axios.get(`https://api.stackexchange.com/2.3/search/advanced`, {
17+
params: {
18+
site: 'stackoverflow',
19+
q: `"${keyword}"`,
20+
filter: 'total',
21+
key: STACKEXCHANGE_CONFIG.key,
22+
},
23+
}),
24+
)
25+
const responses = await Promise.all(promises)
26+
if (responses.every((response) => response.status === 200)) {
27+
console.log('here')
28+
return req.responseHandler.success(req, res, {
29+
total: responses.reduce((acc, response) => acc + response.data.total, 0),
30+
})
31+
}
32+
} catch (e) {
33+
return req.responseHandler.error(req, res, new Error400(req.language))
34+
}
35+
}
36+
return req.responseHandler.error(req, res, new Error400(req.language))
37+
}

backend/src/api/integration/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ export default (app) => {
4545
safeWrap(require('./helpers/hackerNewsCreateOrUpdate').default),
4646
)
4747

48+
app.post(
49+
'/tenant/:tenantId/stackoverflow-connect',
50+
safeWrap(require('./helpers/stackOverflowCreateOrUpdate').default),
51+
)
52+
app.get(
53+
'/tenant/:tenantId/stackoverflow-validate',
54+
safeWrap(require('./helpers/stackOverflowValidator').default),
55+
)
56+
app.get(
57+
'/tenant/:tenantId/stackoverflow-volume',
58+
safeWrap(require('./helpers/stackOverflowVolume').default),
59+
)
60+
4861
if (TWITTER_CONFIG.clientId) {
4962
/**
5063
* Using the passport.authenticate this endpoint forces a

0 commit comments

Comments
 (0)