feat: Add user management APIs, status enum, enhanced notifications

- Add updateUser and resetUserPassword admin endpoints
- Change company status from boolean to enum (registered, lead, customer, inactive)
- Add 'important' event type to calendar validators and email templates
- Add 1-hour-before event notifications cron job
- Add 18:00 evening notifications for next-day events
- Add contact description field support
- Fix count() function usage in admin service
- Add SQL migrations for schema changes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
richardtekula
2026-01-15 09:41:29 +01:00
parent 5d01fc9542
commit 70fa080455
13 changed files with 423 additions and 19 deletions

View File

@@ -0,0 +1,24 @@
-- Fix: Convert status column from boolean to company_status enum
-- The previous migration renamed is_active to status but kept the boolean type
-- Step 1: Add a temporary column with the correct enum type
ALTER TABLE "companies" ADD COLUMN "status_new" company_status;
-- Step 2: Migrate data - convert boolean to enum
-- true (active) -> 'registered', false (inactive) -> 'inactive'
UPDATE "companies" SET "status_new" =
CASE
WHEN "status"::text = 'true' OR "status"::text = 't' THEN 'registered'::company_status
WHEN "status"::text = 'false' OR "status"::text = 'f' THEN 'inactive'::company_status
ELSE 'registered'::company_status
END;
-- Step 3: Drop the old column
ALTER TABLE "companies" DROP COLUMN "status";
-- Step 4: Rename the new column
ALTER TABLE "companies" RENAME COLUMN "status_new" TO "status";
-- Step 5: Set the default and not null constraint
ALTER TABLE "companies" ALTER COLUMN "status" SET DEFAULT 'registered';
ALTER TABLE "companies" ALTER COLUMN "status" SET NOT NULL;

View File

@@ -0,0 +1,38 @@
-- Hotfix Migration Script
-- Run this manually after accepting drizzle-kit push prompts
-- 1. Create the company_status enum type if it doesn't exist
DO $$ BEGIN
CREATE TYPE company_status AS ENUM ('registered', 'lead', 'customer', 'inactive');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- 2. Add the status column to companies table if it doesn't exist
DO $$ BEGIN
ALTER TABLE companies ADD COLUMN status company_status NOT NULL DEFAULT 'registered';
EXCEPTION
WHEN duplicate_column THEN null;
END $$;
-- 3. Migrate data from is_active to status (if is_active column exists)
DO $$ BEGIN
UPDATE companies SET status = CASE
WHEN is_active = true THEN 'customer'::company_status
ELSE 'inactive'::company_status
END;
EXCEPTION
WHEN undefined_column THEN null;
END $$;
-- 4. Drop the is_active column (optional - run after verifying migration)
-- ALTER TABLE companies DROP COLUMN IF EXISTS is_active;
-- 5. Add description column to personal_contacts if it doesn't exist
DO $$ BEGIN
ALTER TABLE personal_contacts ADD COLUMN description TEXT;
EXCEPTION
WHEN duplicate_column THEN null;
END $$;
-- Note: Event type 'important' is stored as text, no schema change needed for events table

View File

@@ -5,6 +5,7 @@ export const roleEnum = pgEnum('role', ['admin', 'member']);
export const projectStatusEnum = pgEnum('project_status', ['active', 'completed', 'on_hold', 'cancelled']);
export const todoStatusEnum = pgEnum('todo_status', ['pending', 'in_progress', 'completed', 'cancelled']);
export const todoPriorityEnum = pgEnum('todo_priority', ['low', 'medium', 'high', 'urgent']);
export const companyStatusEnum = pgEnum('company_status', ['registered', 'lead', 'customer', 'inactive']);
// Users table - používatelia systému
export const users = pgTable('users', {
@@ -89,6 +90,7 @@ export const personalContacts = pgTable('personal_contacts', {
phone: text('phone').notNull(),
email: text('email').notNull(),
secondaryEmail: text('secondary_email'),
description: text('description'), // popis kontaktu
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
}, (table) => ({
@@ -128,7 +130,7 @@ export const companies = pgTable('companies', {
phone: text('phone'),
email: text('email'),
website: text('website'),
isActive: boolean('is_active').default(true).notNull(), // či je firma aktívna
status: companyStatusEnum('status').default('registered').notNull(), // stav firmy
createdBy: uuid('created_by').references(() => users.id, { onDelete: 'set null' }),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),