feat: add social features, leaderboard, auth enhancements, and admin fixes
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Implement Social Features: Shared Tasks, Global Access, Privacy Settings (Leaderboard/Searchable). - Add Leaderboard Page and API. - Enhance Auth: Support Email/Username login, explicit duplicate registration errors. - Fix: Admin login password hash regression. - Refactor: Move to wouter for routing, add Admin Dashboard and User Management. - Add Setup Wizard. - Update UI with Sidebar and Gamification elements.
This commit is contained in:
+163
-1
@@ -6,7 +6,23 @@ import { z } from "zod";
|
||||
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"),
|
||||
showOnLeaderboard: boolean("show_on_leaderboard").notNull().default(false), // Privacy setting
|
||||
isSearchable: boolean("is_searchable").notNull().default(false), // Privacy setting
|
||||
});
|
||||
|
||||
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", {
|
||||
@@ -27,24 +43,170 @@ export const tasks = pgTable("tasks", {
|
||||
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
|
||||
dependencies: text("dependencies").array(), // Array of task IDs
|
||||
userId: varchar("user_id").references(() => users.id), // Added for ownership
|
||||
});
|
||||
|
||||
// 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,
|
||||
});
|
||||
|
||||
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"),
|
||||
});
|
||||
|
||||
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).omit({
|
||||
export const insertTaskSchema = createInsertSchema(tasks, {
|
||||
dueDate: z.coerce.date().nullable(),
|
||||
}).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 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"),
|
||||
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),
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
export type InsertUserReward = z.infer<typeof insertUserRewardSchema>;
|
||||
|
||||
Reference in New Issue
Block a user