feat(channels): add line and zalo minio override config
This commit is contained in:
@@ -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": {
|
||||||
|
|||||||
@@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user