chore(auto): task completed - unknown

This commit is contained in:
MythEclipse
2026-06-13 12:24:32 +07:00
parent dcede1d676
commit 615c23f061
8 changed files with 829 additions and 16 deletions
@@ -156,6 +156,149 @@ export class DashboardRepository {
return { data, nextCursor };
}
async listChannels(query: ListUsersQuery & { guildId?: string }) {
const pool = getPool();
const limit = query.limit ?? 20;
const conditions: string[] = [];
const params: unknown[] = [];
let paramIdx = 1;
if (query.search) {
conditions.push(
`(m.channel_id ILIKE $${paramIdx} OR m.channel_name ILIKE $${paramIdx})`,
);
params.push(`%${query.search}%`);
paramIdx++;
}
if (query.guildId) {
conditions.push(`m.guild_id = $${paramIdx}`);
params.push(query.guildId);
paramIdx++;
}
const whereClause =
conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
const { rows } = await pool.query(
`
SELECT
m.channel_id,
m.channel_name,
m.guild_id,
m.total_messages,
m.flagged_count,
m.last_message_at,
c.culture_summary,
c.last_analyzed_at
FROM (
SELECT
channel_id,
guild_id,
COALESCE(NULLIF((metadata::jsonb -> 'channel' ->> 'channelName'), ''), channel_id) AS channel_name,
COUNT(*)::int AS total_messages,
COUNT(*) FILTER (WHERE ai_status = 'flagged')::int AS flagged_count,
MAX(created_at) AS last_message_at
FROM messages
GROUP BY channel_id, guild_id, (metadata::jsonb -> 'channel' ->> 'channelName')
) m
LEFT JOIN channel_cultures c ON c.channel_id = m.channel_id
${whereClause}
ORDER BY m.total_messages DESC
LIMIT $${paramIdx}
`,
[...params, limit + 1],
);
const data = ((rows as Record<string, unknown>[]) || []).slice(0, limit).map((r) => ({
channel_id: String(r.channel_id),
channel_name: r.channel_name as string | null,
guild_id: r.guild_id as string | null,
total_messages: Number(r.total_messages),
flagged_count: Number(r.flagged_count),
last_message_at: r.last_message_at ? Number(r.last_message_at) : null,
culture_summary: r.culture_summary as string | null,
last_analyzed_at: r.last_analyzed_at
? Number(r.last_analyzed_at)
: null,
}));
const lastRow = rows[limit - 1] as Record<string, unknown> | undefined;
const nextCursor =
rows.length > limit
? String(lastRow?.total_messages ?? "")
: null;
return { data, nextCursor };
}
async getChannelDetail(channelId: string) {
const pool = getPool();
const channelResult = await pool.query(
`
SELECT
m.channel_id,
m.channel_name,
m.guild_id,
m.total_messages,
m.flagged_count,
m.clean_count,
c.culture_summary,
c.last_analyzed_at
FROM (
SELECT
channel_id,
guild_id,
COALESCE(NULLIF((metadata::jsonb -> 'channel' ->> 'channelName'), ''), channel_id) AS channel_name,
COUNT(*)::int AS total_messages,
COUNT(*) FILTER (WHERE ai_status = 'flagged')::int AS flagged_count,
COUNT(*) FILTER (WHERE ai_status = 'clean')::int AS clean_count
FROM messages
WHERE channel_id = $1
GROUP BY channel_id, guild_id, (metadata::jsonb -> 'channel' ->> 'channelName')
) m
LEFT JOIN channel_cultures c ON c.channel_id = m.channel_id
`,
[channelId],
);
const row = channelResult.rows[0] as Record<string, unknown> | undefined;
if (!row) return null;
const recent = await pool.query(
`
SELECT id, content, channel_id, created_at, ai_status, username
FROM messages
WHERE channel_id = $1
ORDER BY created_at DESC
LIMIT 20
`,
[channelId],
);
return {
channel_id: String(row.channel_id),
channel_name: row.channel_name as string | null,
guild_id: row.guild_id as string | null,
total_messages: Number(row.total_messages),
flagged_count: Number(row.flagged_count),
clean_count: Number(row.clean_count),
culture_summary: row.culture_summary as string | null,
last_analyzed_at: row.last_analyzed_at
? Number(row.last_analyzed_at)
: null,
recent_messages: (recent.rows as Record<string, unknown>[]).map((r) => ({
id: String(r.id),
content: String(r.content),
channel_id: String(r.channel_id),
created_at: Number(r.created_at),
ai_status: r.ai_status as string | null,
username: r.username as string | null,
})),
};
}
async getUserDetail(userId: string) {
const pool = getPool();
@@ -48,5 +48,36 @@ export function createDashboardRouter(): Router {
}),
);
// GET /api/dashboard/channels — paginated channel list with culture summaries
router.get(
"/dashboard/channels",
asyncHandler(async (req: Request, res: Response) => {
const limit = Number(req.query.limit) || 20;
const search =
typeof req.query.search === "string" ? req.query.search : undefined;
const guildId =
typeof req.query.guild_id === "string"
? req.query.guild_id
: undefined;
const result = await dashboardService.listChannels({
limit,
search,
guildId,
});
res.json(result);
}),
);
// GET /api/dashboard/channels/:channelId — single channel detail
router.get(
"/dashboard/channels/:channelId",
asyncHandler(async (req: Request, res: Response) => {
const channelId = String(req.params.channelId);
const detail = await dashboardService.getChannelDetail(channelId);
res.json(detail);
}),
);
return router;
}
@@ -24,6 +24,16 @@ export class DashboardService {
logger.debug({ userId }, "Fetching user detail");
return dashboardRepository.getUserDetail(userId);
}
async listChannels(query: { limit: number; search?: string; guildId?: string }) {
logger.debug({ query }, "Listing dashboard channels");
return dashboardRepository.listChannels(query);
}
async getChannelDetail(channelId: string) {
logger.debug({ channelId }, "Fetching channel detail");
return dashboardRepository.getChannelDetail(channelId);
}
}
export const dashboardService = new DashboardService();