mirror of
https://github.com/d0zingcat/alert-message-center.git
synced 2026-05-13 15:09:19 +00:00
feat: add english readme and fix lint
Signed-off-by: d0zingcat <iamtangli42@gmail.com>
This commit is contained in:
@@ -11,8 +11,8 @@ import {
|
||||
topics,
|
||||
users,
|
||||
} from "./db/schema";
|
||||
import { type AuthSession, requireAdmin, requireAuth } from "./middleware";
|
||||
import { notifyAdminsOfNewTopic } from "./lib/admin-notifier";
|
||||
import { type AuthSession, requireAdmin, requireAuth } from "./middleware";
|
||||
|
||||
const api = new Hono<{ Variables: { session: AuthSession } }>();
|
||||
|
||||
@@ -273,10 +273,9 @@ api.get("/groups", requireAuth, async (c) => {
|
||||
const query = c.req.query("q")?.trim();
|
||||
const limit = Math.min(Number(c.req.query("limit") || 100), 200);
|
||||
|
||||
let whereClause = undefined;
|
||||
if (query) {
|
||||
whereClause = sql`${knownGroupChats.name} ilike ${`%${query}%`}`;
|
||||
}
|
||||
const whereClause = query
|
||||
? sql`${knownGroupChats.name} ilike ${`%${query}%`}`
|
||||
: undefined;
|
||||
|
||||
// Return recent active groups
|
||||
const groups = await db
|
||||
@@ -319,10 +318,14 @@ api.post(
|
||||
}
|
||||
|
||||
if (topic.createdBy !== session.id && !session.isAdmin) {
|
||||
return c.json({ error: "Only topic owner or admin can bind groups" }, 403);
|
||||
return c.json(
|
||||
{ error: "Only topic owner or admin can bind groups" },
|
||||
403,
|
||||
);
|
||||
}
|
||||
|
||||
const status = session.isAdmin || session.isTrusted ? "approved" : "pending";
|
||||
const status =
|
||||
session.isAdmin || session.isTrusted ? "approved" : "pending";
|
||||
|
||||
const result = await db
|
||||
.insert(topicGroupChats)
|
||||
@@ -345,7 +348,7 @@ api.post(
|
||||
// Metadata passed to notifier for better context
|
||||
isGroupBinding: true,
|
||||
groupName: body.name,
|
||||
} as any);
|
||||
});
|
||||
}
|
||||
|
||||
return c.json(result[0]);
|
||||
|
||||
@@ -1,85 +1,85 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db";
|
||||
import { users } from "../db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { feishuClient } from "../feishu";
|
||||
import { logger } from "./logger";
|
||||
|
||||
export async function notifyAdminsOfNewTopic(topic: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
createdBy: string | null;
|
||||
isGroupBinding?: boolean;
|
||||
groupName?: string;
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
createdBy: string | null;
|
||||
isGroupBinding?: boolean;
|
||||
groupName?: string;
|
||||
}) {
|
||||
try {
|
||||
// 1. Get all admins
|
||||
const admins = await db.query.users.findMany({
|
||||
where: eq(users.isAdmin, true),
|
||||
});
|
||||
try {
|
||||
// 1. Get all admins
|
||||
const admins = await db.query.users.findMany({
|
||||
where: eq(users.isAdmin, true),
|
||||
});
|
||||
|
||||
if (admins.length === 0) {
|
||||
logger.warn("No admins found to notify");
|
||||
return;
|
||||
}
|
||||
if (admins.length === 0) {
|
||||
logger.warn("No admins found to notify");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Get creator name
|
||||
let creatorName = "Unknown";
|
||||
if (topic.createdBy) {
|
||||
const creator = await db.query.users.findFirst({
|
||||
where: eq(users.id, topic.createdBy),
|
||||
});
|
||||
if (creator) creatorName = creator.name;
|
||||
}
|
||||
// 2. Get creator name
|
||||
let creatorName = "Unknown";
|
||||
if (topic.createdBy) {
|
||||
const creator = await db.query.users.findFirst({
|
||||
where: eq(users.id, topic.createdBy),
|
||||
});
|
||||
if (creator) creatorName = creator.name;
|
||||
}
|
||||
|
||||
// 3. Prepare message content
|
||||
const title = topic.isGroupBinding
|
||||
? "🔗 新的群聊绑定申请"
|
||||
: "🆕 新的 Topic 申请";
|
||||
const detailContent = topic.isGroupBinding
|
||||
? `**Topic:** ${topic.name}\n**群聊:** ${topic.groupName}\n**创建者:** ${creatorName}`
|
||||
: `**名称:** ${topic.name}\n**Slug:** ${topic.slug}\n**创建者:** ${creatorName}`;
|
||||
// 3. Prepare message content
|
||||
const title = topic.isGroupBinding
|
||||
? "🔗 新的群聊绑定申请"
|
||||
: "🆕 新的 Topic 申请";
|
||||
const detailContent = topic.isGroupBinding
|
||||
? `**Topic:** ${topic.name}\n**群聊:** ${topic.groupName}\n**创建者:** ${creatorName}`
|
||||
: `**名称:** ${topic.name}\n**Slug:** ${topic.slug}\n**创建者:** ${creatorName}`;
|
||||
|
||||
const content = {
|
||||
config: { wide_screen_mode: true },
|
||||
header: {
|
||||
template: topic.isGroupBinding ? "blue" : "orange",
|
||||
title: { content: title, tag: "plain_text" },
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
tag: "div",
|
||||
text: {
|
||||
content: detailContent,
|
||||
tag: "lark_md",
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: "action",
|
||||
actions: [
|
||||
{
|
||||
tag: "button",
|
||||
text: { content: "前往审批", tag: "plain_text" },
|
||||
type: "primary",
|
||||
url: `${process.env.FRONTEND_URL || "http://localhost:5173"}/admin/topics`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const content = {
|
||||
config: { wide_screen_mode: true },
|
||||
header: {
|
||||
template: topic.isGroupBinding ? "blue" : "orange",
|
||||
title: { content: title, tag: "plain_text" },
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
tag: "div",
|
||||
text: {
|
||||
content: detailContent,
|
||||
tag: "lark_md",
|
||||
},
|
||||
},
|
||||
{
|
||||
tag: "action",
|
||||
actions: [
|
||||
{
|
||||
tag: "button",
|
||||
text: { content: "前往审批", tag: "plain_text" },
|
||||
type: "primary",
|
||||
url: `${process.env.FRONTEND_URL || "http://localhost:5173"}/admin/topics`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 4. Send notification to each admin
|
||||
for (const admin of admins) {
|
||||
if (admin.feishuUserId) {
|
||||
await feishuClient.sendMessage(
|
||||
admin.feishuUserId,
|
||||
"open_id",
|
||||
"interactive",
|
||||
content,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error({ err: error, topicId: topic.id }, "Failed to notify admins");
|
||||
}
|
||||
// 4. Send notification to each admin
|
||||
for (const admin of admins) {
|
||||
if (admin.feishuUserId) {
|
||||
await feishuClient.sendMessage(
|
||||
admin.feishuUserId,
|
||||
"open_id",
|
||||
"interactive",
|
||||
content,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error({ err: error, topicId: topic.id }, "Failed to notify admins");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,12 +238,13 @@ export default function GroupBindingsModal({
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
className={`ml-3 inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-bold tracking-wider uppercase ${binding.status === "approved"
|
||||
className={`ml-3 inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-bold tracking-wider uppercase ${
|
||||
binding.status === "approved"
|
||||
? "bg-green-100 text-green-700"
|
||||
: binding.status === "rejected"
|
||||
? "bg-red-100 text-red-700"
|
||||
: "bg-amber-100 text-amber-700"
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
{binding.status}
|
||||
</span>
|
||||
@@ -268,8 +269,8 @@ export default function GroupBindingsModal({
|
||||
Add Group Binding
|
||||
</h4>
|
||||
<p className="text-xs text-gray-500 mb-4 leading-relaxed">
|
||||
Search and select a group where the <strong>Alert Messenger</strong> bot
|
||||
has been added.
|
||||
Search and select a group where the <strong>Alert Messenger</strong>{" "}
|
||||
bot has been added.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
@@ -288,7 +289,9 @@ export default function GroupBindingsModal({
|
||||
placeholder="Search for a group name..."
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
onFocus={() => knownGroups.length > 0 && setShowDropdown(true)}
|
||||
onFocus={() =>
|
||||
knownGroups.length > 0 && setShowDropdown(true)
|
||||
}
|
||||
disabled={loading}
|
||||
/>
|
||||
{searchQuery && (
|
||||
@@ -364,10 +367,11 @@ export default function GroupBindingsModal({
|
||||
|
||||
{status && (
|
||||
<div
|
||||
className={`mt-4 p-3 rounded-lg flex items-start gap-2 ${status.type === "success"
|
||||
className={`mt-4 p-3 rounded-lg flex items-start gap-2 ${
|
||||
status.type === "success"
|
||||
? "bg-green-50 text-green-700 border border-green-100"
|
||||
: "bg-red-50 text-red-700 border border-red-100"
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
<div className="text-sm font-medium">{status.message}</div>
|
||||
</div>
|
||||
|
||||
@@ -35,40 +35,44 @@ export default function AdminView() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("load")}
|
||||
className={`${activeTab === "load"
|
||||
className={`${
|
||||
activeTab === "load"
|
||||
? "border-indigo-500 text-indigo-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
} whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm`}
|
||||
} whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm`}
|
||||
>
|
||||
System Load
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("requests")}
|
||||
className={`${activeTab === "requests"
|
||||
className={`${
|
||||
activeTab === "requests"
|
||||
? "border-indigo-500 text-indigo-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
} whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm`}
|
||||
} whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm`}
|
||||
>
|
||||
Topic Requests
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("group-requests")}
|
||||
className={`${activeTab === "group-requests"
|
||||
className={`${
|
||||
activeTab === "group-requests"
|
||||
? "border-indigo-500 text-indigo-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
} whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm`}
|
||||
} whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm`}
|
||||
>
|
||||
Group Bindings
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("topics")}
|
||||
className={`${activeTab === "topics"
|
||||
className={`${
|
||||
activeTab === "topics"
|
||||
? "border-indigo-500 text-indigo-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
} whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm`}
|
||||
} whitespace-nowrap pb-4 px-1 border-b-2 font-medium text-sm`}
|
||||
>
|
||||
All Topics
|
||||
</button>
|
||||
@@ -102,7 +106,7 @@ function GroupRequestsList() {
|
||||
const fetchRequests = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// @ts-ignore - groups requests might not be in the generated client yet
|
||||
// @ts-expect-error - groups requests might not be in the generated client yet
|
||||
const res = await client.api.topics.groups.requests.$get(undefined, {
|
||||
init: { credentials: "include" },
|
||||
});
|
||||
@@ -133,7 +137,7 @@ function GroupRequestsList() {
|
||||
action: "approve" | "reject",
|
||||
) => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
await client.api.topics[":id"].groups[":bindingId"][action].$post(
|
||||
{ param: { id: req.topicId, bindingId: req.id } },
|
||||
{ init: { credentials: "include" } },
|
||||
@@ -164,15 +168,13 @@ function GroupRequestsList() {
|
||||
Group: <span className="text-indigo-600">{req.name}</span>
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
Topic: <span className="font-semibold">{req.topic?.name}</span> (
|
||||
{req.topic?.slug})
|
||||
Topic: <span className="font-semibold">{req.topic?.name}</span>{" "}
|
||||
({req.topic?.slug})
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
Requested by: {req.creator?.name || "Unknown"}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
ID: {req.chatId}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 mt-1">ID: {req.chatId}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
@@ -291,12 +293,13 @@ function TopicsManagement() {
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span
|
||||
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${topic.status === "approved"
|
||||
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
topic.status === "approved"
|
||||
? "bg-green-100 text-green-800"
|
||||
: topic.status === "rejected"
|
||||
? "bg-red-100 text-red-800"
|
||||
: "bg-yellow-100 text-yellow-800"
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
{topic.status}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user