feat: enhance audit logging, add MCP settings, and production docker setup
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Implemented comprehensive audit logging for Tasks, Users, Settings, Goals, Labels, AI Chat, and Rewards. - Added Admin UI for MCP Server settings and Audit Logs. - Created docker-compose-production.yml with Traefik configuration. - Fixed backend bugs (missing storage methods, route closure). - Added Audit Logging Guidelines.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
export const LEVEL_THRESHOLDS = [
|
||||
0, // Level 1: 0-99
|
||||
100, // Level 2: 100-249
|
||||
250, // Level 3: 250-499
|
||||
500, // Level 4: 500-999
|
||||
1000, // Level 5: 1000-1999
|
||||
2000, // Level 6: 2000-3499
|
||||
3500, // Level 7: 3500-4999
|
||||
5000, // Level 8: 5000-7499
|
||||
7500, // Level 9: 7500-9999
|
||||
10000 // Level 10: 10000+
|
||||
];
|
||||
|
||||
export function getLevelFromXP(xp: number): number {
|
||||
for (let i = LEVEL_THRESHOLDS.length - 1; i >= 0; i--) {
|
||||
if (xp >= LEVEL_THRESHOLDS[i]) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
export function getNextLevelXP(level: number): number {
|
||||
if (level >= LEVEL_THRESHOLDS.length) {
|
||||
return LEVEL_THRESHOLDS[LEVEL_THRESHOLDS.length - 1] * 1.5; // Scale indefinitely
|
||||
}
|
||||
return LEVEL_THRESHOLDS[level];
|
||||
}
|
||||
|
||||
export function getLevelProgress(xp: number): number {
|
||||
const currentLevel = getLevelFromXP(xp);
|
||||
const currentLevelStart = LEVEL_THRESHOLDS[currentLevel - 1];
|
||||
const nextLevelStart = getNextLevelXP(currentLevel);
|
||||
|
||||
if (xp >= nextLevelStart) return 100;
|
||||
|
||||
const progress = ((xp - currentLevelStart) / (nextLevelStart - currentLevelStart)) * 100;
|
||||
return Math.min(100, Math.max(0, progress));
|
||||
}
|
||||
|
||||
export const RANK_KEYS = [
|
||||
'novice', // Level 1
|
||||
'apprentice', // Level 2
|
||||
'journeyman', // Level 3
|
||||
'artisan', // Level 4
|
||||
'expert', // Level 5
|
||||
'master', // Level 6
|
||||
'grandmaster', // Level 7
|
||||
'virtuoso', // Level 8
|
||||
'legend', // Level 9
|
||||
'mythic' // Level 10
|
||||
];
|
||||
|
||||
export function getRankKey(level: number): string {
|
||||
if (level <= 0) return 'novice';
|
||||
if (level > RANK_KEYS.length) return RANK_KEYS[RANK_KEYS.length - 1];
|
||||
return RANK_KEYS[level - 1];
|
||||
}
|
||||
@@ -57,6 +57,8 @@ export const tasks = pgTable("tasks", {
|
||||
labelId: varchar("label_id").references(() => labels.id),
|
||||
energyLevel: text("energy_level").default("medium"), // 'low' | 'medium' | 'high'
|
||||
estimatedDuration: integer("estimated_duration"), // in minutes
|
||||
parentTaskId: varchar("parent_task_id").references((): any => tasks.id), // Self-reference for subtasks
|
||||
startDate: timestamp("start_date"), // For multi-day tasks
|
||||
dependencies: text("dependencies").array(), // Array of task IDs
|
||||
userId: varchar("user_id").references(() => users.id), // Added for ownership
|
||||
});
|
||||
@@ -108,6 +110,7 @@ export const insertLabelSchema = createInsertSchema(labels).omit({
|
||||
|
||||
export const insertTaskSchema = createInsertSchema(tasks, {
|
||||
dueDate: z.coerce.date().nullable(),
|
||||
startDate: z.coerce.date().nullable(),
|
||||
}).omit({
|
||||
id: true,
|
||||
userId: true, // We will set this server-side
|
||||
@@ -250,3 +253,56 @@ export type InsertPasswordResetToken = z.infer<typeof insertPasswordResetTokenSc
|
||||
export type PasswordResetToken = typeof passwordResetTokens.$inferSelect;
|
||||
|
||||
export type InsertUserReward = z.infer<typeof insertUserRewardSchema>;
|
||||
|
||||
// AI Chat Tables
|
||||
export const conversations = pgTable("conversations", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
userId: varchar("user_id").references(() => users.id).notNull(),
|
||||
title: text("title").notNull(),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
updatedAt: timestamp("updated_at").defaultNow(),
|
||||
});
|
||||
|
||||
export const messages = pgTable("messages", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
conversationId: varchar("conversation_id").references(() => conversations.id).notNull(),
|
||||
role: text("role").notNull(), // 'user' | 'assistant' | 'system'
|
||||
content: text("content").notNull(),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
|
||||
export const insertConversationSchema = createInsertSchema(conversations).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
});
|
||||
|
||||
export const insertMessageSchema = createInsertSchema(messages).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type InsertConversation = z.infer<typeof insertConversationSchema>;
|
||||
export type Conversation = typeof conversations.$inferSelect;
|
||||
export type InsertMessage = z.infer<typeof insertMessageSchema>;
|
||||
export type Message = typeof messages.$inferSelect;
|
||||
|
||||
// Audit Logging
|
||||
export const auditLogs = pgTable("audit_logs", {
|
||||
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
||||
userId: varchar("user_id").references(() => users.id), // Nullable if system action (though usually we track actor)
|
||||
action: text("action").notNull(), // 'CREATE', 'UPDATE', 'DELETE', 'LOGIN', etc.
|
||||
entityType: text("entity_type").notNull(), // 'TASK', 'GOAL', 'USER', 'SYSTEM'
|
||||
entityId: text("entity_id"), // ID of the modified entity
|
||||
details: json("details"), // JSON object with changed fields
|
||||
source: text("source").notNull().default("USER"), // 'USER' | 'AI' | 'SYSTEM'
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
});
|
||||
|
||||
export const insertAuditLogSchema = createInsertSchema(auditLogs).omit({
|
||||
id: true,
|
||||
createdAt: true,
|
||||
});
|
||||
|
||||
export type InsertAuditLog = z.infer<typeof insertAuditLogSchema>;
|
||||
export type AuditLog = typeof auditLogs.$inferSelect;
|
||||
|
||||
Reference in New Issue
Block a user