fix email issues, add company,project,todos

This commit is contained in:
richardtekula
2025-11-21 13:56:02 +01:00
parent bb851639b8
commit ca93b6f2d2
30 changed files with 4860 additions and 1066 deletions

View File

@@ -0,0 +1,54 @@
import { db } from '../config/database.js';
import { sql } from 'drizzle-orm';
async function createProjectUsersTable() {
console.log('⏳ Creating project_users table...');
try {
// Check if table exists
const result = await db.execute(sql`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'project_users'
);
`);
const tableExists = result.rows[0]?.exists;
if (tableExists) {
console.log('✅ project_users table already exists');
process.exit(0);
}
// Create the table
await db.execute(sql`
CREATE TABLE IF NOT EXISTS project_users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role TEXT,
added_by UUID REFERENCES users(id) ON DELETE SET NULL,
added_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT project_user_unique UNIQUE(project_id, user_id)
);
`);
// Create indexes
await db.execute(sql`
CREATE INDEX IF NOT EXISTS idx_project_users_project_id ON project_users(project_id);
`);
await db.execute(sql`
CREATE INDEX IF NOT EXISTS idx_project_users_user_id ON project_users(user_id);
`);
console.log('✅ project_users table created successfully');
process.exit(0);
} catch (error) {
console.error('❌ Failed to create table:', error);
process.exit(1);
}
}
createProjectUsersTable();

View File

@@ -0,0 +1,31 @@
-- Add company_id to contacts table
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name='contacts' AND column_name='company_id'
) THEN
ALTER TABLE contacts ADD COLUMN company_id UUID REFERENCES companies(id) ON DELETE SET NULL;
CREATE INDEX idx_contacts_company_id ON contacts(company_id);
END IF;
END $$;
-- Add reminder fields to notes table
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name='notes' AND column_name='reminder_date'
) THEN
ALTER TABLE notes ADD COLUMN reminder_date TIMESTAMP;
CREATE INDEX idx_notes_reminder_date ON notes(reminder_date) WHERE reminder_date IS NOT NULL;
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name='notes' AND column_name='reminder_sent'
) THEN
ALTER TABLE notes ADD COLUMN reminder_sent BOOLEAN NOT NULL DEFAULT false;
CREATE INDEX idx_notes_reminder_pending ON notes(reminder_date, reminder_sent) WHERE reminder_date IS NOT NULL AND reminder_sent = false;
END IF;
END $$;

View File

@@ -0,0 +1,107 @@
-- Add new enum types
DO $$ BEGIN
CREATE TYPE project_status AS ENUM('active', 'completed', 'on_hold', 'cancelled');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
CREATE TYPE todo_status AS ENUM('pending', 'in_progress', 'completed', 'cancelled');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
CREATE TYPE todo_priority AS ENUM('low', 'medium', 'high', 'urgent');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Create companies table
CREATE TABLE IF NOT EXISTS companies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
description TEXT,
address TEXT,
city TEXT,
country TEXT,
phone TEXT,
email TEXT,
website TEXT,
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Create projects table
CREATE TABLE IF NOT EXISTS projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
description TEXT,
company_id UUID REFERENCES companies(id) ON DELETE CASCADE,
status project_status NOT NULL DEFAULT 'active',
start_date TIMESTAMP,
end_date TIMESTAMP,
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Create todos table
CREATE TABLE IF NOT EXISTS todos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL,
description TEXT,
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
company_id UUID REFERENCES companies(id) ON DELETE CASCADE,
assigned_to UUID REFERENCES users(id) ON DELETE SET NULL,
status todo_status NOT NULL DEFAULT 'pending',
priority todo_priority NOT NULL DEFAULT 'medium',
due_date TIMESTAMP,
completed_at TIMESTAMP,
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Create notes table
CREATE TABLE IF NOT EXISTS notes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT,
content TEXT NOT NULL,
company_id UUID REFERENCES companies(id) ON DELETE CASCADE,
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
todo_id UUID REFERENCES todos(id) ON DELETE CASCADE,
contact_id UUID REFERENCES contacts(id) ON DELETE CASCADE,
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Add project_id to timesheets table if not exists
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name='timesheets' AND column_name='project_id'
) THEN
ALTER TABLE timesheets ADD COLUMN project_id UUID REFERENCES projects(id) ON DELETE SET NULL;
END IF;
END $$;
-- Create indexes for better query performance
CREATE INDEX IF NOT EXISTS idx_companies_created_at ON companies(created_at);
CREATE INDEX IF NOT EXISTS idx_projects_company_id ON projects(company_id);
CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);
CREATE INDEX IF NOT EXISTS idx_projects_created_at ON projects(created_at);
CREATE INDEX IF NOT EXISTS idx_todos_project_id ON todos(project_id);
CREATE INDEX IF NOT EXISTS idx_todos_company_id ON todos(company_id);
CREATE INDEX IF NOT EXISTS idx_todos_assigned_to ON todos(assigned_to);
CREATE INDEX IF NOT EXISTS idx_todos_status ON todos(status);
CREATE INDEX IF NOT EXISTS idx_todos_created_at ON todos(created_at);
CREATE INDEX IF NOT EXISTS idx_notes_company_id ON notes(company_id);
CREATE INDEX IF NOT EXISTS idx_notes_project_id ON notes(project_id);
CREATE INDEX IF NOT EXISTS idx_notes_todo_id ON notes(todo_id);
CREATE INDEX IF NOT EXISTS idx_notes_contact_id ON notes(contact_id);
CREATE INDEX IF NOT EXISTS idx_notes_created_at ON notes(created_at);
CREATE INDEX IF NOT EXISTS idx_timesheets_project_id ON timesheets(project_id);

View File

@@ -0,0 +1,21 @@
-- Migration: Add project_users junction table for project team management
-- Created: 2025-11-21
-- Description: Allows many-to-many relationship between projects and users
-- Create project_users junction table
CREATE TABLE IF NOT EXISTS project_users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role TEXT,
added_by UUID REFERENCES users(id) ON DELETE SET NULL,
added_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT project_user_unique UNIQUE(project_id, user_id)
);
-- Create indexes for better query performance
CREATE INDEX IF NOT EXISTS idx_project_users_project_id ON project_users(project_id);
CREATE INDEX IF NOT EXISTS idx_project_users_user_id ON project_users(user_id);
-- Add comment
COMMENT ON TABLE project_users IS 'Junction table for many-to-many relationship between projects and users (project team members)';

View File

@@ -1,7 +1,10 @@
import { pgTable, text, timestamp, boolean, uuid, pgEnum, unique, integer } from 'drizzle-orm/pg-core';
// Role enum
// Enums
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']);
// Users table - používatelia systému
export const users = pgTable('users', {
@@ -63,6 +66,7 @@ export const auditLogs = pgTable('audit_logs', {
export const contacts = pgTable('contacts', {
id: uuid('id').primaryKey().defaultRandom(),
emailAccountId: uuid('email_account_id').references(() => emailAccounts.id, { onDelete: 'cascade' }).notNull(),
companyId: uuid('company_id').references(() => companies.id, { onDelete: 'set null' }), // kontakt môže byť linknutý k firme
email: text('email').notNull(),
name: text('name'),
notes: text('notes'),
@@ -96,10 +100,86 @@ export const emails = pgTable('emails', {
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
// Companies table - firmy/spoločnosti
export const companies = pgTable('companies', {
id: uuid('id').primaryKey().defaultRandom(),
name: text('name').notNull(),
description: text('description'),
address: text('address'),
city: text('city'),
country: text('country'),
phone: text('phone'),
email: text('email'),
website: text('website'),
createdBy: uuid('created_by').references(() => users.id, { onDelete: 'set null' }),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
// Projects table - projekty
export const projects = pgTable('projects', {
id: uuid('id').primaryKey().defaultRandom(),
name: text('name').notNull(),
description: text('description'),
companyId: uuid('company_id').references(() => companies.id, { onDelete: 'cascade' }), // projekt môže patriť firme
status: projectStatusEnum('status').default('active').notNull(),
startDate: timestamp('start_date'),
endDate: timestamp('end_date'),
createdBy: uuid('created_by').references(() => users.id, { onDelete: 'set null' }),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
// Project Users - many-to-many medzi projects a users (tím projektu)
export const projectUsers = pgTable('project_users', {
id: uuid('id').primaryKey().defaultRandom(),
projectId: uuid('project_id').references(() => projects.id, { onDelete: 'cascade' }).notNull(),
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
role: text('role'), // napr. 'lead', 'member', 'viewer' - voliteľné
addedBy: uuid('added_by').references(() => users.id, { onDelete: 'set null' }), // kto pridal používateľa do projektu
addedAt: timestamp('added_at').defaultNow().notNull(),
}, (table) => ({
projectUserUnique: unique('project_user_unique').on(table.projectId, table.userId),
}));
// Todos table - úlohy/tasky
export const todos = pgTable('todos', {
id: uuid('id').primaryKey().defaultRandom(),
title: text('title').notNull(),
description: text('description'),
projectId: uuid('project_id').references(() => projects.id, { onDelete: 'cascade' }), // todo môže patriť projektu
companyId: uuid('company_id').references(() => companies.id, { onDelete: 'cascade' }), // alebo firme
assignedTo: uuid('assigned_to').references(() => users.id, { onDelete: 'set null' }), // komu je priradené
status: todoStatusEnum('status').default('pending').notNull(),
priority: todoPriorityEnum('priority').default('medium').notNull(),
dueDate: timestamp('due_date'),
completedAt: timestamp('completed_at'),
createdBy: uuid('created_by').references(() => users.id, { onDelete: 'set null' }),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
// Notes table - poznámky
export const notes = pgTable('notes', {
id: uuid('id').primaryKey().defaultRandom(),
title: text('title'),
content: text('content').notNull(),
companyId: uuid('company_id').references(() => companies.id, { onDelete: 'cascade' }), // poznámka k firme
projectId: uuid('project_id').references(() => projects.id, { onDelete: 'cascade' }), // alebo projektu
todoId: uuid('todo_id').references(() => todos.id, { onDelete: 'cascade' }), // alebo todo
contactId: uuid('contact_id').references(() => contacts.id, { onDelete: 'cascade' }), // alebo kontaktu
reminderDate: timestamp('reminder_date'), // dátum a čas pre reminder
reminderSent: boolean('reminder_sent').default(false).notNull(), // či už bol reminder odoslaný
createdBy: uuid('created_by').references(() => users.id, { onDelete: 'set null' }),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
// Timesheets table - nahrané timesheets od používateľov
export const timesheets = pgTable('timesheets', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), // kto nahral timesheet
projectId: uuid('project_id').references(() => projects.id, { onDelete: 'set null' }), // projekt ku ktorému patrí timesheet
fileName: text('file_name').notNull(), // originálny názov súboru
filePath: text('file_path').notNull(), // cesta k súboru na serveri
fileType: text('file_type').notNull(), // 'pdf' alebo 'xlsx'