178 lines
7.1 KiB
Python

from __future__ import annotations
import json
import sqlite3
import threading
from pathlib import Path
from typing import Any
class Database:
def __init__(self, database_path: Path, employee_seed_path: Path):
database_path.parent.mkdir(parents=True, exist_ok=True)
self.connection = sqlite3.connect(database_path, check_same_thread=False)
self.connection.row_factory = sqlite3.Row
self._lock = threading.RLock()
self._migrate()
self.load_employees(employee_seed_path)
def close(self) -> None:
with self._lock:
self.connection.close()
def _migrate(self) -> None:
with self._lock:
self.connection.executescript(
"""
CREATE TABLE IF NOT EXISTS employees (
id INTEGER PRIMARY KEY AUTOINCREMENT,
feishu_user_id TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
department TEXT NOT NULL DEFAULT '',
manager TEXT NOT NULL DEFAULT '',
role TEXT NOT NULL DEFAULT 'staff',
active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS daily_reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
feishu_user_id TEXT NOT NULL,
employee_name TEXT NOT NULL,
report_date TEXT NOT NULL,
today_done TEXT NOT NULL,
tomorrow_plan TEXT NOT NULL,
blockers TEXT NOT NULL DEFAULT '',
help_needed TEXT NOT NULL DEFAULT '',
submitted_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(feishu_user_id, report_date)
);
"""
)
try:
self.connection.execute("ALTER TABLE employees ADD COLUMN role TEXT NOT NULL DEFAULT 'staff'")
except sqlite3.OperationalError as error:
if "duplicate column name" not in str(error).lower():
raise
self.connection.commit()
def load_employees(self, employee_seed_path: Path) -> None:
if not employee_seed_path.exists():
return
employees = json.loads(employee_seed_path.read_text(encoding="utf-8"))
self.upsert_employees(employees)
def upsert_employees(self, employees: list[dict[str, Any]]) -> None:
with self._lock, self.connection:
for employee in employees:
role = employee.get("role")
if role is None:
existing = self.connection.execute(
"SELECT role FROM employees WHERE feishu_user_id = ?",
(employee["feishu_user_id"],),
).fetchone()
role = existing["role"] if existing else "staff"
self.connection.execute(
"""
INSERT INTO employees (feishu_user_id, name, department, manager, role, active, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT(feishu_user_id) DO UPDATE SET
name = excluded.name,
department = excluded.department,
manager = excluded.manager,
role = excluded.role,
active = excluded.active,
updated_at = CURRENT_TIMESTAMP
""",
(
employee["feishu_user_id"],
employee["name"],
employee.get("department", ""),
employee.get("manager", ""),
role,
0 if employee.get("active") is False else 1,
),
)
def upsert_employee(self, employee: dict[str, Any]) -> None:
self.upsert_employees([employee])
def list_active_employees(self) -> list[dict[str, Any]]:
with self._lock:
rows = self.connection.execute(
"""
SELECT feishu_user_id, name, department, manager, role, active
FROM employees
WHERE active = 1
ORDER BY department, name
"""
).fetchall()
return [dict(row) for row in rows]
def find_employee(self, feishu_user_id: str) -> dict[str, Any] | None:
with self._lock:
row = self.connection.execute(
"""
SELECT feishu_user_id, name, department, manager, role, active
FROM employees
WHERE feishu_user_id = ?
""",
(feishu_user_id,),
).fetchone()
return dict(row) if row else None
def upsert_report(self, report: dict[str, Any]) -> None:
with self._lock, self.connection:
self.connection.execute(
"""
INSERT INTO daily_reports (
feishu_user_id, employee_name, report_date, today_done, tomorrow_plan,
blockers, help_needed, submitted_at, updated_at
)
VALUES (
:feishu_user_id, :employee_name, :report_date, :today_done, :tomorrow_plan,
:blockers, :help_needed, :submitted_at, :updated_at
)
ON CONFLICT(feishu_user_id, report_date) DO UPDATE SET
employee_name = excluded.employee_name,
today_done = excluded.today_done,
tomorrow_plan = excluded.tomorrow_plan,
blockers = excluded.blockers,
help_needed = excluded.help_needed,
updated_at = excluded.updated_at
""",
report,
)
def list_reports_for_date(self, report_date: str) -> list[dict[str, Any]]:
with self._lock:
rows = self.connection.execute(
"""
SELECT id, feishu_user_id, employee_name, report_date, today_done,
tomorrow_plan, blockers, help_needed, submitted_at, updated_at
FROM daily_reports
WHERE report_date = ?
ORDER BY employee_name
""",
(report_date,),
).fetchall()
return [dict(row) for row in rows]
def list_reports_for_employee(self, feishu_user_id: str, limit: int = 10) -> list[dict[str, Any]]:
with self._lock:
rows = self.connection.execute(
"""
SELECT id, feishu_user_id, employee_name, report_date, today_done,
tomorrow_plan, blockers, help_needed, submitted_at, updated_at
FROM daily_reports
WHERE feishu_user_id = ?
ORDER BY report_date DESC, updated_at DESC
LIMIT ?
""",
(feishu_user_id, limit),
).fetchall()
return [dict(row) for row in rows]