refactor(api): tidy the api and shared packages (#2733)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-13 17:37:48 +02:00
1 parent 580401d2dc
commit 5afbf67a70
333 files changed
+10180 -7910

No files matched your search

@@ -0,0 +1,114 @@
import {randomUUID} from 'node:crypto';
import type {IKVProvider} from '@pkgs/kv_client/src/IKVProvider';
import {Logger} from '../Logger';
import {parseJsonRecord} from '../utils/JsonBoundaryUtils';
import {RefreshSubscription, RefreshSubscriptionUnavailableError} from '../utils/RefreshSubscription';
interface InstanceConfigCacheOptions {
provider: IKVProvider | null;
channel: string;
load: () => Promise<Map<string, string>>;
onRefresh: (snapshot: ReadonlyMap<string, string>) => void;
}
export class InstanceConfigCache {
readonly sourceId = randomUUID();
private active = true;
private snapshot: Map<string, string> | null = null;
private writesDuringRefresh: Map<string, string> | null = null;
private initialization: Promise<boolean> | null = null;
private shutdownCompletion: Promise<void> | null = null;
private readonly subscription: RefreshSubscription;
constructor(
private readonly options: InstanceConfigCacheOptions,
private readonly previousShutdown: Promise<void> = Promise.resolve(),
) {
this.subscription = new RefreshSubscription({
name: 'InstanceConfigCache',
channels: [options.channel],
shouldRefresh: (_channel, message) => parseJsonRecord(message)?.source_id !== this.sourceId,
drainInitialNotifications: true,
refresh: () => this.refresh(),
onRefreshError: (error) => {
Logger.error({error}, 'Failed to refresh instance config cache from pubsub');
},
});
}
assertActive(): void {
if (!this.active) throw new Error('Instance config cache is shutting down or shut down');
}
async getSnapshot(): Promise<ReadonlyMap<string, string> | null> {
this.assertActive();
if (!this.subscription.isReady) {
this.initialization ??= this.initialize().finally(() => {
this.initialization = null;
});
const cached = await this.initialization;
this.assertActive();
if (!cached) return null;
}
if (this.snapshot === null) throw new Error('Instance config cache is ready without a snapshot');
return this.snapshot;
}
private async initialize(): Promise<boolean> {
await this.previousShutdown;
this.assertActive();
if (this.options.provider === null) return false;
try {
await this.subscription.start(this.options.provider);
return true;
} catch (error) {
this.assertActive();
if (!(error instanceof RefreshSubscriptionUnavailableError)) throw error;
Logger.error({error}, 'Instance config subscription unavailable, reading directly from the database');
return false;
}
}
private async refresh(): Promise<void> {
this.assertActive();
if (this.writesDuringRefresh !== null) throw new Error('Instance config refresh already in progress');
const writes = new Map<string, string>();
this.writesDuringRefresh = writes;
try {
const snapshot = await this.options.load();
if (!this.active) return;
for (const [key, value] of writes) snapshot.set(key, value);
this.options.onRefresh(snapshot);
this.snapshot = snapshot;
} finally {
this.writesDuringRefresh = null;
}
}
update(key: string, value: string): void {
if (!this.active) return;
this.snapshot?.set(key, value);
this.writesDuringRefresh?.set(key, value);
}
shutdown(): Promise<void> {
if (this.shutdownCompletion) return this.shutdownCompletion;
this.active = false;
this.snapshot = null;
this.shutdownCompletion = this.close();
return this.shutdownCompletion;
}
private async close(): Promise<void> {
const [subscription, previous] = await Promise.allSettled([
this.subscription.stop(),
this.previousShutdown,
this.initialization,
]);
const errors: Array<unknown> = [subscription, previous].flatMap((result) =>
result.status === 'rejected' ? [result.reason] : [],
);
if (errors.length === 1) throw errors[0];
if (errors.length > 1) throw new AggregateError(errors, 'Failed to shut down instance config cache');
}
}
@@ -21,6 +21,9 @@ import {
const VOICE_NOISE_SUPPRESSION_CONFIG_KEY = 'voice_noise_suppression_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';
const INSTANCE_INTEGRATIONS_CONFIG_KEY = 'instance_integrations_config';
class CountingInMemoryCassandraQueryExecutor extends InMemoryCassandraQueryExecutor {
instanceConfigSelects = 0;
@@ -158,6 +161,106 @@ describe('InstanceConfigRepository', () => {
});
});
it('keeps the stored setup state when a branding field is invalid', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await repository.setConfig(
APP_PUBLIC_CONFIG_KEY,
JSON.stringify({branding: {product_name: 'Kept', icon_url: 42}, setup: {configured: false}}),
);
const config = await repository.getAppPublicConfig();
expect(config.setup.configured).toBe(false);
expect(config.branding.product_name).toBe('Kept');
});
it('keeps valid stored instance policy flags when one field is invalid', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await repository.setConfig(
INSTANCE_POLICY_CONFIG_KEY,
JSON.stringify({direct_messages_disabled: true, single_community_enabled: true, premium_mode: 'nonsense'}),
);
const policy = await repository.getInstancePolicyConfig();
expect(policy.direct_messages_disabled).toBe(true);
expect(policy.single_community_enabled).toBe(true);
expect(policy.premium_mode).toBe('everyone');
});
it('keeps valid stored integration settings when one field is invalid', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await repository.setConfig(
INSTANCE_INTEGRATIONS_CONFIG_KEY,
JSON.stringify({bluesky: {client_name: 'Kept Instance', enabled: 'yes'}}),
);
expect((await repository.getEffectiveBlueskyConfig()).client_name).toBe('Kept Instance');
expect((await repository.getInstanceIntegrationsConfig()).bluesky.enabled).toBeNull();
});
it('drops invalid stored SSO allowed domains and keeps the valid ones', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await repository.setConfig('sso_allowed_domains', JSON.stringify(['example.com', '[email protected]', 'Kept.ORG']));
expect((await repository.getSsoConfig()).allowedEmailDomains).toEqual(['example.com', 'kept.org']);
});
it('falls back to the default SSO flags when a stored flag is not a boolean', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await repository.setConfig('sso_enabled', 'yes');
await repository.setConfig('sso_enforced', 'sometimes');
await repository.setConfig('sso_auto_provision', 'maybe');
const config = await repository.getSsoConfig();
expect(config.enabled).toBe(false);
expect(config.enforced).toBe(false);
expect(config.autoProvision).toBe(true);
});
it('clears an invalid allowed domain list only while SSO is disabled', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
const cleared = await repository.setSsoConfig({enabled: false, allowedEmailDomains: ['[email protected]']});
expect(cleared.allowedEmailDomains).toEqual([]);
await expect(repository.setSsoConfig({enabled: true, allowedEmailDomains: ['[email protected]']})).rejects.toThrow();
});
it('keeps an all-invalid stored allowed domain list non-empty so SSO still rejects every domain', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
const kvProvider = new MockKVProvider();
const repository = createRepository(kvProvider);
await repository.setConfig('sso_allowed_domains', JSON.stringify(['[email protected]', 'bad/domain']));
const domains = (await repository.getSsoConfig()).allowedEmailDomains;
expect(domains.length).toBeGreaterThan(0);
expect(domains).not.toContain('example.com');
});
it('returns the default voice noise suppression config when the key is absent', async () => {
const executor = new CountingInMemoryCassandraQueryExecutor();
setCassandraQueryExecutorForTesting(executor);
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {JoinSourceTypes} from '@fluxer/constants/src/GuildConstants';
import {UnknownGuildError} from '@fluxer/errors/src/domains/guild/UnknownGuildError';
import {createGuildID, type GuildID, type UserID} from '../BrandedTypes';
import type {GuildDataService} from '../guild/services/GuildDataService';
import type {GuildMemberService} from '../guild/services/GuildMemberService';
@@ -18,19 +19,15 @@ export class SingleCommunityService {
async getStockCommunityId(): Promise<GuildID | null> {
const policy = await this.instanceConfigRepository.getInstancePolicyConfig();
if (!policy.single_community_enabled || !policy.single_community_guild_id) {
return null;
}
try {
return createGuildID(BigInt(policy.single_community_guild_id));
} catch {
if (!policy.single_community_enabled || policy.single_community_guild_id === null) {
return null;
}
return createGuildID(BigInt(policy.single_community_guild_id));
}
async joinStockCommunity(userId: UserID, requestCache: RequestCache): Promise<void> {
const guildId = await this.getStockCommunityId();
if (!guildId) {
if (guildId === null) {
return;
}
try {
@@ -64,20 +61,16 @@ export class SingleCommunityService {
}
private async findDesignatedGuild(rawGuildId: string | null): Promise<GuildID | null> {
if (!rawGuildId) {
return null;
}
let guildId: GuildID;
try {
guildId = createGuildID(BigInt(rawGuildId));
} catch {
if (rawGuildId === null) {
return null;
}
const guildId = createGuildID(BigInt(rawGuildId));
try {
await this.guildDataService.getGuildSystem(guildId);
return guildId;
} catch {
return null;
} catch (error) {
if (error instanceof UnknownGuildError) return null;
throw error;
}
}
@@ -3,6 +3,7 @@
import {domainToASCII} from 'node:url';
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
import {InstanceConfigResponse} from '@fluxer/schema/src/domains/admin/AdminSchemas';
import type {RequestUrlPolicy} from '@pkgs/http_client/src/HttpClientTypes';
import {createPublicInternetRequestUrlPolicy} from '@pkgs/http_client/src/PublicInternetRequestUrlPolicy';
import {Config} from '../Config';
@@ -38,6 +39,7 @@ export function getSsoRequestUrlPolicy(): RequestUrlPolicy {
return ssoRequestUrlPolicy;
}
const DOMAIN_LABEL_REGEX = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
const SsoAllowedDomainsSchema = InstanceConfigResponse.shape.sso.shape.allowed_domains;
function normalizeOptionalSsoString(value: string | null): string | null {
if (typeof value !== 'string') {
@@ -69,9 +71,11 @@ export function isTestSsoProvider(
);
}
export function normalizeSsoAllowedEmailDomains(domains: Array<string>): Array<string> {
export function normalizeSsoAllowedEmailDomains(domains: unknown): Array<string> {
const result = SsoAllowedDomainsSchema.safeParse(domains);
if (!result.success) throwInvalidDomain();
const normalized = new Set<string>();
for (const rawDomain of domains) {
for (const rawDomain of result.data) {
const trimmed = rawDomain.trim().toLowerCase();
if (!trimmed) {
continue;
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {UnknownGuildError} from '@fluxer/errors/src/domains/guild/UnknownGuildError';
import {describe, expect, it} from 'vitest';
import type {User} from '../../models/User';
import type {InstancePolicyConfig} from '../InstanceConfigRepository';
@@ -30,7 +31,7 @@ function createHarness(params: {designatedGuildId: string | null; designatedGuil
const guildDataService = {
getGuildSystem: async () => {
if (!params.designatedGuildExists) {
throw new Error('unknown guild');
throw new UnknownGuildError();
}
return {} as never;
},
@@ -70,9 +71,9 @@ describe('SingleCommunityService.ensureStockCommunity', () => {
expect(harness.createdNames).toEqual(['Fluxer']);
});
it('creates a fresh community when the stored guild id is not a snowflake', async () => {
it('rejects an invalid stored guild id without creating a community', async () => {
const harness = createHarness({designatedGuildId: 'not-a-snowflake', designatedGuildExists: true});
await harness.service.ensureStockCommunity({owner: OWNER, name: 'Fluxer'});
expect(harness.createdNames).toEqual(['Fluxer']);
await expect(harness.service.ensureStockCommunity({owner: OWNER, name: 'Fluxer'})).rejects.toThrow(SyntaxError);
expect(harness.createdNames).toEqual([]);
});
});