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
34 lines
1.2 KiB
SQL
34 lines
1.2 KiB
SQL
CREATE TABLE `attachments` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`message_id` text NOT NULL,
|
|
`filename` text NOT NULL,
|
|
`mime_type` text NOT NULL,
|
|
`size_bytes` integer NOT NULL,
|
|
`storage_key` text NOT NULL,
|
|
`created_at` integer DEFAULT (unixepoch('subsec') * 1000) NOT NULL,
|
|
FOREIGN KEY (`message_id`) REFERENCES `messages`(`id`) ON UPDATE no action ON DELETE cascade
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE `canned_responses` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`title` text NOT NULL,
|
|
`body` text NOT NULL,
|
|
`created_at` integer DEFAULT (unixepoch('subsec') * 1000) NOT NULL
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE `tags` (
|
|
`id` text PRIMARY KEY NOT NULL,
|
|
`name` text NOT NULL,
|
|
`color` text DEFAULT 'accent' NOT NULL,
|
|
`created_at` integer DEFAULT (unixepoch('subsec') * 1000) NOT NULL
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE UNIQUE INDEX `tags_name_unique` ON `tags` (`name`);--> statement-breakpoint
|
|
CREATE TABLE `ticket_tags` (
|
|
`ticket_id` text NOT NULL,
|
|
`tag_id` text NOT NULL,
|
|
PRIMARY KEY(`ticket_id`, `tag_id`),
|
|
FOREIGN KEY (`ticket_id`) REFERENCES `tickets`(`id`) ON UPDATE no action ON DELETE cascade,
|
|
FOREIGN KEY (`tag_id`) REFERENCES `tags`(`id`) ON UPDATE no action ON DELETE cascade
|
|
);
|