f91978c078
continuous-integration/drone/push Build is failing
- Add PWA manifest and service worker for push notifications
- Implement VAPID key generation and push subscription management
- Add push notification API endpoints (/api/push/*)
- Add push_subscriptions table to database schema
- Update notification settings UI with push support and iOS hints
- Fix translation issue showing "{ task } created" - add missing keys
- Fix mobile sidebar visibility for iOS home screen app
- Change default task filter from "all" to "open" (excludes done tasks)
- Add "Open" filter option to show only todo + inProgress tasks
552 lines
22 KiB
TypeScript
552 lines
22 KiB
TypeScript
import { sql } from "drizzle-orm";
|
|
import { pgTable, text, varchar, timestamp, integer, boolean, json, date } from "drizzle-orm/pg-core";
|
|
import { createInsertSchema } from "drizzle-zod";
|
|
import { z } from "zod";
|
|
|
|
// ADHD Settings Type
|
|
export interface ADHDSettings {
|
|
reducedAnimations: boolean;
|
|
largerTargets: boolean;
|
|
breakReminderInterval: number; // minutes
|
|
singleTaskModeDefault: boolean;
|
|
positiveMessagingLevel: 'minimal' | 'normal' | 'high';
|
|
hyperfocusProtection: boolean;
|
|
hyperfocusMaxMinutes: number;
|
|
visualTimerEnabled: boolean;
|
|
quickWinThreshold: number; // minutes - tasks under this are "quick wins"
|
|
energyCheckInsEnabled: boolean;
|
|
}
|
|
|
|
export const users = pgTable("users", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
username: text("username").notNull().unique(),
|
|
email: text("email").notNull().unique(), // Added email
|
|
password: text("password").notNull(),
|
|
role: text("role").notNull().default("user"), // 'admin' | 'user'
|
|
isActive: boolean("is_active").notNull().default(true),
|
|
xp: integer("xp").notNull().default(0),
|
|
level: integer("level").notNull().default(1),
|
|
currentStreak: integer("current_streak").notNull().default(0),
|
|
lastTaskDate: timestamp("last_task_date"),
|
|
lastActive: timestamp("last_active"), // Track daily activity for streaks
|
|
showOnLeaderboard: boolean("show_on_leaderboard").default(true).notNull(), // Privacy setting
|
|
isSearchable: boolean("is_searchable").notNull().default(false), // Privacy setting
|
|
apiKey: text("api_key"), // For MCP Server access
|
|
aiEnabled: boolean("ai_enabled").notNull().default(true), // Feature flag per user
|
|
routineConfig: json("routine_config").$type<{ morningTime: string, eveningTime: string, enabled: boolean }>().default({ morningTime: "09:00", eveningTime: "17:00", enabled: false }),
|
|
lastMorningRoutine: timestamp("last_morning_routine"),
|
|
lastEveningRoutine: timestamp("last_evening_routine"),
|
|
|
|
// 2FA Fields
|
|
is2faEnabled: boolean("is_2fa_enabled").notNull().default(false),
|
|
otpCode: text("otp_code"), // The temporary 6-digit code
|
|
otpExpiresAt: timestamp("otp_expires_at"),
|
|
language: text("language").notNull().default("en"), // 'en' | 'de'
|
|
workHours: json("work_hours").$type<{ start: string, end: string, days: number[] }>().default({ start: "09:00", end: "17:00", days: [1, 2, 3, 4, 5] }), // Deprecated in favor of availability? Keeping for now.
|
|
availability: json("availability").$type<{
|
|
work: { start: string, end: string, days: number[] },
|
|
personal: { start: string, end: string, days: number[] }
|
|
}>().default({
|
|
work: { start: "09:00", end: "17:00", days: [1, 2, 3, 4, 5] },
|
|
personal: { start: "18:00", end: "22:00", days: [1, 2, 3, 4, 5, 0, 6] } // Mon-Fri Evening + Weekend
|
|
}),
|
|
|
|
// ADHD Mode Fields
|
|
adhdMode: boolean("adhd_mode").notNull().default(false),
|
|
adhdSettings: json("adhd_settings").$type<ADHDSettings>().default({
|
|
reducedAnimations: false,
|
|
largerTargets: true,
|
|
breakReminderInterval: 45,
|
|
singleTaskModeDefault: false,
|
|
positiveMessagingLevel: 'normal',
|
|
hyperfocusProtection: true,
|
|
hyperfocusMaxMinutes: 90,
|
|
visualTimerEnabled: true,
|
|
quickWinThreshold: 10,
|
|
energyCheckInsEnabled: true,
|
|
}),
|
|
lastBreakAt: timestamp("last_break_at"),
|
|
currentEnergyLevel: text("current_energy_level").default("medium"), // 'low' | 'medium' | 'high'
|
|
todayEnergyCheckedIn: boolean("today_energy_checked_in").default(false),
|
|
});
|
|
|
|
export const systemSettings = pgTable("system_settings", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
key: text("key").notNull().unique(), // e.g., 'registration_enabled'
|
|
value: text("value").notNull(), // e.g., 'true'
|
|
updatedAt: timestamp("updated_at").defaultNow(),
|
|
});
|
|
|
|
export const labels = pgTable("labels", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
name: text("name").notNull(),
|
|
color: text("color").notNull(),
|
|
creatorId: varchar("creator_id").references(() => users.id),
|
|
domain: text("domain").notNull().default("neutral"), // 'work' | 'personal' | 'neutral'
|
|
});
|
|
|
|
export const sharedLabels = pgTable("shared_labels", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
labelId: varchar("label_id").references(() => labels.id).notNull(),
|
|
sharedByUserId: varchar("shared_by_user_id").references(() => users.id).notNull(),
|
|
sharedWithUserId: varchar("shared_with_user_id").references(() => users.id).notNull(),
|
|
permission: text("permission").notNull().default("read"), // 'read' | 'write'
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
export const tasks = pgTable("tasks", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
title: text("title").notNull(),
|
|
description: text("description"),
|
|
status: text("status").notNull().default("todo"), // 'todo' | 'inProgress' | 'done'
|
|
priority: text("priority").notNull().default("medium"), // 'low' | 'medium' | 'high'
|
|
dueDate: timestamp("due_date"),
|
|
timeTracked: integer("time_tracked").notNull().default(0), // in minutes
|
|
isTracking: boolean("is_tracking").notNull().default(false),
|
|
projectId: text("project_id"),
|
|
notes: text("notes"),
|
|
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
|
|
|
|
// Recurrence
|
|
isRecurring: boolean("is_recurring").notNull().default(false),
|
|
recurrenceInterval: text("recurrence_interval"), // 'daily', 'weekly', 'monthly', 'yearly'
|
|
recurrenceIntervalValue: integer("recurrence_interval_value").default(1),
|
|
recurrenceDays: integer("recurrence_days").array(), // 0=Sunday, 1=Monday etc.
|
|
recurrenceEnd: timestamp("recurrence_end"),
|
|
});
|
|
|
|
// Single Task Sharing
|
|
export const sharedTasks = pgTable("shared_tasks", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
taskId: varchar("task_id").references(() => tasks.id).notNull(),
|
|
sharedByUserId: varchar("shared_by_user_id").references(() => users.id).notNull(),
|
|
sharedWithUserId: varchar("shared_with_user_id").references(() => users.id).notNull(),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
// Global "Share All" Access
|
|
export const userTaskAccess = pgTable("user_task_access", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
ownerId: varchar("owner_id").references(() => users.id).notNull(),
|
|
viewerId: varchar("viewer_id").references(() => users.id).notNull(),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
export const insertUserSchema = createInsertSchema(users).pick({
|
|
username: true,
|
|
password: true,
|
|
email: true,
|
|
showOnLeaderboard: true,
|
|
isSearchable: true,
|
|
aiEnabled: true,
|
|
language: true,
|
|
workHours: true,
|
|
availability: true,
|
|
adhdMode: true,
|
|
adhdSettings: true,
|
|
});
|
|
|
|
export const registerSchema = insertUserSchema;
|
|
|
|
export const loginSchema = z.object({
|
|
username: z.string().min(1, "Username is required"),
|
|
password: z.string().min(1, "Password is required"),
|
|
rememberMe: z.boolean().optional(),
|
|
});
|
|
|
|
export type LoginUser = z.infer<typeof loginSchema>;
|
|
|
|
export const insertSystemSettingsSchema = createInsertSchema(systemSettings).omit({
|
|
id: true,
|
|
updatedAt: true,
|
|
});
|
|
|
|
export const insertLabelSchema = createInsertSchema(labels).omit({
|
|
id: true,
|
|
});
|
|
|
|
export const insertTaskSchema = createInsertSchema(tasks, {
|
|
dueDate: z.coerce.date().nullable().optional(),
|
|
startDate: z.coerce.date().nullable().optional(),
|
|
}).omit({
|
|
id: true,
|
|
userId: true, // We will set this server-side
|
|
});
|
|
|
|
|
|
// Schema exports for sharing
|
|
export const insertSharedTaskSchema = createInsertSchema(sharedTasks).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export const insertUserTaskAccessSchema = createInsertSchema(userTaskAccess).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export type InsertUser = z.infer<typeof insertUserSchema>;
|
|
export type User = typeof users.$inferSelect;
|
|
export type InsertSystemSettings = z.infer<typeof insertSystemSettingsSchema>;
|
|
export type SystemSettings = typeof systemSettings.$inferSelect;
|
|
export type InsertLabel = z.infer<typeof insertLabelSchema>;
|
|
export type Label = typeof labels.$inferSelect;
|
|
export type SharedLabel = typeof sharedLabels.$inferSelect;
|
|
export type InsertTask = z.infer<typeof insertTaskSchema>;
|
|
export type Task = typeof tasks.$inferSelect;
|
|
export type InsertSharedTask = z.infer<typeof insertSharedTaskSchema>;
|
|
export type SharedTask = typeof sharedTasks.$inferSelect;
|
|
export type InsertUserTaskAccess = z.infer<typeof insertUserTaskAccessSchema>;
|
|
export type UserTaskAccess = typeof userTaskAccess.$inferSelect;
|
|
|
|
|
|
export const notes = pgTable("notes", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
title: text("title"),
|
|
content: text("content"),
|
|
taskId: varchar("task_id").references(() => tasks.id),
|
|
userId: varchar("user_id").references(() => users.id),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
export const insertNoteSchema = createInsertSchema(notes).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export type InsertNote = z.infer<typeof insertNoteSchema>;
|
|
export const xpEvents = pgTable("xp_events", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").references(() => users.id),
|
|
amount: integer("amount").notNull(),
|
|
source: text("source").notNull(), // 'task_completion', 'daily_streak', 'bonus'
|
|
taskId: varchar("task_id"),
|
|
details: json("details"), // For snapshotting task title etc.
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
export const goals = pgTable("goals", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").references(() => users.id),
|
|
title: text("title").notNull(),
|
|
target: integer("target").notNull(),
|
|
current: integer("current").notNull().default(0),
|
|
type: text("type").notNull(), // 'weekly_tasks', 'total_xp', 'streak'
|
|
deadline: timestamp("deadline"),
|
|
completed: boolean("completed").default(false),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
export const insertXpEventSchema = createInsertSchema(xpEvents).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export const insertGoalSchema = createInsertSchema(goals).omit({
|
|
id: true,
|
|
current: true,
|
|
completed: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export type InsertXpEvent = z.infer<typeof insertXpEventSchema>;
|
|
export type XpEvent = typeof xpEvents.$inferSelect;
|
|
export type InsertGoal = z.infer<typeof insertGoalSchema>;
|
|
export type Goal = typeof goals.$inferSelect;
|
|
export type Note = typeof notes.$inferSelect;
|
|
|
|
export const rewards = pgTable("rewards", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
title: text("title").notNull(),
|
|
description: text("description"),
|
|
cost: integer("cost").notNull(),
|
|
icon: text("icon").notNull(),
|
|
type: text("type").notNull().default("virtual"), // 'virtual', 'real_world', 'feature_unlock'
|
|
isSystem: boolean("is_system").default(true),
|
|
userId: varchar("user_id").references(() => users.id), // Nullable for system rewards
|
|
});
|
|
|
|
export const userRewards = pgTable("user_rewards", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").references(() => users.id),
|
|
rewardId: varchar("reward_id").references(() => rewards.id),
|
|
purchasedAt: timestamp("purchased_at").defaultNow(),
|
|
});
|
|
|
|
export const insertRewardSchema = createInsertSchema(rewards).omit({
|
|
id: true,
|
|
});
|
|
|
|
export type InsertReward = z.infer<typeof insertRewardSchema>;
|
|
export type Reward = typeof rewards.$inferSelect;
|
|
export type UserReward = typeof userRewards.$inferSelect;
|
|
|
|
export const insertUserRewardSchema = createInsertSchema(userRewards).omit({
|
|
id: true,
|
|
purchasedAt: true,
|
|
});
|
|
|
|
// Session table (managed by connect-pg-simple but defined here to avoid drizzle-kit deletion)
|
|
export const session = pgTable("session", {
|
|
sid: varchar("sid").primaryKey(),
|
|
sess: json("sess").notNull(),
|
|
expire: timestamp("expire", { precision: 6 }).notNull(),
|
|
});
|
|
|
|
export const passwordResetTokens = pgTable("password_reset_tokens", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").references(() => users.id).notNull(),
|
|
token: text("token").notNull().unique(),
|
|
expiresAt: timestamp("expires_at").notNull(),
|
|
isUsed: boolean("is_used").default(false),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
export const insertPasswordResetTokenSchema = createInsertSchema(passwordResetTokens).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export type InsertPasswordResetToken = z.infer<typeof insertPasswordResetTokenSchema>;
|
|
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;
|
|
|
|
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;
|
|
|
|
export const taskTimeLogs = pgTable("task_time_logs", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
taskId: varchar("task_id").references(() => tasks.id).notNull(),
|
|
userId: varchar("user_id").references(() => users.id).notNull(),
|
|
timeSpent: integer("time_spent").notNull(), // in minutes
|
|
createdAt: timestamp("created_at").defaultNow(), // WHEN the time was logged
|
|
});
|
|
|
|
export const insertTaskTimeLogSchema = createInsertSchema(taskTimeLogs).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export type InsertTaskTimeLog = z.infer<typeof insertTaskTimeLogSchema>;
|
|
export type TaskTimeLog = typeof taskTimeLogs.$inferSelect;
|
|
|
|
// ============================================
|
|
// ADHD FEATURE TABLES
|
|
// ============================================
|
|
|
|
// Energy Logs - Track daily energy levels
|
|
export const energyLogs = pgTable("energy_logs", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").references(() => users.id).notNull(),
|
|
energyLevel: text("energy_level").notNull(), // 'low' | 'medium' | 'high'
|
|
notes: text("notes"),
|
|
loggedAt: timestamp("logged_at").defaultNow(),
|
|
});
|
|
|
|
export const insertEnergyLogSchema = createInsertSchema(energyLogs).omit({
|
|
id: true,
|
|
loggedAt: true,
|
|
});
|
|
|
|
export type InsertEnergyLog = z.infer<typeof insertEnergyLogSchema>;
|
|
export type EnergyLog = typeof energyLogs.$inferSelect;
|
|
|
|
// Break Logs - Track when users take breaks
|
|
export const breakLogs = pgTable("break_logs", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").references(() => users.id).notNull(),
|
|
breakType: text("break_type").notNull(), // 'stretch' | 'water' | 'walk' | 'eyes' | 'snack' | 'breathe' | 'custom'
|
|
durationMinutes: integer("duration_minutes").notNull().default(5),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
export const insertBreakLogSchema = createInsertSchema(breakLogs).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export type InsertBreakLog = z.infer<typeof insertBreakLogSchema>;
|
|
export type BreakLog = typeof breakLogs.$inferSelect;
|
|
|
|
// Body Doubling Sessions
|
|
export const bodyDoublingSessions = pgTable("body_doubling_sessions", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
hostId: varchar("host_id").references(() => users.id).notNull(),
|
|
title: text("title").notNull(),
|
|
sessionType: text("session_type").notNull().default("focus"), // 'focus' | 'brainstorm' | 'admin' | 'creative'
|
|
startsAt: timestamp("starts_at").notNull(),
|
|
durationMinutes: integer("duration_minutes").notNull().default(50),
|
|
maxParticipants: integer("max_participants").notNull().default(5),
|
|
isPublic: boolean("is_public").notNull().default(true),
|
|
status: text("status").notNull().default("scheduled"), // 'scheduled' | 'active' | 'completed' | 'cancelled'
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
export const insertBodyDoublingSessionSchema = createInsertSchema(bodyDoublingSessions).omit({
|
|
id: true,
|
|
status: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export type InsertBodyDoublingSession = z.infer<typeof insertBodyDoublingSessionSchema>;
|
|
export type BodyDoublingSession = typeof bodyDoublingSessions.$inferSelect;
|
|
|
|
// Session Participants
|
|
export const sessionParticipants = pgTable("session_participants", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
sessionId: varchar("session_id").references(() => bodyDoublingSessions.id).notNull(),
|
|
userId: varchar("user_id").references(() => users.id).notNull(),
|
|
joinedAt: timestamp("joined_at").defaultNow(),
|
|
leftAt: timestamp("left_at"),
|
|
});
|
|
|
|
export const insertSessionParticipantSchema = createInsertSchema(sessionParticipants).omit({
|
|
id: true,
|
|
joinedAt: true,
|
|
leftAt: true,
|
|
});
|
|
|
|
export type InsertSessionParticipant = z.infer<typeof insertSessionParticipantSchema>;
|
|
export type SessionParticipant = typeof sessionParticipants.$inferSelect;
|
|
|
|
// Daily Challenges for ADHD motivation
|
|
export const dailyChallenges = pgTable("daily_challenges", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").references(() => users.id).notNull(),
|
|
challengeType: text("challenge_type").notNull(), // 'complete_tasks' | 'take_breaks' | 'quick_wins' | 'focus_time' | 'use_timer'
|
|
title: text("title").notNull(),
|
|
description: text("description"),
|
|
target: integer("target").notNull(),
|
|
progress: integer("progress").notNull().default(0),
|
|
xpReward: integer("xp_reward").notNull(),
|
|
challengeDate: date("challenge_date").notNull(),
|
|
completedAt: timestamp("completed_at"),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
});
|
|
|
|
export const insertDailyChallengeSchema = createInsertSchema(dailyChallenges).omit({
|
|
id: true,
|
|
progress: true,
|
|
completedAt: true,
|
|
createdAt: true,
|
|
});
|
|
|
|
export type InsertDailyChallenge = z.infer<typeof insertDailyChallengeSchema>;
|
|
export type DailyChallenge = typeof dailyChallenges.$inferSelect;
|
|
|
|
// Focus Sessions - Track focused work periods
|
|
export const focusSessions = pgTable("focus_sessions", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").references(() => users.id).notNull(),
|
|
taskId: varchar("task_id").references(() => tasks.id),
|
|
sessionType: text("session_type").notNull().default("pomodoro"), // 'pomodoro' | 'five_minute' | 'deep_focus' | 'visual_timer'
|
|
plannedMinutes: integer("planned_minutes").notNull(),
|
|
actualMinutes: integer("actual_minutes"),
|
|
wasCompleted: boolean("was_completed").default(false),
|
|
startedAt: timestamp("started_at").defaultNow(),
|
|
endedAt: timestamp("ended_at"),
|
|
});
|
|
|
|
export const insertFocusSessionSchema = createInsertSchema(focusSessions).omit({
|
|
id: true,
|
|
actualMinutes: true,
|
|
wasCompleted: true,
|
|
startedAt: true,
|
|
endedAt: true,
|
|
});
|
|
|
|
export type InsertFocusSession = z.infer<typeof insertFocusSessionSchema>;
|
|
export type FocusSession = typeof focusSessions.$inferSelect;
|
|
|
|
// Encouragements/Affirmations shown to users
|
|
export const encouragementLogs = pgTable("encouragement_logs", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").references(() => users.id).notNull(),
|
|
encouragementType: text("encouragement_type").notNull(), // 'task_complete' | 'returning' | 'struggling' | 'streak_broken' | 'milestone'
|
|
message: text("message").notNull(),
|
|
shownAt: timestamp("shown_at").defaultNow(),
|
|
});
|
|
|
|
export type EncouragementLog = typeof encouragementLogs.$inferSelect;
|
|
|
|
// ============================================
|
|
// PUSH NOTIFICATIONS
|
|
// ============================================
|
|
|
|
// Push Subscriptions - Store Web Push subscriptions for each user/device
|
|
export const pushSubscriptions = pgTable("push_subscriptions", {
|
|
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
|
|
userId: varchar("user_id").references(() => users.id).notNull(),
|
|
endpoint: text("endpoint").notNull().unique(),
|
|
p256dh: text("p256dh").notNull(), // Public key
|
|
auth: text("auth").notNull(), // Auth secret
|
|
userAgent: text("user_agent"), // To identify device
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
lastUsedAt: timestamp("last_used_at").defaultNow(),
|
|
});
|
|
|
|
export const insertPushSubscriptionSchema = createInsertSchema(pushSubscriptions).omit({
|
|
id: true,
|
|
createdAt: true,
|
|
lastUsedAt: true,
|
|
});
|
|
|
|
export type InsertPushSubscription = z.infer<typeof insertPushSubscriptionSchema>;
|
|
export type PushSubscription = typeof pushSubscriptions.$inferSelect;
|