Commit Graph
14 Commits
Author SHA1 Message Date
ogrechkoandClaude Sonnet 5 14fe8c0445 Fix silent login failure on infra without HTTPS yet, and build-time SQLITE_BUSY
Session cookie always set Secure (tied to NODE_ENV=production, hardcoded in
the Dockerfile), which the browser silently drops over plain HTTP — looked
like login accepted the password but bounced straight back to /login. Secure
now only drops when COOKIE_ALLOW_INSECURE=true, for temporary use before the
HTTPS reverse proxy is wired up.

Also fixed pragma order in db/client.ts: busy_timeout must be set before
journal_mode, since switching to WAL itself takes a momentary exclusive lock
that isn't covered by a busy_timeout set afterward — caused an intermittent
SQLITE_BUSY during `next build`'s parallel page-data collection.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012o9j9RezxbZVKQMrB7oRLY
2026-08-05 07:47:22 +00:00
ogrechkoandClaude Sonnet 5 fa7bda7a0f Bump build-time SQLITE_BUSY timeout from 5s to 30s
Docker build started failing intermittently again with more routes now
collecting page data concurrently — 5s wasn't always enough for workers
racing to init the fresh build-time db.sqlite. This only costs time
during that one-off build step, not runtime.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
2026-07-31 16:44:38 +00:00
ogrechkoandClaude Sonnet 5 904f09ff09 Add LDAP login for the customer portal as a backup to the personal link
Customers now have a second way in besides their email/Telegram link:
/portal-login, authenticating against the same LDAP directory used for
staff. On success it looks up (or creates) their customer record by
email — reusing findOrCreateCustomerByEmail, the same JIT pattern
already used for the email channel — so a lost link never strands them
as long as their LDAP account still resolves to the same email.

No local password for customers (LDAP only) — the personal link stays
the primary path, this is just resilience if it's lost or Telegram gets
blocked.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
2026-07-31 16:43:29 +00:00
ogrechkoandClaude Sonnet 5 cb12711e91 Add account management and LDAP login
Admins can now create local agent/admin accounts and configure LDAP
directly from the UI (Настройки → Аккаунты / LDAP), with login trying
LDAP first and falling back to the local password. This is the first
place users.role is actually enforced (requireAdminSession).

Fixed a bug in the generated 0005 migration: the INSERT into __new_users
selected auth_source from the old users table, which doesn't have that
column yet — caused drizzle-kit migrate to fail silently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
2026-07-27 08:56:08 +00:00
ogrechkoandClaude Sonnet 5 8121d8aa51 Add busy_timeout pragma to avoid SQLITE_BUSY during docker build
Next's build-time page-data collection evaluates route modules across
multiple worker processes concurrently. On a fresh (empty) data volume
— as in a clean docker build — two workers racing to create/open
db.sqlite for the first time hit SQLITE_BUSY instead of just waiting the
few ms for the other's lock. Reproduced by this session's docker build
after adding the attachments route; local builds never hit it since the
dev data/ directory already existed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
2026-07-26 17:34:48 +00:00
ogrechkoandClaude Sonnet 5 89b9c5eaac Deferred backlog: attachments, canned responses, tags, search, notifications, password change
Attachments: file storage on the same data volume, serving route with
dual auth (session or portal/widget token, internal-note attachments
never token-servable), inbound ingestion from email (mailparser) and
Telegram (photo/document handlers), outbound delivery via SMTP
attachments and Telegram sendDocument/sendPhoto, upload UI (paperclip +
pending-file chip) on all three reply surfaces. Extracted
lib/tickets/delivery.ts so the text-only and attachment-upload admin
routes share one channel-delivery code path instead of duplicating it.

Canned responses: CRUD + a popover picker in the reply box that inserts
a saved template into the draft.

Tags: fixed 6-color palette reusing existing soft-badge tokens, a picker
on the ticket page, colored chips + a filter row on the dashboard.

Search: LIKE-based (not FTS5 — simpler and sufficient at this volume)
across ticket subject, customer name, and message bodies; a debounced
search box on the dashboard.

Desktop notifications: permission toggle + a background subscriber that
fires for new tickets and customer messages while the tab is hidden,
clicking one navigates to the ticket.

Password change: new account settings page, verifies the current
password before updating.

Nav: gear-menu settings list now includes Шаблоны ответов/Теги/Аккаунт.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
2026-07-26 17:32:12 +00:00
ogrechkoandClaude Sonnet 5 7947633709 Simplify top nav: 2 links + gear menu for settings
Nav down to Заявки (renamed from Дашборд) + Статистика. Telegram/Почта/
Виджет moved into a gear-icon dropdown next to the theme toggle instead
of competing for space in the horizontal nav row.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
2026-07-26 15:24:26 +00:00
ogrechkoandClaude Sonnet 5 cda92de9d2 Mobile-responsive admin nav/thread header, real dark theme pass
Nav overflowed the viewport on phones (confirmed via screenshot) — header
now wraps, nav row scrolls horizontally instead of pushing user/theme/
logout off-screen, ticket-thread's status/assignee selects stack full-width
below the subject on narrow screens.

Dark theme got a real pass instead of reusing near-identical values:
deeper/richer surfaces, punchier accent (#8b5cf6, matches the already-
validated dark chart-1), a subtle violet glow behind the page, and a
dark-appropriate card shadow (inset highlight + soft shadow — the flat
black shadow from light mode was invisible on a dark surface).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
2026-07-26 15:10:57 +00:00
ogrechkoandClaude Sonnet 5 9f1413f0b9 Design overhaul, stats page, internal notes
Design: Onest+Unbounded font pairing (Cyrillic-verified, replacing plain
Golos Text), manual light/dark toggle wired to the data-theme cascade
already built in MVP-1, refreshed palette + new chart-mark tokens
(CVD-validated via the dataviz skill's validator), colored avatar
initials, per-channel colored badges, favicon, nicer empty states.

Stats: /stats page — status/channel breakdown, 14-day ticket volume,
avg first-response time, per-agent open-ticket workload. Colors and
chart forms follow the dataviz skill's procedure (categorical order
re-stepped to clear the CVD adjacency check in both themes).

Internal notes: messages.visibility ("public"/"internal") column.
Agent-only notes never reach customer-facing reads (getTicketForCustomer)
or the customer SSE stream (/api/portal/events, reused by the widget) —
fixed at both the initial-fetch and live-delivery layers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
2026-07-26 14:38:52 +00:00
ogrechkoandClaude Sonnet 5 4efce5f307 Add embeddable chat widget for websites
Reuses the existing portal-token/SSE mechanism (customers.portalToken,
/api/portal/events) for anonymous widget visitors instead of building a
parallel auth system. Public API: session bootstrap + messages endpoint,
both scoped by a non-secret siteKey with optional origin allowlist.
Embeddable script (public/widget.js) is a small self-contained vanilla JS
file: floating button + iframe kept mounted for a live SSE connection,
with a postMessage bridge for the unread badge. Admin UI at
Settings -> Виджет manages sites and shows the embed snippet.

Also generalized recordTelegramInboundMessage -> recordChatInboundMessage
(channel param) since Telegram and the widget need the identical
find-or-create-open-ticket heuristic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
2026-07-26 13:34:08 +00:00
ogrechkoandClaude Sonnet 5 59e6c32d79 Fix IMAP idle loop: react to 'exists' event, not idle() resolution
client.idle() resolves on re-idle/connection events, not per new message —
new mail is signaled via the 'exists' event while IDLE is active. The
previous loop awaited idle() expecting it to return per message, so new
mail was only ever caught on the very first pass. Also made unseen-message
processing resilient to a message vanishing between search and fetch
(handle one UID at a time, each in its own try/catch) and added logging
to make the pipeline observable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
2026-07-26 13:11:21 +00:00
ogrechkoandClaude Sonnet 5 7e62ed739b Add email channel: IMAP IDLE inbound, SMTP outbound, thread matching
Inbound mail on support@top-sysops.ru creates/updates tickets in realtime
(IMAP IDLE, not polling). Replies thread via In-Reply-To/References against
stored Message-IDs, falling back to the customer's open ticket. Mailbox
credentials configured and encrypted via Settings -> Почта, same pattern as
the Telegram channel. TLS verification is opt-in-skippable per mailbox
(mail.top-sysops.ru's cert is currently expired; LAN-only server).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
2026-07-26 13:01:21 +00:00
ogrechkoandClaude Sonnet 5 cf0b6cf58e MVP-1: helpdesk core with dashboard, Telegram channel, customer portal
Next.js 16 + Drizzle/SQLite (matching ~/project-claude conventions), SSE-based
realtime updates, admin dashboard with status columns, ticket thread view,
Telegram long-polling integration (inbound + outbound), token-based customer
portal, Docker Compose packaging.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
2026-07-26 12:40:56 +00:00
ogrechko 888b7b82ba Initial commit 2026-07-26 11:44:15 +00:00