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
180 changes: 180 additions & 0 deletions .github/scripts/repair-moderation-policy.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
const { StreamClient } = require('@stream-io/node-sdk');

const templateNames = [
'moderation_template_activity',
'moderation_template_reaction',
];
const blockListName = 'stream_java_moderation_tests';
const triggerWord = 'pissoar';
const rule = { name: blockListName, action: 'remove' };
const unavailableBlockListNames = new Set(['profanity_en']);
const propagationRetryDelaysMs = [1000, 2000, 4000, 8000];

function sleep(delayMs) {
return new Promise((resolve) => setTimeout(resolve, delayMs));
}

async function retryAfterBlockListPropagation(operation) {
for (const delayMs of propagationRetryDelaysMs) {
try {
return await operation();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (!message.includes(`Blocklist not found: ${blockListName}`)) {
throw error;
}

console.log(`Waiting ${delayMs}ms for ${blockListName} to propagate`);
await sleep(delayMs);
}
}

return operation();
}

async function ensureTestBlockList(client) {
const response = await client.listBlockLists();
const blockList = response.blocklists.find(
(candidate) => candidate.name === blockListName,
);

if (!blockList) {
await client.createBlockList({
name: blockListName,
type: 'word',
words: [triggerWord],
});
console.log(`Created test blocklist ${blockListName}`);
return;
}

if (!blockList.words.includes(triggerWord)) {
await client.updateBlockList({
name: blockListName,
words: [...blockList.words, triggerWord],
});
console.log(`Updated test blocklist ${blockListName}`);
return;
}

console.log(`Test blocklist ${blockListName} already exists`);
}

function withRequiredRule(blockListConfig = {}) {
const rules = (blockListConfig.rules || []).filter(
(candidate) => !unavailableBlockListNames.has(candidate.name),
);
const index = rules.findIndex((candidate) => candidate.name === rule.name);

if (index === -1) {
rules.push(rule);
} else {
rules[index] = { ...rules[index], action: rule.action };
}

return { ...blockListConfig, rules };
}

function hasRequiredRule(blockListConfig) {
return blockListConfig?.rules?.some(
(candidate) =>
candidate.name === rule.name && candidate.action === rule.action,
);
}

async function repairPolicy(client, key) {
const response = await client.moderation.getConfig({ key });
const config = response.config;
if (!config) {
throw new Error(`Moderation policy ${key} was not found`);
}
if (hasRequiredRule(config.block_list_config)) {
console.log(`Moderation policy ${key} already contains the required rule`);
return;
}

const writableFields = [
'async',
'team',
'ai_audio_config',
'ai_image_config',
'ai_text_config',
'ai_video_config',
'automod_platform_circumvention_config',
'automod_toxicity_config',
'aws_rekognition_config',
'block_list_config',
'bodyguard_config',
'flood_config',
'google_vision_config',
'llm_config',
'rule_builder_config',
'velocity_filter_config',
'video_call_rule_config',
];
const payload = { key };
for (const field of writableFields) {
if (config[field] !== undefined) {
payload[field] = config[field];
}
}
payload.block_list_config = withRequiredRule(config.block_list_config);

await retryAfterBlockListPropagation(() =>
client.moderation.upsertConfig(payload),
);
console.log(`Repaired moderation policy ${key}`);
}

async function main() {
if (!process.env.STREAM_KEY || !process.env.STREAM_SECRET) {
throw new Error('STREAM_KEY and STREAM_SECRET are required');
}

const client = new StreamClient(
process.env.STREAM_KEY,
process.env.STREAM_SECRET,
);
await ensureTestBlockList(client);
const response = await client.moderation.v2QueryTemplates();
const templates = new Map(
response.templates.map((template) => [template.name, template]),
);
const repairedPolicies = new Set();

for (const name of templateNames) {
const template = templates.get(name);
if (!template?.config) {
throw new Error(`Moderation template ${name} was not found`);
}

if (template.config.config_key) {
if (!repairedPolicies.has(template.config.config_key)) {
await repairPolicy(client, template.config.config_key);
repairedPolicies.add(template.config.config_key);
}
continue;
}

if (hasRequiredRule(template.config.block_list_config)) {
console.log(`Moderation template ${name} already contains the required rule`);
continue;
}

await retryAfterBlockListPropagation(() =>
client.moderation.v2UpsertTemplate({
name,
config: {
...template.config,
block_list_config: withRequiredRule(template.config.block_list_config),
},
}),
);
console.log(`Repaired moderation template ${name}`);
}
}

main().catch((error) => {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
});
24 changes: 20 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,47 @@ concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true

permissions:
contents: read
pull-requests: read

jobs:
build:
name: 🧪 Test & lint
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-java@v3
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'

- name: Commit message lint
uses: wagoid/commitlint-github-action@v4
uses: wagoid/commitlint-github-action@v6

- name: Restore cache
uses: actions/cache@v3
uses: actions/cache@v5
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*') }}
restore-keys: |
${{ runner.os }}-gradle-

- uses: actions/setup-node@v6
with:
node-version: '24'

- name: Repair moderation test policy
env:
STREAM_KEY: ${{ secrets.STREAM_KEY }}
STREAM_SECRET: ${{ secrets.STREAM_SECRET }}
run: |
npm install --prefix "$RUNNER_TEMP/moderation-repair" --no-save --package-lock=false --ignore-scripts @stream-io/node-sdk@0.8.3
NODE_PATH="$RUNNER_TEMP/moderation-repair/node_modules" node .github/scripts/repair-moderation-policy.cjs

- name: Test
env:
STREAM_KEY: ${{ secrets.STREAM_KEY }}
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/initiate_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ on:
description: "The new version number with a 'v' prefix. Example: v1.40.1"
required: true

permissions:
contents: write
pull-requests: write

jobs:
init_release:
name: 🚀 Create release PR
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
with:
fetch-depth: 0 # gives the changelog generator access to all previous commits
- uses: actions/setup-java@v3
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
Expand All @@ -32,7 +36,7 @@ jobs:
git push -q -u origin "release-$VERSION"

- name: Get changelog diff
uses: actions/github-script@v5
uses: actions/github-script@v9
with:
script: |
const get_change_log_diff = require('./scripts/get_changelog_diff.js')
Expand Down
24 changes: 14 additions & 10 deletions .github/workflows/javadoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,31 @@ on:
push:
branches:
- main

permissions:
contents: write

jobs:
javadoc:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
- name: Set up Node.js 16
uses: actions/setup-node@v2
uses: actions/setup-node@v6
with:
node-version: 16
- name: Generate doc
run: ./gradlew --no-daemon javadoc
- name: Deploy
uses: JamesIves/github-pages-deploy-action@releases/v3
uses: JamesIves/github-pages-deploy-action@v4
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages
FOLDER: build/docs/javadoc/
token: ${{ secrets.GITHUB_TOKEN }}
branch: gh-pages
folder: build/docs/javadoc/
9 changes: 6 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@ on:
branches:
- main

permissions:
contents: write

jobs:
Release:
name: 🚀 Release
if: github.event.pull_request.merged && startsWith(github.head_ref, 'release-')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-java@v3
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
- uses: actions/github-script@v5
- uses: actions/github-script@v9
with:
script: |
const get_change_log_diff = require('./scripts/get_changelog_diff.js')
Expand Down