Skip to content

Commit 09d9918

Browse files
committed
Merge branch 'main' into crowd-linux
2 parents b43a77a + ecb00e9 commit 09d9918

Some content is hidden

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

50 files changed

+2118
-15
lines changed

backend/.env.dist.local

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,4 @@ CROWD_WEEKLY_EMAILS_ENABLED="true"
165165
CROWD_ANALYTICS_IS_ENABLED=
166166
CROWD_ANALYTICS_TENANT_ID=
167167
CROWD_ANALYTICS_BASE_URL=
168-
CROWD_ANALYTICS_API_TOKEN=
168+
CROWD_ANALYTICS_API_TOKEN=
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).groupsioConnectOrUpdate(req.body)
8+
await req.responseHandler.success(req, res, payload)
9+
}
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).groupsioGetToken(req.body)
8+
await req.responseHandler.success(req, res, payload)
9+
}
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).groupsioVerifyGroup(req.body)
8+
await req.responseHandler.success(req, res, payload)
9+
}

backend/src/api/integration/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,21 @@ export default (app) => {
132132
safeWrap(require('./helpers/hubspotStopSyncOrganization').default),
133133
)
134134

135+
app.post(
136+
'/tenant/:tenantId/groupsio-connect',
137+
safeWrap(require('./helpers/groupsioConnectOrUpdate').default),
138+
)
139+
140+
app.post(
141+
'/tenant/:tenantId/groupsio-get-token',
142+
safeWrap(require('./helpers/groupsioGetToken').default),
143+
)
144+
145+
app.post(
146+
'/tenant/:tenantId/groupsio-verify-group',
147+
safeWrap(require('./helpers/groupsioVerifyGroup').default),
148+
)
149+
135150
// if (TWITTER_CONFIG.clientId) {
136151
// /**
137152
// * Using the passport.authenticate this endpoint forces a

backend/src/database/repositories/segmentRepository.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -671,8 +671,12 @@ class SegmentRepository extends RepositoryBase<
671671
}
672672
})
673673

674-
// TODO: Add member count to segments after implementing member relations
675-
return { count, rows, limit: criteria.limit, offset: criteria.offset }
674+
return {
675+
count,
676+
rows: rows.map((sr) => SegmentRepository.populateRelations(sr)),
677+
limit: criteria.limit,
678+
offset: criteria.offset,
679+
}
676680
}
677681

678682
private async queryIntegrationsForSubprojects(subprojects) {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export interface GroupsioIntegrationData {
2+
email: string
3+
token: string
4+
groupNames: GroupName[]
5+
}
6+
7+
export interface GroupsioGetToken {
8+
email: string
9+
password: string
10+
twoFactorCode?: string
11+
}
12+
13+
export interface GroupsioVerifyGroup {
14+
groupName: GroupName
15+
cookie: string
16+
}
17+
18+
export type GroupName = string

backend/src/services/integrationService.ts

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createAppAuth } from '@octokit/auth-app'
22
import { request } from '@octokit/request'
33
import moment from 'moment'
4-
import axios from 'axios'
4+
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
55
import { PlatformType } from '@crowd/types'
66
import {
77
HubspotFieldMapperFactory,
@@ -44,6 +44,11 @@ import OrganizationService from './organizationService'
4444
import MemberSyncRemoteRepository from '@/database/repositories/memberSyncRemoteRepository'
4545
import OrganizationSyncRemoteRepository from '@/database/repositories/organizationSyncRemoteRepository'
4646
import MemberRepository from '@/database/repositories/memberRepository'
47+
import {
48+
GroupsioIntegrationData,
49+
GroupsioGetToken,
50+
GroupsioVerifyGroup,
51+
} from '@/serverless/integrations/usecases/groupsio/types'
4752

4853
const discordToken = DISCORD_CONFIG.token || DISCORD_CONFIG.token2
4954

@@ -1403,4 +1408,109 @@ export default class IntegrationService {
14031408

14041409
return integration
14051410
}
1411+
1412+
async groupsioConnectOrUpdate(integrationData: GroupsioIntegrationData) {
1413+
const transaction = await SequelizeRepository.createTransaction(this.options)
1414+
let integration
1415+
1416+
// integration data should have the following fields
1417+
// email, token, array of groups
1418+
// we shouldn't store password and 2FA token in the database
1419+
// user should update them every time thety change something
1420+
1421+
try {
1422+
this.options.log.info('Creating Groups.io integration!')
1423+
integration = await this.createOrUpdate(
1424+
{
1425+
platform: PlatformType.GROUPSIO,
1426+
settings: {
1427+
email: integrationData.email,
1428+
token: integrationData.token,
1429+
groups: integrationData.groupNames,
1430+
updateMemberAttributes: true,
1431+
},
1432+
status: 'in-progress',
1433+
},
1434+
transaction,
1435+
)
1436+
1437+
await SequelizeRepository.commitTransaction(transaction)
1438+
} catch (err) {
1439+
await SequelizeRepository.rollbackTransaction(transaction)
1440+
throw err
1441+
}
1442+
1443+
this.options.log.info(
1444+
{ tenantId: integration.tenantId },
1445+
'Sending Groups.io message to int-run-worker!',
1446+
)
1447+
const emitter = await getIntegrationRunWorkerEmitter()
1448+
await emitter.triggerIntegrationRun(
1449+
integration.tenantId,
1450+
integration.platform,
1451+
integration.id,
1452+
true,
1453+
)
1454+
1455+
return integration
1456+
}
1457+
1458+
async groupsioGetToken(data: GroupsioGetToken) {
1459+
const config: AxiosRequestConfig = {
1460+
method: 'post',
1461+
url: 'https://groups.io/api/v1/login',
1462+
params: {
1463+
email: data.email,
1464+
password: data.password,
1465+
twofactor: data.twoFactorCode,
1466+
},
1467+
headers: {
1468+
'Content-Type': 'application/json',
1469+
},
1470+
}
1471+
1472+
let response: AxiosResponse
1473+
1474+
try {
1475+
response = await axios(config)
1476+
1477+
// we need to get cookie from the response
1478+
1479+
const cookie = response.headers['set-cookie'][0].split(';')[0]
1480+
1481+
return {
1482+
groupsioCookie: cookie,
1483+
}
1484+
} catch (err) {
1485+
if ('two_factor_required' in response.data) {
1486+
throw new Error400(this.options.language, 'errors.groupsio.twoFactorRequired')
1487+
}
1488+
throw new Error400(this.options.language, 'errors.groupsio.invalidCredentials')
1489+
}
1490+
}
1491+
1492+
async groupsioVerifyGroup(data: GroupsioVerifyGroup) {
1493+
const groupName = data.groupName
1494+
1495+
const config: AxiosRequestConfig = {
1496+
method: 'post',
1497+
url: `https://groups.io/api/v1/gettopics?group_name=${encodeURIComponent(groupName)}`,
1498+
headers: {
1499+
'Content-Type': 'application/json',
1500+
Cookie: data.cookie,
1501+
},
1502+
}
1503+
1504+
let response: AxiosResponse
1505+
1506+
try {
1507+
response = await axios(config)
1508+
1509+
return {
1510+
group: response?.data?.data?.group_id,
1511+
}
1512+
} catch (err) {
1513+
throw new Error400(this.options.language, 'errors.groupsio.invalidGroup')
1514+
}
1515+
}
14061516
}

backend/src/types/webhooks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export enum WebhookType {
1010
GITHUB = 'GITHUB',
1111
DISCORD = 'DISCORD',
1212
DISCOURSE = 'DISCOURSE',
13+
GROUPSIO = 'GROUPSIO',
1314
}
1415

1516
export enum DiscordWebsocketEvent {

frontend/public/icons/crowd-icons.svg

Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)