Skip to content

Commit f41380e

Browse files
authored
Merge branch 'main' into chore/update-pvtr-version
2 parents 6789e92 + 485a47b commit f41380e

16 files changed

Lines changed: 391 additions & 57 deletions

File tree

backend/.env.dist.local

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ CROWD_TEMPORAL_ENCRYPTION_KEY_ID=local
151151
CROWD_TEMPORAL_ENCRYPTION_KEY=FweBMRnGCLshER8FlSvNusQA6G3MRUKt
152152

153153
# Temporal — packages namespace
154+
CROWD_PACKAGES_TEMPORAL_SERVER_URL=localhost:7233
154155
CROWD_PACKAGES_TEMPORAL_NAMESPACE=default
155156

156157
# Seach sync api

backend/config/custom-environment-variables.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@
180180
"certificate": "CROWD_TEMPORAL_CERTIFICATE",
181181
"privateKey": "CROWD_TEMPORAL_PRIVATE_KEY"
182182
},
183+
"packagesTemporal": {
184+
"serverUrl": "CROWD_PACKAGES_TEMPORAL_SERVER_URL",
185+
"namespace": "CROWD_PACKAGES_TEMPORAL_NAMESPACE",
186+
"certificate": "CROWD_TEMPORAL_CERTIFICATE",
187+
"privateKey": "CROWD_TEMPORAL_PRIVATE_KEY"
188+
},
183189
"searchSyncApi": {
184190
"baseUrl": "CROWD_SEARCH_SYNC_API_URL"
185191
},

backend/config/default.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"isEnabled": "false"
4444
},
4545
"temporal": {},
46+
"packagesTemporal": {},
4647
"searchSyncApi": {},
4748
"encryption": {},
4849
"openStatusApi": {},

backend/src/conf/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ export const PACKAGES_DB_CONFIG: IDatabaseConfig | undefined = config.has('packa
8686
? config.get<IDatabaseConfig>('packagesDb')
8787
: undefined
8888

89+
// packages_worker (npm/maven/pypi/osv/security-contacts/...) runs in its own Temporal
90+
// namespace, separate from the API's default namespace — see CROWD_PACKAGES_TEMPORAL_NAMESPACE.
91+
export const PACKAGES_TEMPORAL_CONFIG: ITemporalConfig | undefined = config.has(
92+
'packagesTemporal.namespace',
93+
)
94+
? config.get<ITemporalConfig>('packagesTemporal')
95+
: undefined
96+
8997
export const SEGMENT_CONFIG: SegmentConfiguration = config.get<SegmentConfiguration>('segment')
9098

9199
export const COMPREHEND_CONFIG: ComprehendConfiguration =

backend/src/db/packagesTemporal.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { IS_DEV_ENV, SERVICE } from '@crowd/common'
2+
import { Client, Connection, getDataConverter } from '@crowd/temporal'
3+
4+
import { PACKAGES_TEMPORAL_CONFIG } from '@/conf'
5+
6+
let _init: Promise<Client> | undefined
7+
8+
// Separate connection from the API's default req.temporal client — packages_worker
9+
// (npm/maven/pypi/osv/security-contacts/...) polls task queues in its own Temporal
10+
// namespace (CROWD_PACKAGES_TEMPORAL_NAMESPACE), not the API's default namespace.
11+
export function getPackagesTemporalClient(): Promise<Client> {
12+
if (!_init) {
13+
if (!PACKAGES_TEMPORAL_CONFIG?.serverUrl) {
14+
throw new Error('Packages Temporal is not configured — set CROWD_PACKAGES_TEMPORAL_NAMESPACE')
15+
}
16+
17+
const cfg = PACKAGES_TEMPORAL_CONFIG
18+
_init = Connection.connect({
19+
address: cfg.serverUrl,
20+
tls:
21+
cfg.certificate && cfg.privateKey
22+
? {
23+
clientCertPair: {
24+
crt: Buffer.from(cfg.certificate, 'base64'),
25+
key: Buffer.from(cfg.privateKey, 'base64'),
26+
},
27+
}
28+
: undefined,
29+
})
30+
.then(
31+
async (connection) =>
32+
new Client({
33+
connection,
34+
namespace: cfg.namespace,
35+
identity: SERVICE,
36+
dataConverter: IS_DEV_ENV ? undefined : await getDataConverter(),
37+
}),
38+
)
39+
.catch((err) => {
40+
_init = undefined
41+
throw err
42+
})
43+
}
44+
return _init
45+
}

scripts/services/security-contacts-worker.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ x-env-args: &env-args
77
SHELL: /bin/sh
88
SUPPRESS_NO_CONFIG_WARNING: 'true'
99
CROWD_TEMPORAL_TASKQUEUE: packages-worker
10+
CROWD_TEMPORAL_NAMESPACE: ${CROWD_PACKAGES_TEMPORAL_NAMESPACE}
1011

1112
services:
1213
security-contacts-worker:

services/apps/git_integration/src/crowdgit/database/crud.py

Lines changed: 90 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,20 @@ async def find_many_organization_ids_by_identities(identities: list[dict]) -> li
720720
return results
721721

722722

723+
async def fetch_organizations(org_ids: list[str]) -> list[dict]:
724+
if not org_ids:
725+
return []
726+
727+
return await query(
728+
"""
729+
SELECT id, "isAffiliationBlocked"
730+
FROM organizations
731+
WHERE id = ANY($1::uuid[])
732+
""",
733+
(org_ids,),
734+
)
735+
736+
723737
async def fetch_member_organizations(member_ids: list[str]) -> list[dict]:
724738
if not member_ids:
725739
return []
@@ -751,9 +765,9 @@ async def fetch_segment_affiliations(member_ids: list[str], segment_id: str) ->
751765
)
752766

753767

754-
async def insert_member_organizations(rows: list[dict]) -> None:
768+
async def insert_member_organizations(rows: list[dict]) -> list[dict]:
755769
if not rows:
756-
return
770+
return []
757771

758772
undated_rows: list[tuple] = []
759773
open_ended_rows: list[tuple] = []
@@ -767,8 +781,10 @@ async def insert_member_organizations(rows: list[dict]) -> None:
767781
row.get("date_end"),
768782
row["source"],
769783
)
784+
770785
date_start = row.get("date_start")
771786
date_end = row.get("date_end")
787+
772788
if date_start is None and date_end is None:
773789
undated_rows.append(params)
774790
elif date_end is None:
@@ -787,41 +803,91 @@ async def insert_member_organizations(rows: list[dict]) -> None:
787803
"createdAt",
788804
"updatedAt"
789805
)
790-
VALUES ($1, $2, $3, $4, NULL, $5, NOW(), NOW())
791806
"""
792807

793-
if undated_rows:
794-
sql = (
795-
insert_sql
796-
+ """
808+
returning_sql = """
809+
RETURNING id, "memberId", "organizationId"
810+
"""
811+
812+
buckets = [
813+
(
814+
undated_rows,
815+
"""
797816
ON CONFLICT ("memberId", "organizationId")
798817
WHERE ("dateStart" IS NULL AND "dateEnd" IS NULL AND "deletedAt" IS NULL)
799818
DO NOTHING
800-
"""
801-
)
802-
await executemany(sql, undated_rows)
803-
804-
if open_ended_rows:
805-
sql = (
806-
insert_sql
807-
+ """
819+
""",
820+
),
821+
(
822+
open_ended_rows,
823+
"""
808824
ON CONFLICT ("memberId", "organizationId", "dateStart")
809825
WHERE ("dateEnd" IS NULL AND "deletedAt" IS NULL)
810826
DO NOTHING
811-
"""
812-
)
813-
await executemany(sql, open_ended_rows)
814-
815-
if dated_rows:
816-
sql = (
817-
insert_sql
818-
+ """
827+
""",
828+
),
829+
(
830+
dated_rows,
831+
"""
819832
ON CONFLICT ("memberId", "organizationId", "dateStart", "dateEnd")
820833
WHERE ("deletedAt" IS NULL)
821834
DO NOTHING
835+
""",
836+
),
837+
]
838+
839+
created_rows: list[dict] = []
840+
841+
for bucket_rows, conflict_sql in buckets:
842+
if not bucket_rows:
843+
continue
844+
845+
values_parts: list[str] = []
846+
params: list = []
847+
param_index = 1
848+
849+
for member_id, organization_id, date_start, date_end, source in bucket_rows:
850+
values_parts.append(
851+
f"(${param_index}, ${param_index + 1}, ${param_index + 2}, "
852+
f"${param_index + 3}, NULL, ${param_index + 4}, NOW(), NOW())"
853+
)
854+
params.extend([member_id, organization_id, date_start, date_end, source])
855+
param_index += 5
856+
857+
created_rows.extend(
858+
await query(
859+
insert_sql + f" VALUES {', '.join(values_parts)}" + conflict_sql + returning_sql,
860+
tuple(params),
861+
)
862+
)
863+
864+
return created_rows
865+
866+
867+
async def insert_member_organization_affiliation_overrides(rows: list[dict]) -> None:
868+
if not rows:
869+
return
870+
871+
await executemany(
822872
"""
873+
INSERT INTO "memberOrganizationAffiliationOverrides"(
874+
id,
875+
"memberId",
876+
"memberOrganizationId",
877+
"allowAffiliation"
823878
)
824-
await executemany(sql, dated_rows)
879+
VALUES (gen_random_uuid(), $1, $2, $3)
880+
ON CONFLICT ("memberId", "memberOrganizationId") DO NOTHING
881+
""",
882+
[
883+
(
884+
row["member_id"],
885+
row["member_organization_id"],
886+
row["allow_affiliation"],
887+
)
888+
for row in rows
889+
],
890+
)
825891

826892

827893
async def insert_member_segment_affiliations(rows: list[dict]) -> None:

0 commit comments

Comments
 (0)