fix email issues, add company,project,todos
This commit is contained in:
54
src/db/create_project_users_table.js
Normal file
54
src/db/create_project_users_table.js
Normal 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();
|
||||
31
src/db/migrations/add_company_link_and_reminders.sql
Normal file
31
src/db/migrations/add_company_link_and_reminders.sql
Normal 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 $$;
|
||||
107
src/db/migrations/add_crm_tables.sql
Normal file
107
src/db/migrations/add_crm_tables.sql
Normal 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);
|
||||
21
src/db/migrations/add_project_users.sql
Normal file
21
src/db/migrations/add_project_users.sql
Normal 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)';
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user