feat(threads): ship threads and the Plutonium page to everyone (#3277)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-08 16:30:39 +02:00
1 parent 1515a8487f
commit f8505d19f7
53 files changed
+296 -2008

No files matched your search

@@ -1,30 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {ChannelThreadsConfig} from '@fluxer/schema/src/domains/admin/ChannelThreadsSchemas';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
const textEncoder = new TextEncoder();
export const CHANNEL_THREADS_CONFIG_NATS_SUBJECT = 'config.channel.threads';
interface ChannelThreadsConfigNatsMessage {
type: 'channel_threads_config';
config: ChannelThreadsConfig;
}
export class ChannelThreadsConfigPublisher {
constructor(private readonly connectionManager: INatsConnectionManager) {}
async publish(config: ChannelThreadsConfig): Promise<void> {
if (this.connectionManager.isClosed()) {
await this.connectionManager.connect();
}
const connection = this.connectionManager.getConnection();
const message: ChannelThreadsConfigNatsMessage = {
type: 'channel_threads_config',
config,
};
connection.publish(CHANNEL_THREADS_CONFIG_NATS_SUBJECT, textEncoder.encode(JSON.stringify(message)));
await connection.flush();
}
}
@@ -18,6 +18,10 @@ import {InstanceConfigWriteRaceExecutor} from '@app/api/instance/tests/InstanceC
import {startDockerContainer} from '@app/api/test/DockerTestContainer';
import {InMemoryCassandraQueryExecutor} from '@app/api/test/InMemoryCassandraQueryExecutor';
import {MockKVProvider} from '@app/api/test/mocks/MockKVProvider';
import {
DEFAULT_CHANNEL_THREADS_CONFIG,
everyoneChannelThreadsConfig,
} from '@fluxer/schema/src/domains/admin/ChannelThreadsSchemas';
import {
DEFAULT_DOMAIN_MIGRATION_CONFIG,
type DomainMigrationConfig,
@@ -35,6 +39,7 @@ import {
import {afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from 'vitest';
const DOMAIN_MIGRATION_CONFIG_KEY = 'domain_migration_config';
const CHANNEL_THREADS_CONFIG_KEY = 'channel_threads_config';
const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config';
const APP_PUBLIC_CONFIG_KEY = 'app_public_config';
const INSTANCE_POLICY_CONFIG_KEY = 'instance_policy_config';
@@ -301,6 +306,37 @@ describe('InstanceConfigRepository', () => {
expect(domains).not.toContain('example.com');
});
it('serves the everyone channel threads config at version zero when the key is absent', async () => {
setCassandraQueryExecutorForTesting(new CountingInMemoryCassandraQueryExecutor());
const repository = createRepository(new MockKVProvider());
await expect(repository.getChannelThreadsConfig()).resolves.toEqual(everyoneChannelThreadsConfig(0));
});
it.each([
{
name: 'a disabled row',
stored: JSON.stringify({
...DEFAULT_CHANNEL_THREADS_CONFIG,
enabled: false,
config_version: 9,
disabled_guild_ids: ['1400000000000000001'],
excluded_user_ids: ['1400000000000000002'],
}),
version: 9,
},
{name: 'a partial rollout row', stored: '{"enabled":true,"config_version":4,"guild_basis_points":100}', version: 4},
{name: 'a row with an invalid version', stored: '{"enabled":false,"config_version":-1}', version: 0},
{name: 'unparseable text', stored: 'not-json', version: 0},
])('serves the everyone channel threads config for $name and keeps the stored version', async ({stored, version}) => {
setCassandraQueryExecutorForTesting(new CountingInMemoryCassandraQueryExecutor());
const repository = createRepository(new MockKVProvider());
await repository.setConfig(CHANNEL_THREADS_CONFIG_KEY, stored);
await expect(repository.getChannelThreadsConfig()).resolves.toEqual(everyoneChannelThreadsConfig(version));
});
it('returns the default domain migration config when the key is absent', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
@@ -53,8 +53,8 @@ import {
} from '@fluxer/schema/src/domains/admin/CaptchaSchemas';
import {
type ChannelThreadsConfig,
ChannelThreadsConfigSchema,
type CompiledChannelThreadsConfig,
everyoneChannelThreadsConfig,
} from '@fluxer/schema/src/domains/admin/ChannelThreadsSchemas';
import {
type DomainMigrationConfig,
@@ -69,10 +69,6 @@ import {
type StoredBillingConfig,
StoredBillingConfigSchema,
} from '@fluxer/schema/src/domains/admin/InstanceBillingSchemas';
import {
type PlutoniumPageConfig,
PlutoniumPageConfigSchema,
} from '@fluxer/schema/src/domains/admin/PlutoniumPageSchemas';
import {
type LegacyPushServiceDeliveryWire,
type PushRelayConfig,
@@ -100,7 +96,6 @@ import {z} from 'zod';
const GATEWAY_ROLLOUT_CONFIG_KEY = 'gateway_rollout_config';
const PUSH_RELAY_CONFIG_KEY = 'push_service_delivery_config';
const DOMAIN_MIGRATION_CONFIG_KEY = 'domain_migration_config';
const PLUTONIUM_PAGE_CONFIG_KEY = 'plutonium_page_config';
const CAPTCHA_CONFIG_KEY = 'captcha_config';
const CHANNEL_THREADS_CONFIG_KEY = 'channel_threads_config';
const EXPERIMENT_DELIVERY_CONFIG_KEY = 'experiment_delivery_config';
@@ -433,7 +428,6 @@ type StoredConfigSection =
| 'gateway rollout'
| 'push relay'
| 'domain migration'
| 'plutonium page'
| 'captcha'
| 'channel threads'
| 'experiment delivery'
@@ -653,16 +647,15 @@ function parseStoredDomainMigrationConfig(raw: string | null): DomainMigrationCo
return parseStoredConfigOrDefault(DomainMigrationConfigSchema, raw, 'domain migration');
}
function parseStoredPlutoniumPageConfig(raw: string | null): PlutoniumPageConfig {
return parseStoredConfigOrDefault(PlutoniumPageConfigSchema, raw, 'plutonium page');
}
function parseStoredCaptchaConfig(raw: string | null): CaptchaConfig {
return parseStoredConfigOrDefault(CaptchaConfigSchema, raw, 'captcha');
}
const StoredChannelThreadsVersionSchema = z.object({config_version: z.number().int().min(0)});
function parseStoredChannelThreadsConfig(raw: string | null): ChannelThreadsConfig {
return parseStoredConfigOrDefault(ChannelThreadsConfigSchema, raw, 'channel threads');
const stored = StoredChannelThreadsVersionSchema.safeParse(readStoredConfigValue(raw, 'channel threads'));
return everyoneChannelThreadsConfig(stored.success ? stored.data.config_version : 0);
}
function parseStoredExperimentDeliveryConfig(raw: string | null): ExperimentDeliveryConfig {
@@ -1327,7 +1320,6 @@ export class InstanceConfigRepository {
);
parseStoredPushRelayConfig(snapshot.get(PUSH_RELAY_CONFIG_KEY) ?? null);
parseStoredDomainMigrationConfig(snapshot.get(DOMAIN_MIGRATION_CONFIG_KEY) ?? null);
parseStoredPlutoniumPageConfig(snapshot.get(PLUTONIUM_PAGE_CONFIG_KEY) ?? null);
parseStoredCaptchaConfig(snapshot.get(CAPTCHA_CONFIG_KEY) ?? null);
syncChannelThreadsConfig(snapshot.get(CHANNEL_THREADS_CONFIG_KEY) ?? null, parseStoredChannelThreadsConfig);
parseStoredExperimentDeliveryConfig(snapshot.get(EXPERIMENT_DELIVERY_CONFIG_KEY) ?? null);
@@ -1571,23 +1563,6 @@ export class InstanceConfigRepository {
);
}
async getPlutoniumPageConfig(): Promise<PlutoniumPageConfig> {
const raw = await this.getConfig(PLUTONIUM_PAGE_CONFIG_KEY);
return parseStoredPlutoniumPageConfig(raw);
}
async setPlutoniumPageConfig(config: PlutoniumPageConfig): Promise<void> {
await this.updatePlutoniumPageConfig(() => config);
}
updatePlutoniumPageConfig(
update: (current: PlutoniumPageConfig) => PlutoniumPageConfig,
): Promise<PlutoniumPageConfig> {
return this.updateStoredConfig(PLUTONIUM_PAGE_CONFIG_KEY, (raw) =>
validateStoredConfig(PlutoniumPageConfigSchema, update(parseStoredPlutoniumPageConfig(raw)), 'plutonium page'),
);
}
async getCaptchaConfig(): Promise<CaptchaConfig> {
const raw = await this.getConfig(CAPTCHA_CONFIG_KEY);
return parseStoredCaptchaConfig(raw);
@@ -1613,16 +1588,6 @@ export class InstanceConfigRepository {
return syncChannelThreadsConfig(raw, parseStoredChannelThreadsConfig);
}
async updateChannelThreadsConfig(
update: (current: ChannelThreadsConfig) => ChannelThreadsConfig,
): Promise<ChannelThreadsConfig> {
const landed = await this.updateStoredConfig(CHANNEL_THREADS_CONFIG_KEY, (raw) =>
validateStoredConfig(ChannelThreadsConfigSchema, update(parseStoredChannelThreadsConfig(raw)), 'channel threads'),
);
syncChannelThreadsConfig(JSON.stringify(landed), parseStoredChannelThreadsConfig);
return landed;
}
async getExperimentDeliveryConfig(): Promise<ExperimentDeliveryConfig> {
const raw = await this.getConfig(EXPERIMENT_DELIVERY_CONFIG_KEY);
return parseStoredExperimentDeliveryConfig(raw);