feat(channels): add line and zalo minio override config

This commit is contained in:
William Valentin
2026-02-17 10:54:43 -08:00
parent 2fe6495c92
commit 77ae15b3e2
4 changed files with 171 additions and 6 deletions
+13
View File
@@ -3724,6 +3724,19 @@
"docs/plans/state.json" "docs/plans/state.json"
], ],
"test_status": "pnpm test:run src/channels/line/adapter.test.ts src/channels/zalo/adapter.test.ts + pnpm typecheck passing" "test_status": "pnpm test:run src/channels/line/adapter.test.ts src/channels/zalo/adapter.test.ts + pnpm typecheck passing"
},
"line-zalo-channel-minio-overrides": {
"status": "completed",
"date": "2026-02-17",
"updated": "2026-02-17",
"summary": "Added channel-specific MinIO override configuration for LINE and Zalo (`line.minio`, `zalo.minio`) with fallback inheritance from `backup.minio`, enabling per-channel endpoint/credential/bucket/prefix control while preserving legacy shared-prefix behavior.",
"files_modified": [
"src/config/schema.ts",
"src/config/schema.test.ts",
"src/daemon/channels.ts",
"docs/plans/state.json"
],
"test_status": "pnpm test:run src/config/schema.test.ts src/daemon/channels.test.ts src/channels/line/adapter.test.ts src/channels/zalo/adapter.test.ts + pnpm typecheck passing"
} }
}, },
"overall_progress": { "overall_progress": {
+75
View File
@@ -790,6 +790,44 @@ describe('configSchema — line', () => {
expect(result.line.allowed_source_ids).toEqual([]); expect(result.line.allowed_source_ids).toEqual([]);
expect(result.line.require_mention).toBe(true); expect(result.line.require_mention).toBe(true);
expect(result.line.mention_name).toBe('flynn'); expect(result.line.mention_name).toBe('flynn');
expect(result.line.minio.enabled).toBe(false);
expect(result.line.minio.prefix).toBe('flynn/channels/line');
expect(result.line.minio.secure).toBe(true);
expect(result.line.minio.expires).toBe('24h');
});
it('accepts line-specific minio overrides', () => {
const result = configSchema.parse({
...minimalConfig,
line: {
channel_access_token: 'line-token',
channel_secret: 'line-secret',
minio: {
enabled: true,
endpoint: 'localhost:9000',
access_key: 'line-key',
secret_key: 'line-secret',
bucket: 'line-attachments',
prefix: 'line/files',
secure: false,
expires: '12h',
mc_path: '/usr/local/bin/mc',
},
},
});
if (!result.line) {
throw new Error('Expected line config');
}
expect(result.line.minio.enabled).toBe(true);
expect(result.line.minio.endpoint).toBe('localhost:9000');
expect(result.line.minio.access_key).toBe('line-key');
expect(result.line.minio.secret_key).toBe('line-secret');
expect(result.line.minio.bucket).toBe('line-attachments');
expect(result.line.minio.prefix).toBe('line/files');
expect(result.line.minio.secure).toBe(false);
expect(result.line.minio.expires).toBe('12h');
expect(result.line.minio.mc_path).toBe('/usr/local/bin/mc');
}); });
}); });
@@ -842,6 +880,43 @@ describe('configSchema — zalo', () => {
expect(result.zalo.allowed_user_ids).toEqual([]); expect(result.zalo.allowed_user_ids).toEqual([]);
expect(result.zalo.require_mention).toBe(true); expect(result.zalo.require_mention).toBe(true);
expect(result.zalo.mention_name).toBe('flynn'); expect(result.zalo.mention_name).toBe('flynn');
expect(result.zalo.minio.enabled).toBe(false);
expect(result.zalo.minio.prefix).toBe('flynn/channels/zalo');
expect(result.zalo.minio.secure).toBe(true);
expect(result.zalo.minio.expires).toBe('24h');
});
it('accepts zalo-specific minio overrides', () => {
const result = configSchema.parse({
...minimalConfig,
zalo: {
oa_access_token: 'oa-token',
minio: {
enabled: true,
endpoint: 'localhost:9000',
access_key: 'zalo-key',
secret_key: 'zalo-secret',
bucket: 'zalo-attachments',
prefix: 'zalo/files',
secure: false,
expires: '8h',
mc_path: '/usr/local/bin/mc',
},
},
});
if (!result.zalo) {
throw new Error('Expected zalo config');
}
expect(result.zalo.minio.enabled).toBe(true);
expect(result.zalo.minio.endpoint).toBe('localhost:9000');
expect(result.zalo.minio.access_key).toBe('zalo-key');
expect(result.zalo.minio.secret_key).toBe('zalo-secret');
expect(result.zalo.minio.bucket).toBe('zalo-attachments');
expect(result.zalo.minio.prefix).toBe('zalo/files');
expect(result.zalo.minio.secure).toBe(false);
expect(result.zalo.minio.expires).toBe('8h');
expect(result.zalo.minio.mc_path).toBe('/usr/local/bin/mc');
}); });
}); });
+22
View File
@@ -595,6 +595,17 @@ const lineSchema = z.object({
allowed_source_ids: z.array(z.string()).default([]), allowed_source_ids: z.array(z.string()).default([]),
require_mention: z.boolean().default(true), require_mention: z.boolean().default(true),
mention_name: z.string().default('flynn'), mention_name: z.string().default('flynn'),
minio: z.object({
enabled: z.boolean().default(false),
endpoint: z.string().optional(),
access_key: z.string().optional(),
secret_key: z.string().optional(),
bucket: z.string().optional(),
prefix: z.string().default('flynn/channels/line'),
secure: z.boolean().default(true),
expires: z.string().default('24h'),
mc_path: z.string().optional(),
}).default({}),
}).optional(); }).optional();
const feishuSchema = z.object({ const feishuSchema = z.object({
@@ -614,6 +625,17 @@ const zaloSchema = z.object({
allowed_user_ids: z.array(z.string()).default([]), allowed_user_ids: z.array(z.string()).default([]),
require_mention: z.boolean().default(true), require_mention: z.boolean().default(true),
mention_name: z.string().default('flynn'), mention_name: z.string().default('flynn'),
minio: z.object({
enabled: z.boolean().default(false),
endpoint: z.string().optional(),
access_key: z.string().optional(),
secret_key: z.string().optional(),
bucket: z.string().optional(),
prefix: z.string().default('flynn/channels/zalo'),
secure: z.boolean().default(true),
expires: z.string().default('24h'),
mc_path: z.string().optional(),
}).default({}),
}).optional(); }).optional();
const browserSchema = z.object({ const browserSchema = z.object({
+61 -6
View File
@@ -26,6 +26,8 @@ function resolveChannelMinioShare(config: Config): {
bucket: string; bucket: string;
prefix: string; prefix: string;
secure: boolean; secure: boolean;
expires?: string;
mcPath?: string;
} | undefined { } | undefined {
const minio = config.backup.minio; const minio = config.backup.minio;
if (!minio.enabled || !minio.endpoint || !minio.access_key || !minio.secret_key || !minio.bucket) { if (!minio.enabled || !minio.endpoint || !minio.access_key || !minio.secret_key || !minio.bucket) {
@@ -42,6 +44,55 @@ function resolveChannelMinioShare(config: Config): {
}; };
} }
function resolveChannelMinioShareWithOverrides(
base: ReturnType<typeof resolveChannelMinioShare>,
override: {
enabled: boolean;
endpoint?: string;
access_key?: string;
secret_key?: string;
bucket?: string;
prefix: string;
secure: boolean;
expires: string;
mc_path?: string;
} | undefined,
): {
enabled: boolean;
endpoint: string;
accessKey: string;
secretKey: string;
bucket: string;
prefix: string;
secure: boolean;
expires?: string;
mcPath?: string;
} | undefined {
if (!override?.enabled) {
return base;
}
const endpoint = override.endpoint ?? base?.endpoint;
const accessKey = override.access_key ?? base?.accessKey;
const secretKey = override.secret_key ?? base?.secretKey;
const bucket = override.bucket ?? base?.bucket;
if (!endpoint || !accessKey || !secretKey || !bucket) {
return undefined;
}
return {
enabled: true,
endpoint,
accessKey,
secretKey,
bucket,
prefix: override.prefix,
secure: override.secure,
expires: override.expires,
mcPath: override.mc_path,
};
}
export function registerChannels(deps: ChannelsDeps): ChannelsResult { export function registerChannels(deps: ChannelsDeps): ChannelsResult {
const { config, channelRegistry, hookEngine, pairingManager, gateway } = deps; const { config, channelRegistry, hookEngine, pairingManager, gateway } = deps;
const channelMinioShare = resolveChannelMinioShare(config); const channelMinioShare = resolveChannelMinioShare(config);
@@ -181,15 +232,17 @@ export function registerChannels(deps: ChannelsDeps): ChannelsResult {
// Register LINE adapter (if configured) // Register LINE adapter (if configured)
if (config.line) { if (config.line) {
const lineMinioShare = resolveChannelMinioShareWithOverrides(channelMinioShare, config.line.minio);
const effectiveLineMinioShare = lineMinioShare && !config.line.minio.enabled
? { ...lineMinioShare, prefix: `${lineMinioShare.prefix.replace(/\/+$/, '')}/channels/line` }
: lineMinioShare;
const lineAdapter = new LineAdapter({ const lineAdapter = new LineAdapter({
channelAccessToken: config.line.channel_access_token, channelAccessToken: config.line.channel_access_token,
channelSecret: config.line.channel_secret, channelSecret: config.line.channel_secret,
allowedSourceIds: config.line.allowed_source_ids.length > 0 ? config.line.allowed_source_ids : undefined, allowedSourceIds: config.line.allowed_source_ids.length > 0 ? config.line.allowed_source_ids : undefined,
requireMention: config.line.require_mention, requireMention: config.line.require_mention,
mentionName: config.line.mention_name, mentionName: config.line.mention_name,
minio: channelMinioShare minio: effectiveLineMinioShare,
? { ...channelMinioShare, prefix: `${channelMinioShare.prefix.replace(/\/+$/, '')}/channels/line` }
: undefined,
}); });
channelRegistry.register(lineAdapter); channelRegistry.register(lineAdapter);
gateway.setLineHandler(lineAdapter); gateway.setLineHandler(lineAdapter);
@@ -212,6 +265,10 @@ export function registerChannels(deps: ChannelsDeps): ChannelsResult {
// Register Zalo adapter (if configured) // Register Zalo adapter (if configured)
if (config.zalo) { if (config.zalo) {
const zaloMinioShare = resolveChannelMinioShareWithOverrides(channelMinioShare, config.zalo.minio);
const effectiveZaloMinioShare = zaloMinioShare && !config.zalo.minio.enabled
? { ...zaloMinioShare, prefix: `${zaloMinioShare.prefix.replace(/\/+$/, '')}/channels/zalo` }
: zaloMinioShare;
const zaloAdapter = new ZaloAdapter({ const zaloAdapter = new ZaloAdapter({
oaAccessToken: config.zalo.oa_access_token, oaAccessToken: config.zalo.oa_access_token,
endpoint: config.zalo.endpoint, endpoint: config.zalo.endpoint,
@@ -219,9 +276,7 @@ export function registerChannels(deps: ChannelsDeps): ChannelsResult {
allowedUserIds: config.zalo.allowed_user_ids.length > 0 ? config.zalo.allowed_user_ids : undefined, allowedUserIds: config.zalo.allowed_user_ids.length > 0 ? config.zalo.allowed_user_ids : undefined,
requireMention: config.zalo.require_mention, requireMention: config.zalo.require_mention,
mentionName: config.zalo.mention_name, mentionName: config.zalo.mention_name,
minio: channelMinioShare minio: effectiveZaloMinioShare,
? { ...channelMinioShare, prefix: `${channelMinioShare.prefix.replace(/\/+$/, '')}/channels/zalo` }
: undefined,
}); });
channelRegistry.register(zaloAdapter); channelRegistry.register(zaloAdapter);
gateway.setZaloHandler(zaloAdapter); gateway.setZaloHandler(zaloAdapter);