feat: in-app support form with email + Gitea issue integration (v0.49.0)

Users can now report bugs, suggestions, or questions from a new /support
page. Each submission sends a confirmation email and, when GITEA_URL/
GITEA_TOKEN/GITEA_REPO are configured in admin Settings, opens a labeled
issue on that repo automatically (best-effort — failure doesn't block the
ticket). Admins get a Support section to triage status and retry failed
Gitea issue creation.

New support_tickets table, gitea.ts client, site-settings entries for the
three new secrets, and OpenAPI docs for the two user-facing endpoints.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Arnaud
2026-07-18 11:23:02 +02:00
parent 9ea1f90ec4
commit 811d4cad42
27 changed files with 6136 additions and 6 deletions
+26
View File
@@ -0,0 +1,26 @@
import { pgTable, text, timestamp, index, pgEnum } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
import { users } from "./users";
export const supportTicketTypeEnum = pgEnum("support_ticket_type", ["bug", "suggestion", "question"]);
export const supportTicketStatusEnum = pgEnum("support_ticket_status", ["open", "triaged", "closed"]);
export const supportTickets = pgTable("support_tickets", {
id: text("id").primaryKey(),
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
type: supportTicketTypeEnum("type").notNull(),
title: text("title").notNull(),
description: text("description").notNull(),
status: supportTicketStatusEnum("status").notNull().default("open"),
giteaIssueUrl: text("gitea_issue_url"),
giteaError: text("gitea_error"),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(),
}, (t) => [
index("support_tickets_user_idx").on(t.userId, t.createdAt),
index("support_tickets_status_idx").on(t.status, t.createdAt),
]);
export const supportTicketsRelations = relations(supportTickets, ({ one }) => ({
user: one(users, { fields: [supportTickets.userId], references: [users.id] }),
}));