Skip to content

Commit 9b3580e

Browse files
Merge upstream develop
2 parents f235f42 + 82104af commit 9b3580e

64 files changed

Lines changed: 5019 additions & 429 deletions

Some content is hidden

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

.gitlab-ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@ include:
1111
- local: '.gitlab/ci/deploy_services.yml'
1212
- local: '.gitlab/ci/deploy_pages.yml'
1313
- local: '.gitlab/ci/scan_dependencies.yml'
14+
- local: '.gitlab/ci/test_moderation.yml'
1415
- local: '.gitlab/ci/release.yml'
1516
stages:
1617
- setup
1718
- check
1819
- build
20+
- test
1921
- publish
2022
- deploy
2123
- release
22-
- test
2324
variables:
2425
CARGO_HOME: ${CI_PROJECT_DIR}/.cargo
2526
CARGO_TARGET_DIR: ${CI_PROJECT_DIR}/target

.gitlab/ci/build_js_sdks.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ js-sdks-format-check:
3030
- pnpm format
3131
allow_failure: true
3232

33+
js-core-tests-check:
34+
extends: .js-sdks-base
35+
stage: check
36+
allow_failure: true
37+
script:
38+
- pnpm --filter js-core test:run
39+
3340
js-sdks-build:
3441
extends: .js-sdks-base
3542
stage: build

.gitlab/ci/deploy_app.yml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,25 @@
1515
stage: deploy
1616
image:
1717
name: gcr.io/go-containerregistry/crane:debug
18-
entrypoint: ['']
18+
entrypoint: [""]
1919
script:
2020
- crane auth login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD
21-
- crane tag $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA $CI_ENVIRONMENT_NAME
21+
- ENVIRONMENT_TAG="${CI_ENVIRONMENT_NAME%%/*}"
22+
- crane tag $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA $ENVIRONMENT_TAG
2223
needs:
2324
- job: app-web-image
2425
artifacts: false
2526

2627
app-deploy-image-staging:
2728
extends: .app-deploy-image
2829
environment:
29-
name: staging
30+
name: staging/app
3031

3132
app-deploy-image-production:
3233
extends: .app-deploy-image
3334
when: manual
3435
environment:
35-
name: production
36+
name: production/app
3637

3738
###############################
3839
# Rolling restart on Manifest #
@@ -47,7 +48,7 @@ app-deploy-image-production:
4748
strategy: depend
4849
variables:
4950
MANIFEST_SETUP_COMMAND: quick
50-
MANIFEST_STAGGER_TIME: '10'
51+
MANIFEST_STAGGER_TIME: "10"
5152

5253
app-deploy-manifest-staging:
5354
extends: .app-deploy-manifest

.gitlab/ci/deploy_services.yml

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
entrypoint: ['']
66
script:
77
- crane auth login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD
8-
- crane tag $CI_REGISTRY_IMAGE/${SERVICE_NAME}:$CI_COMMIT_SHA $ENVIRONMENT
8+
- ENVIRONMENT_TAG="${CI_ENVIRONMENT_NAME%%/*}"
9+
- crane tag $CI_REGISTRY_IMAGE/${SERVICE_NAME}:$CI_COMMIT_SHA $ENVIRONMENT_TAG
910

1011
.service-deploy-manifest:
1112
stage: deploy
@@ -46,14 +47,14 @@
4647

4748
server-deploy-image-staging:
4849
extends: .server-deploy-image
49-
variables:
50-
ENVIRONMENT: staging
50+
environment:
51+
name: staging/server
5152

5253
server-deploy-image-production:
5354
extends: .server-deploy-image
5455
when: manual
55-
variables:
56-
ENVIRONMENT: production
56+
environment:
57+
name: production/server
5758

5859
###############################
5960
# Rolling restart on Manifest #
@@ -101,8 +102,8 @@ moderation-deploy-image-production:
101102
- .moderation-deploy-workflow
102103
- .service-deploy-image
103104
when: manual
104-
variables:
105-
ENVIRONMENT: production
105+
environment:
106+
name: production/moderation-service
106107
needs:
107108
- job: moderation-image
108109
artifacts: false
@@ -144,8 +145,8 @@ notifications-deploy-image-production:
144145
- .notifications-deploy-workflow
145146
- .service-deploy-image
146147
when: manual
147-
variables:
148-
ENVIRONMENT: production
148+
environment:
149+
name: production/notifications-service
149150
needs:
150151
- job: notifications-image
151152
artifacts: false
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Runs the server stack with Docker, then runs the moderation service
4+
# end-to-end CSAM test using cargo.
5+
#
6+
# Env:
7+
# KEEP_STACK=1 leave the Docker stack running on exit (default: tear down)
8+
set -euo pipefail
9+
10+
cd "$(dirname "$0")/../../.."
11+
12+
# A fixed project name keeps the network name (<project>_default) independent
13+
# of the checkout directory, for both the connect below and teardown.
14+
export COMPOSE_PROJECT_NAME=polycentric
15+
NETWORK="${COMPOSE_PROJECT_NAME}_default"
16+
17+
# Host:port the readiness probe waits on (and, locally, the test connects to).
18+
SERVER_HOST=localhost
19+
SERVER_PORT=3000
20+
21+
# The job's own container ID. GitLab sets the build container's hostname to a
22+
# short predefined name (runner-…-concurrent-N) that the daemon does not know
23+
# it by, so `docker network connect <hostname>` fails. Recover the real 64-hex
24+
# ID from the bind mounts Docker sets up for /etc/hostname, /etc/hosts, etc.
25+
# (sourced from /var/lib/docker/containers/<id>/…), falling back to hostname.
26+
self_container() {
27+
local id
28+
id=$(grep -oE 'containers/[0-9a-f]{64}' /proc/self/mountinfo | head -1 | cut -d/ -f2)
29+
echo "${id:-$(cat /etc/hostname)}"
30+
}
31+
32+
cleanup() {
33+
if [[ "${CI:-}" == "true" ]]; then
34+
docker network disconnect "$NETWORK" "$(self_container)" >/dev/null 2>&1 || true
35+
fi
36+
if [[ "${KEEP_STACK:-0}" != "1" ]]; then
37+
docker compose down -v >/dev/null 2>&1 || true
38+
fi
39+
}
40+
trap cleanup EXIT
41+
42+
echo "==> Bringing up the server stack…"
43+
docker compose up -d --build --wait postgres rustfs kafka server
44+
45+
if [[ "${CI:-}" == "true" ]]; then
46+
echo "==> Joining the job container to the stack network ($NETWORK)…"
47+
docker network connect "$NETWORK" "$(self_container)"
48+
# Reach the services by their in-network names instead of localhost. Kafka
49+
# is reached on its INTERNAL listener, which advertises kafka:19092 on this
50+
# network (the EXTERNAL listener advertises localhost:9092, for local use).
51+
SERVER_HOST=server
52+
export POLYCENTRIC_TEST_SERVER="http://server:3000"
53+
export POLYCENTRIC_TEST_DATABASE_URL="postgres://postgres:testing@postgres:5432"
54+
export POLYCENTRIC_TEST_OS_ENDPOINT="http://rustfs:9000"
55+
export POLYCENTRIC_TEST_KAFKA_BROKERS="kafka:19092"
56+
fi
57+
58+
echo "==> Waiting for the server gRPC port ($SERVER_HOST:$SERVER_PORT)…"
59+
for _ in $(seq 1 60); do
60+
if (exec 3<>"/dev/tcp/$SERVER_HOST/$SERVER_PORT") 2>/dev/null; then
61+
exec 3>&- 3<&-
62+
echo " server is accepting connections"
63+
break
64+
fi
65+
sleep 1
66+
done
67+
68+
echo "==> Applying server database migrations…"
69+
docker compose exec -T server /app/migration up
70+
71+
echo "==> Running the moderation CSAM pipeline test…"
72+
cargo test -p moderation-service --test csam_pipeline -- --ignored --nocapture

.gitlab/ci/setup_images.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
variables:
22
RUST_IMAGE: '${CI_REGISTRY}/${CI_PROJECT_PATH}:rust'
3+
RUST_DIND_IMAGE: '${CI_REGISTRY}/${CI_PROJECT_PATH}:rust-dind'
34

45
.setup-images-workflow:
56
stage: setup
@@ -32,3 +33,17 @@ setup-image-rust:
3233
--import-cache type=registry,ref=$RUST_IMAGE \
3334
--export-cache type=inline \
3435
--output type=image,name=$RUST_IMAGE,push=true
36+
37+
setup-image-rust-dind:
38+
extends: .setup-images-workflow
39+
needs: ['setup-image-rust']
40+
script:
41+
- |
42+
buildctl-daemonless.sh build \
43+
--frontend dockerfile.v0 \
44+
--opt build-arg:RUST_IMAGE=$RUST_IMAGE \
45+
--local context=.gitlab/images/rust-dind \
46+
--local dockerfile=.gitlab/images/rust-dind \
47+
--import-cache type=registry,ref=$RUST_DIND_IMAGE \
48+
--export-cache type=inline \
49+
--output type=image,name=$RUST_DIND_IMAGE,push=true

.gitlab/ci/test_moderation.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
########################################################################
2+
# moderation — integration tests
3+
#
4+
# End-to-end CSAM pipeline test verifying that the moderation service
5+
# purges and reports PhotoDNA CSAM matches. Uses a mock PhotoDNA
6+
# endpoint which always returns a match.
7+
########################################################################
8+
9+
.moderation-integration-workflow:
10+
image: $RUST_DIND_IMAGE
11+
stage: test
12+
rules:
13+
- if: $CI_COMMIT_TAG =~ /^moderation-/
14+
when: always
15+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
16+
changes:
17+
- services/moderation/**/*
18+
- services/server/**/*
19+
- compose.yml
20+
- .gitlab/ci/test_moderation.yml
21+
- .gitlab/ci/scripts/integration-moderation.sh
22+
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
23+
when: never
24+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
25+
changes:
26+
- services/moderation/**/*
27+
- services/server/**/*
28+
- compose.yml
29+
- .gitlab/ci/test_moderation.yml
30+
- .gitlab/ci/scripts/integration-moderation.sh
31+
variables:
32+
RUNNER_SCRIPT_TIMEOUT: 30m
33+
34+
moderation-integration-csam:
35+
extends: .moderation-integration-workflow
36+
script:
37+
- .gitlab/ci/scripts/integration-moderation.sh
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
ARG RUST_IMAGE
2+
3+
# Debian bookworm (the rust base) has no Compose v2 plugin in its repos, so
4+
# we copy the binaries from the official docker image, pinned by tag.
5+
FROM docker:28-cli AS docker
6+
FROM ${RUST_IMAGE}
7+
8+
COPY --from=docker /usr/local/bin/docker /usr/local/bin/docker
9+
COPY --from=docker /usr/local/libexec/docker/cli-plugins/docker-compose /usr/local/libexec/docker/cli-plugins/docker-compose
10+
11+
# Fail the build if the CLI can't find the compose plugin.
12+
RUN docker compose version
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as Linking from 'expo-linking';
2+
import * as Notifications from 'expo-notifications';
3+
import { router } from 'expo-router';
4+
import { useEffect } from 'react';
5+
6+
/**
7+
* Navigate in response to a tapped push notification. The notifications
8+
* service attaches a deep-link URL as `data.url`
9+
* (e.g. `polycentric:///{identity}/post/{keyFingerprint}/{sequence}` or
10+
* `polycentric:///{identity}`); we parse its path and route to it in-app.
11+
*/
12+
function navigateFromResponse(response: Notifications.NotificationResponse) {
13+
const data = response.notification.request.content.data;
14+
const url = typeof data?.url === 'string' ? data.url : undefined;
15+
if (!url) return;
16+
17+
// `polycentric:///id/post/fp/seq` -> path "id/post/fp/seq" -> route "/id/post/fp/seq".
18+
const { path } = Linking.parse(url);
19+
if (!path) return;
20+
21+
router.push(`/${path}` as never);
22+
}
23+
24+
/**
25+
* Wire up navigation for notification taps. Handles both a tap while the app
26+
* is running/backgrounded and a tap that cold-starts the app.
27+
*
28+
* `ready` must be `true` only once the router is mounted (i.e. the provider
29+
* has finished initializing and is rendering the navigation tree), otherwise
30+
* `router.push` has no navigator to act on and the navigation is silently lost.
31+
*/
32+
export function useNotificationNavigation(ready: boolean) {
33+
useEffect(() => {
34+
if (!ready) return;
35+
36+
let cancelled = false;
37+
38+
// Cold start: the app was launched by tapping a notification.
39+
Notifications.getLastNotificationResponseAsync().then((response) => {
40+
if (response && !cancelled) navigateFromResponse(response);
41+
});
42+
43+
// Warm: tapped while the app was already running or backgrounded.
44+
const subscription =
45+
Notifications.addNotificationResponseReceivedListener(
46+
navigateFromResponse,
47+
);
48+
49+
return () => {
50+
cancelled = true;
51+
subscription.remove();
52+
};
53+
}, [ready]);
54+
}

apps/polycentric/src/common/lib/polycentric-hooks/PolycentricProvider.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { Platform, Text, View } from 'react-native';
2020
import { Atoms, useTheme } from '../../theme';
2121
import { registerForPushNotifications } from '../notifications/registerPushToken';
22+
import { useNotificationNavigation } from '../notifications/useNotificationNavigation';
2223
import {
2324
createPolycentricStore,
2425
useStore,
@@ -138,6 +139,10 @@ export function PolycentricProvider({
138139
const [isLoading, setIsLoading] = useState(true);
139140
const [error, setError] = useState<Error | null>(null);
140141

142+
// Route to the deep link carried by a tapped push notification, once the
143+
// router is mounted (provider initialized without error).
144+
useNotificationNavigation(!isLoading && !error);
145+
141146
useEffect(() => {
142147
if (!isLoading) {
143148
onInitialized?.();
@@ -175,8 +180,6 @@ export function PolycentricProvider({
175180

176181
if (cancelled) return;
177182

178-
c.servers = [DEFAULT_SERVER];
179-
180183
const s = createPolycentricStore(c);
181184
await s.getState().refreshIdentities();
182185

0 commit comments

Comments
 (0)