Merge pull request #1 from d0zingcat/enhancement/topic_display

This commit is contained in:
2026-01-12 12:00:32 +08:00
committed by GitHub
6 changed files with 45 additions and 3 deletions

View File

@@ -1,3 +1,4 @@
export { };
// Simulate admin checking requests
async function run() {

View File

@@ -1,3 +1,4 @@
export { };
async function run() {
console.log('Fetching dashboard stats as admin...');

View File

@@ -32,6 +32,8 @@ api.get('/topics', requireAuth, async (c) => {
const allTopics = await db.query.topics.findMany({
where: eq(topics.status, 'approved'),
with: {
creator: true,
approver: true,
subscriptions: {
where: (subscriptions, { eq }) =>
isAdmin ? undefined : (currentUserId ? eq(subscriptions.userId, currentUserId) : undefined),
@@ -59,6 +61,7 @@ api.get('/topics/all', requireAdmin, async (c) => {
const allTopics = await db.query.topics.findMany({
with: {
creator: true,
approver: true,
subscriptions: true
},
orderBy: [desc(topics.createdAt)]
@@ -71,14 +74,18 @@ api.get('/topics/my-requests', requireAuth, async (c) => {
const requests = await db.query.topics.findMany({
where: eq(topics.createdBy, session.id),
orderBy: [desc(topics.createdAt)],
with: {
approver: true,
}
});
return c.json(requests);
});
api.post('/topics/:id/approve', requireAdmin, async (c) => {
const id = c.req.param('id');
const session = c.get('session');
const result = await db.update(topics)
.set({ status: 'approved' })
.set({ status: 'approved', approvedBy: session.id })
.where(eq(topics.id, id))
.returning();
return c.json(result[0]);
@@ -105,6 +112,7 @@ api.post('/topics', requireAuth, zValidator('json', topicSchema), async (c) => {
...body,
status,
createdBy: session.id,
approvedBy: session.isAdmin ? session.id : null,
}).returning();
return c.json(result[0]);
});

View File

@@ -9,6 +9,7 @@ export const topics = pgTable('topics', {
description: text('description'),
status: text('status', { enum: ['pending', 'approved', 'rejected'] }).default('approved').notNull(),
createdBy: text('created_by').references(() => users.id),
approvedBy: text('approved_by').references(() => users.id),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
@@ -17,6 +18,12 @@ export const topicsRelations = relations(topics, ({ many, one }) => ({
creator: one(users, {
fields: [topics.createdBy],
references: [users.id],
relationName: 'creator',
}),
approver: one(users, {
fields: [topics.approvedBy],
references: [users.id],
relationName: 'approver',
}),
}));
@@ -31,6 +38,8 @@ export const users = pgTable('users', {
export const usersRelations = relations(users, ({ many }) => ({
subscriptions: many(subscriptions),
createdTopics: many(topics, { relationName: 'creator' }),
approvedTopics: many(topics, { relationName: 'approver' }),
}));
// Subscriptions: 用户直接订阅 Topic

View File

@@ -99,6 +99,7 @@ function TopicsManagement() {
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Subscribers</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Created By</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Approved By</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
@@ -123,6 +124,9 @@ function TopicsManagement() {
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{topic.creator?.name || 'Unknown'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{topic.approver?.name || '-'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button
onClick={() => handleDelete(topic.id, topic.name)}

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
import { Plus, Settings, UserPlus, UserMinus, Copy, Check } from 'lucide-react';
import { Plus, Settings, UserPlus, UserMinus, Copy, Check, User, ShieldCheck } from 'lucide-react';
import Modal from '../components/Modal';
import { useAuth } from '../contexts/AuthContext';
import { client } from '../lib/client';
@@ -21,6 +21,8 @@ interface Topic {
slug: string;
description?: string;
subscriptions: Subscription[];
creator?: User;
approver?: User;
}
export default function TopicsView() {
@@ -281,6 +283,20 @@ export default function TopicsView() {
<p className="flex items-center text-sm text-gray-500 mt-1">
{topic.description}
</p>
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 mt-2">
{topic.creator && (
<div className="flex items-center text-xs text-gray-500">
<User className="w-3 h-3 mr-1 text-gray-400" />
<span>Created by: <span className="text-gray-900 font-medium">{topic.creator.name}</span></span>
</div>
)}
{topic.approver && (
<div className="flex items-center text-xs text-gray-500">
<ShieldCheck className="w-3 h-3 mr-1 text-indigo-400" />
<span>Approved by: <span className="text-gray-900 font-medium">{topic.approver.name}</span></span>
</div>
)}
</div>
{currentUser && (
<div className="mt-3 bg-gray-50 p-2 rounded border border-gray-200">
<div className="flex justify-between items-center">
@@ -358,7 +374,10 @@ export default function TopicsView() {
<div className="mt-2 text-sm text-gray-500">
<p>Slug: <span className="font-mono">{req.slug}</span></p>
{req.description && <p className="mt-1">{req.description}</p>}
<p className="mt-1 text-xs text-gray-400">Requested on: {new Date(req.createdAt).toLocaleDateString()}</p>
<p className="mt-1 text-xs text-gray-400">
Requested on: {new Date(req.createdAt).toLocaleDateString()}
{req.approver && <span className="ml-2">| Approved by: {req.approver.name}</span>}
</p>
</div>
</div>
</div>