From 22aa5baaaad447e7a3823bca9a1ad39cd5e66279 Mon Sep 17 00:00:00 2001 From: Acbox Date: Mon, 12 Jan 2026 00:45:32 +0800 Subject: [PATCH] feat: platform --- packages/api/src/index.ts | 2 + packages/api/src/modules/platform/index.ts | 223 ++++++++++++++ packages/api/src/modules/platform/model.ts | 49 ++++ packages/api/src/modules/platform/service.ts | 203 +++++++++++++ packages/cli/src/cli/commands/platform.ts | 294 +++++++++++++++++++ packages/cli/src/cli/index.ts | 5 + packages/cli/src/core/index.ts | 14 + packages/cli/src/core/platform.ts | 188 ++++++++++++ packages/cli/src/types/index.ts | 10 + packages/db/src/platform.ts | 11 + packages/db/src/schema.ts | 3 +- packages/platform-telegram/src/index.ts | 1 + packages/shared/src/index.ts | 3 +- packages/shared/src/platform.ts | 7 + 14 files changed, 1011 insertions(+), 2 deletions(-) create mode 100644 packages/api/src/modules/platform/index.ts create mode 100644 packages/api/src/modules/platform/model.ts create mode 100644 packages/api/src/modules/platform/service.ts create mode 100644 packages/cli/src/cli/commands/platform.ts create mode 100644 packages/cli/src/core/platform.ts create mode 100644 packages/db/src/platform.ts create mode 100644 packages/shared/src/platform.ts diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 075dff55..a6d18eb2 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -2,6 +2,7 @@ import { Elysia } from 'elysia' import { corsMiddleware, errorMiddleware } from './middlewares' import { agentModule, authModule, modelModule, scheduleModule, settingsModule, userModule } from './modules' import { memoryModule } from './modules/memory' +import { platformModule } from './modules/platform' import openapi from '@elysiajs/openapi' const port = process.env.API_SERVER_PORT || 7002 @@ -17,6 +18,7 @@ export const app = new Elysia() .use(scheduleModule) .use(settingsModule) .use(userModule) + .use(platformModule) .listen(port) console.log( diff --git a/packages/api/src/modules/platform/index.ts b/packages/api/src/modules/platform/index.ts new file mode 100644 index 00000000..77d28187 --- /dev/null +++ b/packages/api/src/modules/platform/index.ts @@ -0,0 +1,223 @@ +import Elysia from 'elysia' +import { adminMiddleware, optionalAuthMiddleware } from '../../middlewares/auth' +import { + CreatePlatformModel, + UpdatePlatformModel, + GetPlatformByIdModel, + DeletePlatformModel, + UpdatePlatformConfigModel, + SetPlatformActiveModel, +} from './model' +import { + getPlatforms, + getPlatformById, + createPlatform, + updatePlatform, + deletePlatform, + updatePlatformConfig, + getActivePlatforms, + activePlatform, + setActivePlatform, +} from './service' +import { Platform } from '@memohome/shared' + +export const platformModule = new Elysia({ + prefix: '/platform', +}) + // 公开的读取接口 - 用户可读 + .use(optionalAuthMiddleware) + // Get all platforms + .onStart(async () => { + const platforms = await getActivePlatforms() + for (const platform of platforms) { + await activePlatform({ + id: platform.id, + name: platform.name, + endpoint: platform.endpoint, + config: platform.config as Record, + active: platform.active, + }) + } + }) + .get('/', async ({ query }) => { + try { + const page = parseInt(query.page as string) || 1 + const limit = parseInt(query.limit as string) || 10 + const sortOrder = (query.sortOrder as string) || 'desc' + + const result = await getPlatforms({ + page, + limit, + sortOrder: sortOrder as 'asc' | 'desc', + }) + + return { + success: true, + ...result, + } + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to fetch platforms', + } + } + }) + // Get platform by ID + .get('/:id', async ({ params }) => { + try { + const { id } = params + const platform = await getPlatformById(id) + if (!platform) { + return { + success: false, + error: 'Platform not found', + } + } + return { + success: true, + data: platform, + } + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to fetch platform', + } + } + }, GetPlatformByIdModel) + // 管理员权限的写入接口 - 管理员可读写 + .guard( + { + beforeHandle: () => { + // This will be overridden by adminMiddleware + }, + }, + (app) => + app + .use(adminMiddleware) + // Create new platform + .post('/', async ({ body }) => { + try { + const newPlatform = await createPlatform(body as Omit) + return { + success: true, + data: newPlatform, + } + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to create platform', + } + } + }, CreatePlatformModel) + // Update platform + .put('/:id', async ({ params, body }) => { + try { + const { id } = params + const updatedPlatform = await updatePlatform(id, body as Partial>) + if (!updatedPlatform) { + return { + success: false, + error: 'Platform not found', + } + } + return { + success: true, + data: updatedPlatform, + } + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to update platform', + } + } + }, UpdatePlatformModel) + // Update platform config + .put('/:id/config', async ({ params, body }) => { + try { + const { id } = params + const { config } = body as { config: Record } + const updatedPlatform = await updatePlatformConfig(id, config) + if (!updatedPlatform) { + return { + success: false, + error: 'Platform not found', + } + } + return { + success: true, + data: updatedPlatform, + } + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to update platform config', + } + } + }, UpdatePlatformConfigModel) + // Delete platform + .delete('/:id', async ({ params }) => { + try { + const { id } = params + const deletedPlatform = await deletePlatform(id) + if (!deletedPlatform) { + return { + success: false, + error: 'Platform not found', + } + } + return { + success: true, + data: deletedPlatform, + } + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to delete platform', + } + } + }, DeletePlatformModel) + // Active platform + .post('/:id/active', async ({ params }) => { + try { + const { id } = params + const activatedPlatform = await setActivePlatform(id, true) + if (!activatedPlatform) { + return { + success: false, + error: 'Platform not found', + } + } + return { + success: true, + data: activatedPlatform, + } + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to activate platform', + } + } + }, SetPlatformActiveModel) + // Inactive platform + .post('/:id/inactive', async ({ params }) => { + try { + const { id } = params + const inactivatedPlatform = await setActivePlatform(id, false) + if (!inactivatedPlatform) { + return { + success: false, + error: 'Platform not found', + } + } + return { + success: true, + data: inactivatedPlatform, + } + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to inactivate platform', + } + } + }, SetPlatformActiveModel) + ) diff --git a/packages/api/src/modules/platform/model.ts b/packages/api/src/modules/platform/model.ts new file mode 100644 index 00000000..85b21cc0 --- /dev/null +++ b/packages/api/src/modules/platform/model.ts @@ -0,0 +1,49 @@ +import { z } from 'zod' + +const PlatformSchema = z.object({ + name: z.string().min(1, 'Platform name is required'), + endpoint: z.string().min(1, 'Endpoint is required'), + config: z.record(z.string(), z.unknown()), + active: z.boolean().optional().default(true), +}) + +export type PlatformInput = z.infer + +export const CreatePlatformModel = { + body: PlatformSchema, +} + +export const UpdatePlatformModel = { + params: z.object({ + id: z.string(), + }), + body: PlatformSchema, +} + +export const GetPlatformByIdModel = { + params: z.object({ + id: z.string(), + }), +} + +export const DeletePlatformModel = { + params: z.object({ + id: z.string(), + }), +} + +export const UpdatePlatformConfigModel = { + params: z.object({ + id: z.string(), + }), + body: z.object({ + config: z.record(z.string(), z.unknown()), + }), +} + +export const SetPlatformActiveModel = { + params: z.object({ + id: z.string(), + }), +} + diff --git a/packages/api/src/modules/platform/service.ts b/packages/api/src/modules/platform/service.ts new file mode 100644 index 00000000..9b569b82 --- /dev/null +++ b/packages/api/src/modules/platform/service.ts @@ -0,0 +1,203 @@ +import { db } from '@memohome/db' +import { platform } from '@memohome/db/schema' +import { Platform } from '@memohome/shared' +import { eq, sql, desc, asc } from 'drizzle-orm' +import { calculateOffset, createPaginatedResult, type PaginatedResult } from '../../utils/pagination' +import path from 'node:path' + +/** + * 平台列表返回类型 + */ +type PlatformListItem = { + id: string + name: string + endpoint: string + config: Record + active: boolean + createdAt: Date + updatedAt: Date +} + +export const getPlatforms = async (params?: { + page?: number + limit?: number + sortOrder?: 'asc' | 'desc' +}): Promise> => { + const page = params?.page || 1 + const limit = params?.limit || 10 + const sortOrder = params?.sortOrder || 'desc' + const offset = calculateOffset(page, limit) + + // 获取总数 + const [{ count }] = await db + .select({ count: sql`count(*)` }) + .from(platform) + + // 获取分页数据 + const orderFn = sortOrder === 'desc' ? desc : asc + const platforms = await db + .select() + .from(platform) + .orderBy(orderFn(platform.createdAt)) + .limit(limit) + .offset(offset) + + // Cast config to Record for type safety + const typedPlatforms = platforms.map(p => ({ + ...p, + config: p.config as Record, + })) + + return createPaginatedResult(typedPlatforms, Number(count), page, limit) +} + +export const getPlatformById = async (id: string) => { + const [result] = await db.select().from(platform).where(eq(platform.id, id)) + return result +} + +export const getPlatformByName = async (name: string) => { + const [result] = await db.select().from(platform).where(eq(platform.name, name)) + return result +} + +export const getActivePlatforms = async () => { + return await db.select() + .from(platform) + .where(eq(platform.active, true)) +} + +export const createPlatform = async (data: Omit) => { + const [newPlatform] = await db + .insert(platform) + .values({ + name: data.name, + endpoint: data.endpoint, + config: data.config, + active: data.active ?? true, + }) + .returning() + if (data.active ?? true) { + await activePlatform({ + id: newPlatform.id, + name: newPlatform.name, + endpoint: newPlatform.endpoint, + config: newPlatform.config as Record, + active: newPlatform.active, + }) + } + return newPlatform +} + +export const updatePlatform = async (id: string, data: Partial>) => { + const updateData: { + name?: string + endpoint?: string + config?: Record + active?: boolean + updatedAt: Date + } = { + updatedAt: new Date(), + } + + if (data.name !== undefined) updateData.name = data.name + if (data.endpoint !== undefined) updateData.endpoint = data.endpoint + if (data.config !== undefined) updateData.config = data.config + if (data.active !== undefined) updateData.active = data.active + + const [updatedPlatform] = await db + .update(platform) + .set(updateData) + .where(eq(platform.id, id)) + .returning() + return updatedPlatform +} + +export const deletePlatform = async (id: string) => { + const [deletedPlatform] = await db + .delete(platform) + .where(eq(platform.id, id)) + .returning() + return deletedPlatform +} + +export const updatePlatformConfig = async (id: string, config: Record) => { + const [updatedPlatform] = await db + .update(platform) + .set({ + config, + updatedAt: new Date(), + }) + .where(eq(platform.id, id)) + .returning() + return updatedPlatform +} + +// active + +export const activePlatform = async (platform: Platform) => { + if (platform.active) { + return + } + await fetch(path.join(platform.endpoint, '/start'), { + method: 'POST', + body: JSON.stringify(platform.config), + headers: { + 'Content-Type': 'application/json', + }, + }) +} + +export const inactivePlatform = async (platform: Platform) => { + if (!platform.active) { + return + } + await fetch(path.join(platform.endpoint, '/stop'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) +} + +export const setActivePlatform = async (id: string, active: boolean) => { + const currentPlatform = await getPlatformById(id) + if (!currentPlatform) { + throw new Error('Platform not found') + } + const platformData: Platform = { + id: currentPlatform.id, + name: currentPlatform.name, + endpoint: currentPlatform.endpoint, + config: currentPlatform.config as Record, + active: active, + } + if (active) { + await activePlatform(platformData) + } else { + await inactivePlatform(platformData) + } + const [updatedPlatform] = await db + .update(platform) + .set({ active }) + .where(eq(platform.id, id)) + .returning() + return updatedPlatform +} + +export const sendMessageToPlatform = async (name: string, options: { + message: string + userId: string +}) => { + const currentPlatform = await getPlatformByName(name) + if (!currentPlatform) { + throw new Error('Platform not found') + } + await fetch(path.join(currentPlatform.endpoint, '/send'), { + method: 'POST', + body: JSON.stringify(options), + headers: { + 'Content-Type': 'application/json', + }, + }) +} diff --git a/packages/cli/src/cli/commands/platform.ts b/packages/cli/src/cli/commands/platform.ts new file mode 100644 index 00000000..e37233b0 --- /dev/null +++ b/packages/cli/src/cli/commands/platform.ts @@ -0,0 +1,294 @@ +import type { Command } from 'commander' +import chalk from 'chalk' +import inquirer from 'inquirer' +import ora from 'ora' +import { table } from 'table' +import * as platformCore from '../../core/platform' +import { formatError } from '../../utils' +import { getApiUrl } from '../../core/client' + +export function platformCommands(program: Command) { + program + .command('list') + .description('List all platform configurations') + .action(async () => { + const spinner = ora('Fetching platform list...').start() + try { + const platforms = await platformCore.listPlatforms() + spinner.succeed(chalk.green('Platform List')) + + if (platforms.length === 0) { + console.log(chalk.yellow('No platform configurations found')) + return + } + + const tableData = [ + ['ID', 'Name', 'Endpoint', 'Active', 'Created'], + ...platforms.map((item) => [ + item.id.substring(0, 8) + '...', + item.name, + item.endpoint, + item.active ? chalk.green('✓ Active') : chalk.dim('✗ Inactive'), + new Date(item.createdAt).toLocaleDateString(), + ]), + ] + + console.log(table(tableData)) + } catch (error) { + spinner.fail(chalk.red('Operation failed')) + if (error instanceof Error) { + if (error.name === 'AbortError' || error.name === 'TimeoutError') { + console.error(chalk.red('Connection timeout, please check:')) + console.error(chalk.yellow(' 1. Is the API server running?')) + console.error(chalk.yellow(' 2. Is the API URL correct?')) + console.error(chalk.dim(` Current config: ${getApiUrl()}`)) + } else { + console.error(chalk.red('Error:'), error.message) + } + } else { + console.error(chalk.red('Error:'), String(error)) + } + process.exit(1) + } + }) + + program + .command('create') + .description('Create platform configuration') + .option('-n, --name ', 'Platform name') + .option('-e, --endpoint ', 'Platform endpoint URL') + .option('-c, --config ', 'Platform config (JSON string)') + .option('-a, --active', 'Set platform as active', true) + .action(async (options) => { + try { + let { name, endpoint, config, active } = options + + if (!name || !endpoint) { + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'name', + message: 'Platform name:', + when: !name, + validate: (value: string) => { + if (value.trim()) return true + return 'Platform name is required' + }, + }, + { + type: 'input', + name: 'endpoint', + message: 'Platform endpoint URL:', + when: !endpoint, + validate: (value: string) => { + if (value.trim()) return true + return 'Endpoint is required' + }, + }, + { + type: 'input', + name: 'config', + message: 'Platform config (JSON string):', + default: '{}', + when: !config, + }, + { + type: 'confirm', + name: 'active', + message: 'Set as active?', + default: true, + when: active === undefined, + }, + ]) + + name = name || answers.name + endpoint = endpoint || answers.endpoint + config = config || answers.config + active = active ?? answers.active + } + + // Parse config JSON + let configObj: Record = {} + if (config) { + try { + configObj = JSON.parse(config) + } catch { + console.error(chalk.red('Invalid JSON config')) + process.exit(1) + } + } + + const spinner = ora('Creating platform configuration...').start() + + const platform = await platformCore.createPlatform({ + name, + endpoint, + config: configObj, + active, + }) + + spinner.succeed(chalk.green('Platform configuration created successfully')) + console.log(chalk.blue(`Name: ${platform.name}`)) + console.log(chalk.blue(`Endpoint: ${platform.endpoint}`)) + console.log(chalk.blue(`Active: ${platform.active ? 'Yes' : 'No'}`)) + console.log(chalk.blue(`ID: ${platform.id}`)) + } catch (error) { + console.error(chalk.red(formatError(error))) + process.exit(1) + } + }) + + program + .command('get ') + .description('Get platform configuration details') + .action(async (id) => { + const spinner = ora('Fetching platform configuration...').start() + try { + const platform = await platformCore.getPlatform(id) + spinner.succeed(chalk.green('Platform Configuration')) + console.log(chalk.blue(`ID: ${platform.id}`)) + console.log(chalk.blue(`Name: ${platform.name}`)) + console.log(chalk.blue(`Endpoint: ${platform.endpoint}`)) + console.log(chalk.blue(`Active: ${platform.active ? 'Yes' : 'No'}`)) + console.log(chalk.blue(`Config: ${JSON.stringify(platform.config, null, 2)}`)) + console.log(chalk.blue(`Created At: ${new Date(platform.createdAt).toLocaleString('en-US')}`)) + console.log(chalk.blue(`Updated At: ${new Date(platform.updatedAt).toLocaleString('en-US')}`)) + } catch (error) { + spinner.fail(chalk.red('Operation failed')) + console.error(chalk.red(formatError(error))) + process.exit(1) + } + }) + + program + .command('update ') + .description('Update platform configuration') + .option('-n, --name ', 'Platform name') + .option('-e, --endpoint ', 'Platform endpoint URL') + .option('-c, --config ', 'Platform config (JSON string)') + .option('-a, --active ', 'Set active status (true/false)') + .action(async (id, options) => { + try { + const updates: Record = {} + + if (options.name) updates.name = options.name + if (options.endpoint) updates.endpoint = options.endpoint + if (options.config) { + try { + updates.config = JSON.parse(options.config) + } catch { + console.error(chalk.red('Invalid JSON config')) + process.exit(1) + } + } + if (options.active !== undefined) { + updates.active = options.active === 'true' + } + + if (Object.keys(updates).length === 0) { + console.log(chalk.yellow('No updates specified')) + return + } + + const spinner = ora('Updating platform configuration...').start() + const platform = await platformCore.updatePlatform(id, updates as any) + spinner.succeed(chalk.green('Platform configuration updated successfully')) + console.log(chalk.blue(`Name: ${platform.name}`)) + console.log(chalk.blue(`Endpoint: ${platform.endpoint}`)) + console.log(chalk.blue(`Active: ${platform.active ? 'Yes' : 'No'}`)) + } catch (error) { + console.error(chalk.red(formatError(error))) + process.exit(1) + } + }) + + program + .command('update-config ') + .description('Update platform config only') + .requiredOption('-c, --config ', 'Platform config (JSON string)') + .action(async (id, options) => { + try { + let configObj: Record + try { + configObj = JSON.parse(options.config) + } catch { + console.error(chalk.red('Invalid JSON config')) + process.exit(1) + } + + const spinner = ora('Updating platform config...').start() + const platform = await platformCore.updatePlatformConfig(id, configObj) + spinner.succeed(chalk.green('Platform config updated successfully')) + console.log(chalk.blue(`Name: ${platform.name}`)) + console.log(chalk.blue(`Config: ${JSON.stringify(platform.config, null, 2)}`)) + } catch (error) { + console.error(chalk.red(formatError(error))) + process.exit(1) + } + }) + + program + .command('delete ') + .description('Delete platform configuration') + .action(async (id) => { + try { + const { confirm } = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm', + message: chalk.yellow(`Are you sure you want to delete platform configuration ${id}?`), + default: false, + }, + ]) + + if (!confirm) { + console.log(chalk.yellow('Cancelled')) + return + } + + const spinner = ora('Deleting platform configuration...').start() + await platformCore.deletePlatform(id) + spinner.succeed(chalk.green('Platform configuration deleted')) + } catch (error) { + console.error(chalk.red(formatError(error))) + process.exit(1) + } + }) + + program + .command('activate ') + .description('Activate platform (admin only)') + .action(async (id) => { + const spinner = ora('Activating platform...').start() + try { + const platform = await platformCore.activatePlatform(id) + spinner.succeed(chalk.green('Platform activated successfully')) + console.log(chalk.blue(`Name: ${platform.name}`)) + console.log(chalk.blue(`Endpoint: ${platform.endpoint}`)) + console.log(chalk.blue(`Active: ${platform.active ? chalk.green('Yes') : 'No'}`)) + } catch (error) { + spinner.fail(chalk.red('Operation failed')) + console.error(chalk.red(formatError(error))) + process.exit(1) + } + }) + + program + .command('deactivate ') + .description('Deactivate platform (admin only)') + .action(async (id) => { + const spinner = ora('Deactivating platform...').start() + try { + const platform = await platformCore.inactivatePlatform(id) + spinner.succeed(chalk.green('Platform deactivated successfully')) + console.log(chalk.blue(`Name: ${platform.name}`)) + console.log(chalk.blue(`Endpoint: ${platform.endpoint}`)) + console.log(chalk.blue(`Active: ${platform.active ? 'Yes' : chalk.dim('No')}`)) + } catch (error) { + spinner.fail(chalk.red('Operation failed')) + console.error(chalk.red(formatError(error))) + process.exit(1) + } + }) +} + diff --git a/packages/cli/src/cli/index.ts b/packages/cli/src/cli/index.ts index 69f29153..21e32dd4 100755 --- a/packages/cli/src/cli/index.ts +++ b/packages/cli/src/cli/index.ts @@ -5,6 +5,7 @@ import chalk from 'chalk' import { authCommands } from './commands/auth' import { userCommands } from './commands/user' import { modelCommands } from './commands/model' +import { platformCommands } from './commands/platform' import { agentCommands, startInteractiveMode } from './commands/agent' import { memoryCommands } from './commands/memory' import { configCommands } from './commands/config' @@ -30,6 +31,10 @@ userCommands(user) const model = program.command('model').description('AI model configuration management') modelCommands(model) +// Platform management commands +const platform = program.command('platform').description('Platform configuration management') +platformCommands(platform) + // Agent conversation commands const agent = program.command('agent').description('Chat with AI Agent') agentCommands(agent) diff --git a/packages/cli/src/core/index.ts b/packages/cli/src/core/index.ts index d6d87748..fb396432 100644 --- a/packages/cli/src/core/index.ts +++ b/packages/cli/src/core/index.ts @@ -54,6 +54,20 @@ export { type ModelListItem, } from './model' +// Platform +export { + listPlatforms, + createPlatform, + getPlatform, + updatePlatform, + updatePlatformConfig, + deletePlatform, + activatePlatform, + inactivatePlatform, + type CreatePlatformParams, + type PlatformListItem, +} from './platform' + // Agent export { chat, diff --git a/packages/cli/src/core/platform.ts b/packages/cli/src/core/platform.ts new file mode 100644 index 00000000..47bf2381 --- /dev/null +++ b/packages/cli/src/core/platform.ts @@ -0,0 +1,188 @@ +import { createClient, requireAuth } from './client' +import type { Platform, ApiResponse } from '../types' + +export interface CreatePlatformParams { + name: string + endpoint: string + config: Record + active?: boolean +} + +export interface PlatformListItem { + id: string + name: string + endpoint: string + config: Record + active: boolean + createdAt: string + updatedAt: string +} + +/** + * List all platforms + */ +export async function listPlatforms(): Promise { + requireAuth() + const client = createClient() + + const response = await client.platform.get() + + if (response.error) { + throw new Error(response.error.value) + } + + const data = response.data as { success?: boolean; items?: PlatformListItem[] } | null + if (data?.success && data?.items) { + return data.items + } + + throw new Error('Failed to fetch platform list') +} + +/** + * Create platform configuration + */ +export async function createPlatform(params: CreatePlatformParams): Promise { + requireAuth() + const client = createClient() + + const payload: Record = { + name: params.name, + endpoint: params.endpoint, + config: params.config, + active: params.active ?? true, + } + + const response = await client.platform.post(payload) + + if (response.error) { + throw new Error(response.error.value) + } + + const data = response.data as ApiResponse | null + if (data?.success && data?.data) { + return data.data + } + + throw new Error('Failed to create platform configuration') +} + +/** + * Get platform by ID + */ +export async function getPlatform(id: string): Promise { + requireAuth() + const client = createClient() + + const response = await client.platform({ id }).get() + + if (response.error) { + throw new Error(response.error.value) + } + + const data = response.data as ApiResponse | null + if (data?.success && data?.data) { + return data.data + } + + throw new Error('Failed to fetch platform configuration') +} + +/** + * Update platform + */ +export async function updatePlatform(id: string, params: Partial): Promise { + requireAuth() + const client = createClient() + + const response = await client.platform({ id }).put(params) + + if (response.error) { + throw new Error(response.error.value) + } + + const data = response.data as ApiResponse | null + if (data?.success && data?.data) { + return data.data + } + + throw new Error('Failed to update platform configuration') +} + +/** + * Update platform config + */ +export async function updatePlatformConfig(id: string, config: Record): Promise { + requireAuth() + const client = createClient() + + const response = await client.platform({ id }).config.put({ config }) + + if (response.error) { + throw new Error(response.error.value) + } + + const data = response.data as ApiResponse | null + if (data?.success && data?.data) { + return data.data + } + + throw new Error('Failed to update platform config') +} + +/** + * Delete platform + */ +export async function deletePlatform(id: string): Promise { + requireAuth() + const client = createClient() + + const response = await client.platform({ id }).delete() + + if (response.error) { + throw new Error(response.error.value) + } +} + +/** + * Activate platform + */ +export async function activatePlatform(id: string): Promise { + requireAuth() + const client = createClient() + + const response = await client.platform({ id }).active.post() + + if (response.error) { + throw new Error(response.error.value) + } + + const data = response.data as ApiResponse | null + if (data?.success && data?.data) { + return data.data + } + + throw new Error('Failed to activate platform') +} + +/** + * Inactivate platform + */ +export async function inactivatePlatform(id: string): Promise { + requireAuth() + const client = createClient() + + const response = await client.platform({ id }).inactive.post() + + if (response.error) { + throw new Error(response.error.value) + } + + const data = response.data as ApiResponse | null + if (data?.success && data?.data) { + return data.data + } + + throw new Error('Failed to inactivate platform') +} + diff --git a/packages/cli/src/types/index.ts b/packages/cli/src/types/index.ts index 7930f7ed..1c35e24f 100644 --- a/packages/cli/src/types/index.ts +++ b/packages/cli/src/types/index.ts @@ -69,3 +69,13 @@ export interface Schedule { updatedAt: string } +export interface Platform { + id: string + name: string + endpoint: string + config: Record + active: boolean + createdAt: string + updatedAt: string +} + diff --git a/packages/db/src/platform.ts b/packages/db/src/platform.ts new file mode 100644 index 00000000..d080bd56 --- /dev/null +++ b/packages/db/src/platform.ts @@ -0,0 +1,11 @@ +import { boolean, jsonb, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core' + +export const platform = pgTable('platform', { + id: uuid('id').primaryKey().defaultRandom(), + name: text('name').notNull(), + endpoint: text('endpoint').notNull(), + config: jsonb('config').notNull(), + active: boolean('active').notNull().default(true), + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at').notNull().defaultNow(), +}) \ No newline at end of file diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index a85ed99d..8d758267 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -2,4 +2,5 @@ export * from './history' export * from './model' export * from './settings' export * from './schedule' -export * from './users' \ No newline at end of file +export * from './users' +export * from './platform' \ No newline at end of file diff --git a/packages/platform-telegram/src/index.ts b/packages/platform-telegram/src/index.ts index a30d89e0..13077550 100644 --- a/packages/platform-telegram/src/index.ts +++ b/packages/platform-telegram/src/index.ts @@ -18,6 +18,7 @@ export class TelegramPlatform extends BasePlatform { config = z.object({ botToken: z.string(), }) + port = 7101 private bot?: Telegraf redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379') diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 15b9157a..bc3ef823 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1,2 +1,3 @@ export * from './model' -export * from './schedule' \ No newline at end of file +export * from './schedule' +export * from './platform' \ No newline at end of file diff --git a/packages/shared/src/platform.ts b/packages/shared/src/platform.ts new file mode 100644 index 00000000..de71401c --- /dev/null +++ b/packages/shared/src/platform.ts @@ -0,0 +1,7 @@ +export interface Platform { + id: string + name: string + endpoint: string + config: Record + active: boolean +} \ No newline at end of file