mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
🐛 fix(database): remove content validation limits for agent cron jobs (#11444)
* 🐛 fix(database): remove content validation limits for agent cron jobs - Remove min(1) validation to allow empty content - Remove max(2000) validation to allow unlimited content length - Content can now be empty when using editData for rich content 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * ♻️ refactor(types): move agentCronJob schemas to types package - Create manual Zod schemas in @lobechat/types instead of using createInsertSchema - Define InsertAgentCronJobSchema and UpdateAgentCronJobSchema manually - Re-export types from database schema for backward compatibility - Update router to use new schema imports from @lobechat/types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
||||
import { type ExecutionConditions } from '@lobechat/types';
|
||||
import { boolean, index, integer, jsonb, pgTable, text, timestamp } from 'drizzle-orm/pg-core';
|
||||
import { createInsertSchema } from 'drizzle-zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { idGenerator } from '../utils/idGenerator';
|
||||
import { timestamps } from './_helpers';
|
||||
@@ -9,16 +8,6 @@ import { agents } from './agent';
|
||||
import { chatGroups } from './chatGroup';
|
||||
import { users } from './user';
|
||||
|
||||
// Execution conditions type for JSONB field
|
||||
export interface ExecutionConditions {
|
||||
maxExecutionsPerDay?: number;
|
||||
timeRange?: {
|
||||
end: string; // "18:00"
|
||||
start: string; // "09:00"
|
||||
};
|
||||
weekdays?: number[]; // [1,2,3,4,5] (Monday=1, Sunday=0)
|
||||
}
|
||||
|
||||
// Agent cron jobs table - supports multiple cron jobs per agent
|
||||
export const agentCronJobs = pgTable(
|
||||
'agent_cron_jobs',
|
||||
@@ -74,98 +63,11 @@ export const agentCronJobs = pgTable(
|
||||
],
|
||||
);
|
||||
|
||||
// Validation schemas
|
||||
export const cronPatternSchema = z
|
||||
.string()
|
||||
.regex(
|
||||
/^(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})$/,
|
||||
'Invalid cron pattern',
|
||||
);
|
||||
|
||||
// Minimum 30 minutes validation (using standard cron format)
|
||||
export const minimumIntervalSchema = z.string().refine((pattern) => {
|
||||
// Standard cron format: minute hour day month weekday
|
||||
const allowedPatterns = [
|
||||
'*/30 * * * *', // Every 30 minutes
|
||||
'0 * * * *', // Every hour
|
||||
'0 */2 * * *', // Every 2 hours
|
||||
'0 */3 * * *', // Every 3 hours
|
||||
'0 */4 * * *', // Every 4 hours
|
||||
'0 */6 * * *', // Every 6 hours
|
||||
'0 */8 * * *', // Every 8 hours
|
||||
'0 */12 * * *', // Every 12 hours
|
||||
'0 0 * * *', // Daily at midnight
|
||||
'0 0 * * 0', // Weekly on Sunday
|
||||
'0 0 1 * *', // Monthly on 1st
|
||||
];
|
||||
|
||||
// Check if it matches allowed patterns
|
||||
if (allowedPatterns.includes(pattern)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse pattern to validate minimum 30-minute interval
|
||||
const parts = pattern.split(' ');
|
||||
if (parts.length !== 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [minute, hour] = parts;
|
||||
|
||||
// Allow minute intervals >= 30 (e.g., */30, */45, */60)
|
||||
if (minute.startsWith('*/')) {
|
||||
const interval = parseInt(minute.slice(2));
|
||||
if (!isNaN(interval) && interval >= 30) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow hourly patterns: 0 */N * * * where N >= 1
|
||||
if (minute === '0' && hour.startsWith('*/')) {
|
||||
const interval = parseInt(hour.slice(2));
|
||||
if (!isNaN(interval) && interval >= 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow specific hour patterns: 0 N * * * (runs once per day)
|
||||
if (minute === '0' && /^\d+$/.test(hour)) {
|
||||
const h = parseInt(hour);
|
||||
if (!isNaN(h) && h >= 0 && h <= 23) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}, 'Minimum execution interval is 30 minutes');
|
||||
|
||||
export const executionConditionsSchema = z
|
||||
.object({
|
||||
maxExecutionsPerDay: z.number().min(1).max(100).optional(),
|
||||
timeRange: z
|
||||
.object({
|
||||
end: z.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, 'Invalid time format'),
|
||||
start: z.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, 'Invalid time format'),
|
||||
})
|
||||
.optional(),
|
||||
weekdays: z.array(z.number().min(0).max(6)).optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
export const insertAgentCronJobSchema = createInsertSchema(agentCronJobs, {
|
||||
cronPattern: minimumIntervalSchema,
|
||||
content: z.string().min(1).max(2000),
|
||||
editData: z.record(z.any()).optional(), // Allow any JSON structure for rich content
|
||||
name: z.string().max(100).optional(),
|
||||
description: z.string().max(500).optional(),
|
||||
maxExecutions: z.number().min(1).max(10_000).optional(),
|
||||
executionConditions: executionConditionsSchema,
|
||||
});
|
||||
|
||||
export const updateAgentCronJobSchema = insertAgentCronJobSchema.partial();
|
||||
|
||||
// Type exports
|
||||
export type NewAgentCronJob = typeof agentCronJobs.$inferInsert;
|
||||
export type AgentCronJob = typeof agentCronJobs.$inferSelect;
|
||||
export type CreateAgentCronJobData = z.infer<typeof insertAgentCronJobSchema>;
|
||||
export type UpdateAgentCronJobData = z.infer<typeof updateAgentCronJobSchema>;
|
||||
|
||||
// Re-export types from types package for consumers
|
||||
export type { ExecutionConditions } from '@lobechat/types';
|
||||
export type { InsertAgentCronJob as CreateAgentCronJobData } from '@lobechat/types';
|
||||
export type { UpdateAgentCronJob as UpdateAgentCronJobData } from '@lobechat/types';
|
||||
|
||||
115
packages/types/src/agentCronJob/index.ts
Normal file
115
packages/types/src/agentCronJob/index.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// Execution conditions type
|
||||
export interface ExecutionConditions {
|
||||
maxExecutionsPerDay?: number;
|
||||
timeRange?: {
|
||||
end: string; // "18:00"
|
||||
start: string; // "09:00"
|
||||
};
|
||||
weekdays?: number[]; // [1,2,3,4,5] (Monday=1, Sunday=0)
|
||||
}
|
||||
|
||||
// Cron pattern validation schema
|
||||
export const cronPatternSchema = z
|
||||
.string()
|
||||
.regex(
|
||||
/^(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})$/,
|
||||
'Invalid cron pattern',
|
||||
);
|
||||
|
||||
// Minimum 30 minutes validation (using standard cron format)
|
||||
export const minimumIntervalSchema = z.string().refine((pattern) => {
|
||||
// Standard cron format: minute hour day month weekday
|
||||
const allowedPatterns = [
|
||||
'*/30 * * * *', // Every 30 minutes
|
||||
'0 * * * *', // Every hour
|
||||
'0 */2 * * *', // Every 2 hours
|
||||
'0 */3 * * *', // Every 3 hours
|
||||
'0 */4 * * *', // Every 4 hours
|
||||
'0 */6 * * *', // Every 6 hours
|
||||
'0 */8 * * *', // Every 8 hours
|
||||
'0 */12 * * *', // Every 12 hours
|
||||
'0 0 * * *', // Daily at midnight
|
||||
'0 0 * * 0', // Weekly on Sunday
|
||||
'0 0 1 * *', // Monthly on 1st
|
||||
];
|
||||
|
||||
// Check if it matches allowed patterns
|
||||
if (allowedPatterns.includes(pattern)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse pattern to validate minimum 30-minute interval
|
||||
const parts = pattern.split(' ');
|
||||
if (parts.length !== 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [minute, hour] = parts;
|
||||
|
||||
// Allow minute intervals >= 30 (e.g., */30, */45, */60)
|
||||
if (minute.startsWith('*/')) {
|
||||
const interval = parseInt(minute.slice(2));
|
||||
if (!isNaN(interval) && interval >= 30) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow hourly patterns: 0 */N * * * where N >= 1
|
||||
if (minute === '0' && hour.startsWith('*/')) {
|
||||
const interval = parseInt(hour.slice(2));
|
||||
if (!isNaN(interval) && interval >= 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow specific hour patterns: 0 N * * * (runs once per day)
|
||||
if (minute === '0' && /^\d+$/.test(hour)) {
|
||||
const h = parseInt(hour);
|
||||
if (!isNaN(h) && h >= 0 && h <= 23) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}, 'Minimum execution interval is 30 minutes');
|
||||
|
||||
// Execution conditions schema
|
||||
export const ExecutionConditionsSchema = z
|
||||
.object({
|
||||
maxExecutionsPerDay: z.number().min(1).max(100).optional(),
|
||||
timeRange: z
|
||||
.object({
|
||||
end: z.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, 'Invalid time format'),
|
||||
start: z.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, 'Invalid time format'),
|
||||
})
|
||||
.optional(),
|
||||
weekdays: z.array(z.number().min(0).max(6)).optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
// Insert schema for creating agent cron jobs
|
||||
export const InsertAgentCronJobSchema = z.object({
|
||||
agentId: z.string(),
|
||||
content: z.string(), // Allow empty content (when using editData for rich content)
|
||||
cronPattern: minimumIntervalSchema,
|
||||
description: z.string().optional().nullable(),
|
||||
editData: z.record(z.string(), z.any()).optional().nullable(),
|
||||
enabled: z.boolean().optional().nullable(),
|
||||
executionConditions: ExecutionConditionsSchema.nullable(),
|
||||
groupId: z.string().optional().nullable(),
|
||||
id: z.string().optional(),
|
||||
maxExecutions: z.number().min(1).max(10_000).optional().nullable(),
|
||||
name: z.string().optional().nullable(),
|
||||
remainingExecutions: z.number().optional().nullable(),
|
||||
timezone: z.string().optional().nullable(),
|
||||
userId: z.string().optional(),
|
||||
});
|
||||
|
||||
// Update schema (all fields optional)
|
||||
export const UpdateAgentCronJobSchema = InsertAgentCronJobSchema.partial();
|
||||
|
||||
// Type exports
|
||||
export type InsertAgentCronJob = z.infer<typeof InsertAgentCronJobSchema>;
|
||||
export type UpdateAgentCronJob = z.infer<typeof UpdateAgentCronJobSchema>;
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './agent';
|
||||
export * from './agentCronJob';
|
||||
export * from './agentGroup';
|
||||
export * from './aiChat';
|
||||
export * from './aiProvider';
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {
|
||||
type InsertAgentCronJob,
|
||||
InsertAgentCronJobSchema,
|
||||
type UpdateAgentCronJob,
|
||||
UpdateAgentCronJobSchema,
|
||||
} from '@lobechat/types';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AgentCronJobModel } from '@/database/models/agentCronJob';
|
||||
import {
|
||||
type CreateAgentCronJobData,
|
||||
type UpdateAgentCronJobData,
|
||||
insertAgentCronJobSchema,
|
||||
updateAgentCronJobSchema,
|
||||
} from '@/database/schemas/agentCronJob';
|
||||
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
||||
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
|
||||
|
||||
@@ -31,7 +31,7 @@ const batchUpdateStatusSchema = z.object({
|
||||
});
|
||||
|
||||
// Create input schema for tRPC that omits server-managed fields
|
||||
const createAgentCronJobInputSchema = insertAgentCronJobSchema.omit({
|
||||
const createAgentCronJobInputSchema = InsertAgentCronJobSchema.omit({
|
||||
userId: true, // Provided by authentication context
|
||||
});
|
||||
|
||||
@@ -81,7 +81,7 @@ export const agentCronJobRouter = router({
|
||||
const cronJobModel = new AgentCronJobModel(db, userId);
|
||||
// Add userId to the input data since it's provided by authentication context
|
||||
const cronJobData = { ...input, userId };
|
||||
const cronJob = await cronJobModel.create(cronJobData as CreateAgentCronJobData);
|
||||
const cronJob = await cronJobModel.create(cronJobData as InsertAgentCronJob);
|
||||
|
||||
return {
|
||||
data: cronJob,
|
||||
@@ -327,7 +327,7 @@ export const agentCronJobRouter = router({
|
||||
update: agentCronJobProcedure
|
||||
.input(
|
||||
z.object({
|
||||
data: updateAgentCronJobSchema,
|
||||
data: UpdateAgentCronJobSchema,
|
||||
id: z.string(),
|
||||
}),
|
||||
)
|
||||
@@ -337,7 +337,7 @@ export const agentCronJobRouter = router({
|
||||
|
||||
try {
|
||||
const cronJobModel = new AgentCronJobModel(db, userId);
|
||||
const cronJob = await cronJobModel.update(id, data as UpdateAgentCronJobData);
|
||||
const cronJob = await cronJobModel.update(id, data as UpdateAgentCronJob);
|
||||
|
||||
if (!cronJob) {
|
||||
throw new TRPCError({
|
||||
|
||||
Reference in New Issue
Block a user