- Full-stack: React 18 + Express + SQLite - Drag-and-drop kanban boards with @hello-pangea/dnd - Google App Password email integration (SMTP + IMAP) - Inbound email: create cards by sending emails - Reply-to-card: email replies become comments - Admin/user management with role-based access - Setup wizard: email config → admin creation - Checklists, time tracking, priorities, labels, due dates - Real-time notifications with activity feed - Beautiful HTML email templates
183 lines
5.7 KiB
JavaScript
183 lines
5.7 KiB
JavaScript
import Database from 'better-sqlite3';
|
|
import { fileURLToPath } from 'url';
|
|
import { dirname, join } from 'path';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const DB_PATH = join(__dirname, 'data', 'teamflow.db');
|
|
|
|
import { mkdirSync } from 'fs';
|
|
mkdirSync(join(__dirname, 'data'), { recursive: true });
|
|
|
|
const db = new Database(DB_PATH);
|
|
|
|
db.pragma('journal_mode = WAL');
|
|
db.pragma('foreign_keys = ON');
|
|
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS app_state (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
email TEXT UNIQUE NOT NULL,
|
|
password TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
avatar_color TEXT DEFAULT '#6366f1',
|
|
role TEXT DEFAULT 'member',
|
|
is_active INTEGER DEFAULT 1,
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS boards (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
title TEXT NOT NULL,
|
|
description TEXT DEFAULT '',
|
|
background TEXT DEFAULT 'gradient-blue',
|
|
is_archived INTEGER DEFAULT 0,
|
|
created_by INTEGER REFERENCES users(id),
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS board_members (
|
|
board_id INTEGER REFERENCES boards(id) ON DELETE CASCADE,
|
|
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
|
role TEXT DEFAULT 'member',
|
|
joined_at TEXT DEFAULT (datetime('now')),
|
|
PRIMARY KEY (board_id, user_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS lists (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
board_id INTEGER REFERENCES boards(id) ON DELETE CASCADE,
|
|
title TEXT NOT NULL,
|
|
position REAL NOT NULL,
|
|
is_archived INTEGER DEFAULT 0,
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS cards (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
list_id INTEGER REFERENCES lists(id) ON DELETE CASCADE,
|
|
title TEXT NOT NULL,
|
|
description TEXT DEFAULT '',
|
|
position REAL NOT NULL,
|
|
due_date TEXT,
|
|
priority TEXT DEFAULT 'none',
|
|
color TEXT DEFAULT '',
|
|
estimated_hours REAL,
|
|
time_spent REAL DEFAULT 0,
|
|
created_by INTEGER REFERENCES users(id),
|
|
assigned_to INTEGER REFERENCES users(id),
|
|
created_at TEXT DEFAULT (datetime('now')),
|
|
updated_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS labels (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
board_id INTEGER REFERENCES boards(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
color TEXT NOT NULL,
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS card_labels (
|
|
card_id INTEGER REFERENCES cards(id) ON DELETE CASCADE,
|
|
label_id INTEGER REFERENCES labels(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (card_id, label_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS card_comments (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
card_id INTEGER REFERENCES cards(id) ON DELETE CASCADE,
|
|
user_id INTEGER REFERENCES users(id),
|
|
content TEXT NOT NULL,
|
|
is_email_reply INTEGER DEFAULT 0,
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS card_activity (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
card_id INTEGER REFERENCES cards(id) ON DELETE CASCADE,
|
|
user_id INTEGER REFERENCES users(id),
|
|
action TEXT NOT NULL,
|
|
details TEXT DEFAULT '',
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS email_config (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
smtp_host TEXT DEFAULT 'smtp.gmail.com',
|
|
smtp_port INTEGER DEFAULT 587,
|
|
email TEXT NOT NULL,
|
|
app_password TEXT NOT NULL,
|
|
imap_host TEXT DEFAULT 'imap.gmail.com',
|
|
imap_port INTEGER DEFAULT 993,
|
|
inbound_enabled INTEGER DEFAULT 0,
|
|
inbound_folder TEXT DEFAULT 'INBOX',
|
|
board_email_prefix TEXT DEFAULT 'tf-',
|
|
configured_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS email_log (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
from_email TEXT,
|
|
to_email TEXT,
|
|
subject TEXT,
|
|
card_id INTEGER REFERENCES cards(id),
|
|
direction TEXT,
|
|
status TEXT,
|
|
error TEXT DEFAULT '',
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS email_tokens (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
card_id INTEGER REFERENCES cards(id) ON DELETE CASCADE,
|
|
user_id INTEGER REFERENCES users(id),
|
|
token TEXT UNIQUE NOT NULL,
|
|
purpose TEXT DEFAULT 'reply',
|
|
expires_at TEXT,
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS notifications (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
|
type TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
message TEXT DEFAULT '',
|
|
card_id INTEGER,
|
|
board_id INTEGER,
|
|
is_read INTEGER DEFAULT 0,
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS checklists (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
card_id INTEGER REFERENCES cards(id) ON DELETE CASCADE,
|
|
title TEXT NOT NULL DEFAULT 'Checklist',
|
|
position REAL DEFAULT 0,
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS checklist_items (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
checklist_id INTEGER REFERENCES checklists(id) ON DELETE CASCADE,
|
|
text TEXT NOT NULL,
|
|
is_checked INTEGER DEFAULT 0,
|
|
position REAL DEFAULT 0,
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_cards_list ON cards(list_id);
|
|
CREATE INDEX IF NOT EXISTS idx_card_activity_card ON card_activity(card_id);
|
|
CREATE INDEX IF NOT EXISTS idx_card_comments_card ON card_comments(card_id);
|
|
CREATE INDEX IF NOT EXISTS idx_notifications_user ON notifications(user_id, is_read);
|
|
CREATE INDEX IF NOT EXISTS idx_lists_board ON lists(board_id);
|
|
CREATE INDEX IF NOT EXISTS idx_email_tokens_token ON email_tokens(token);
|
|
`);
|
|
|
|
export default db;
|