Skip to content

Commit c34045b

Browse files
feat: add audit logs to tg user page
1 parent b7b9c78 commit c34045b

2 files changed

Lines changed: 99 additions & 11 deletions

File tree

src/app/dashboard/(active)/telegram/users/page.tsx

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client"
22
import { useQuery, useQueryClient } from "@tanstack/react-query"
3-
import { ArrowLeft, ExternalLinkIcon, Search, X } from "lucide-react"
3+
import { ArrowLeft, ExternalLinkIcon, Search, Star, X } from "lucide-react"
44
import Link from "next/link"
55
import { useState } from "react"
66
import { toast } from "sonner"
@@ -9,9 +9,10 @@ import { Button } from "@/components/ui/button"
99
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
1010
import { Input } from "@/components/ui/input"
1111
import { Label } from "@/components/ui/label"
12+
import { useSession } from "@/lib/auth"
1213
import { useTRPC } from "@/lib/trpc/client"
1314
import type { ApiOutput } from "@/lib/trpc/types"
14-
import { stripChatId } from "@/lib/utils/telegram"
15+
import { fmtUser, stripChatId } from "@/lib/utils/telegram"
1516
import { AddRole } from "./add-role"
1617
import { DeleteGroupAdmin } from "./delete-group-admin"
1718
import { NewGroupAdmin } from "./new-group-admin"
@@ -29,6 +30,7 @@ export default function TgUsers() {
2930
const searchQuery = trpc.tg.users.getByUsername.queryOptions({ username: query })
3031
const { data: userData } = useQuery(trpc.tg.permissions.getRoles.queryOptions({ userId: user?.id ?? 0 }))
3132
const { data: messages } = useQuery(trpc.tg.messages.getLastByUser.queryOptions({ userId: user?.id ?? 0 }))
33+
const { data: auditLog } = useQuery(trpc.tg.auditLog.getById.queryOptions({ targetId: user?.id ?? 0 }))
3234

3335
async function search(e: React.FormEvent<HTMLFormElement>) {
3436
e.preventDefault()
@@ -97,13 +99,37 @@ export default function TgUsers() {
9799
.map((m) => (
98100
<GroupAdminCard groupAdminInfo={m} user={user} key={m.group.id} />
99101
))}
102+
103+
{userData?.groupAdmin.length === 0 && (
104+
<p className="bg-card rounded-lg p-3 italic text-muted-foreground text-sm">
105+
This user is not group admin in any group.
106+
</p>
107+
)}
100108
</div>
101109

102110
<p className="pt-6">Last messages:</p>
103111
<div className="grid grid-cols-3 py-2 gap-4">
104112
{messages?.messages?.map((m) => (
105113
<MessageCard message={m} key={`${m.chatId}-${m.messageId}`} />
106114
))}
115+
116+
{messages?.messages?.length === 0 && (
117+
<p className="bg-card rounded-lg p-3 italic text-muted-foreground text-sm">
118+
No recent messages sent by this user
119+
</p>
120+
)}
121+
</div>
122+
123+
<p className="pt-6">Audit log:</p>
124+
<div className="grid grid-cols-3 py-2 gap-4">
125+
{auditLog?.map((m) => (
126+
<AuditLogCard log={m} key={`${m.id}-${m.type}`} />
127+
))}
128+
{auditLog?.length === 0 && (
129+
<p className="bg-card rounded-lg p-3 italic text-muted-foreground text-sm">
130+
No audit log found for this user
131+
</p>
132+
)}
107133
</div>
108134
</>
109135
)}
@@ -113,15 +139,26 @@ export default function TgUsers() {
113139

114140
type UserRoles = ApiOutput["tg"]["permissions"]["getRoles"]["roles"]
115141
function UserInfoCard({ user, roles }: { user: NonNullable<User>; roles: UserRoles }) {
142+
const sesh = useSession()
143+
const seshUserId = sesh.data?.user.telegramId
144+
const isSelf = seshUserId && seshUserId === user.id
145+
116146
return (
117147
<Card className="w-fit min-w-120">
118148
<CardHeader>
119-
<CardTitle>User ID: {user.id}</CardTitle>
149+
<CardTitle>
150+
User ID: {user.id}{" "}
151+
{isSelf && (
152+
<Badge>
153+
<Star /> You
154+
</Badge>
155+
)}
156+
</CardTitle>
120157
</CardHeader>
121158
<CardContent className="space-y-2">
122159
<p>
123160
<span className="text-muted-foreground">Name: </span>
124-
{user.firstName} {user.lastName}
161+
{user.firstName} {user.lastName ?? ""}
125162
</p>
126163
<p>
127164
<span className="text-muted-foreground">Username: </span>
@@ -143,8 +180,8 @@ function UserInfoCard({ user, roles }: { user: NonNullable<User>; roles: UserRol
143180
type GroupAdminSingle = NonNullable<ApiOutput["tg"]["permissions"]["getRoles"]["groupAdmin"][number]>
144181
function GroupAdminCard({ user, groupAdminInfo: m }: { user: NonNullable<User>; groupAdminInfo: GroupAdminSingle }) {
145182
return (
146-
<Card key={m.group.id}>
147-
<CardContent>
183+
<Card>
184+
<CardContent className="space-y-2">
148185
<p>
149186
{" "}
150187
<span className="text-muted-foreground">Chat: </span>
@@ -165,20 +202,17 @@ function GroupAdminCard({ user, groupAdminInfo: m }: { user: NonNullable<User>;
165202
type Message = NonNullable<ApiOutput["tg"]["messages"]["getLastByUser"]["messages"]>[number]
166203
function MessageCard({ message: m }: { message: Message }) {
167204
return (
168-
<Card key={`${m.messageId}-${m.chatId}`}>
169-
<CardContent>
205+
<Card>
206+
<CardContent className="space-y-1">
170207
<p>
171-
{" "}
172208
<span className="text-muted-foreground">Chat: </span>
173209
{m.group && <span>{m.group.title}</span>} [{m.chatId}]
174210
</p>
175211
<p>
176-
{" "}
177212
<span className="text-muted-foreground">Message ID: </span>
178213
{m.messageId}
179214
</p>
180215
<p>
181-
{" "}
182216
<span className="text-muted-foreground">Timestamp: </span>
183217
{m.timestamp.toLocaleString()}
184218
</p>
@@ -207,3 +241,46 @@ function MessageCard({ message: m }: { message: Message }) {
207241
</Card>
208242
)
209243
}
244+
245+
type Log = NonNullable<ApiOutput["tg"]["auditLog"]["getById"]>[number]
246+
function AuditLogCard({ log: m }: { log: Log }) {
247+
const trpc = useTRPC()
248+
249+
const { data: admin } = useQuery(trpc.tg.users.get.queryOptions({ userId: m.adminId }))
250+
251+
return (
252+
<Card>
253+
<CardHeader>
254+
<CardTitle>{m.type}</CardTitle>
255+
</CardHeader>
256+
<CardContent>
257+
<p>
258+
{" "}
259+
<span className="text-muted-foreground">Chat: </span>
260+
{m.groupTitle && <span>{m.groupTitle}</span>} [{m.groupId}]
261+
</p>
262+
<p>
263+
{" "}
264+
<span className="text-muted-foreground">Admin ID: </span>
265+
{admin && admin.user && fmtUser(admin.user)}
266+
</p>
267+
{m.createdAt && (
268+
<p>
269+
<span className="text-muted-foreground">Created: </span>
270+
{m.createdAt.toLocaleString()}
271+
</p>
272+
)}
273+
{m.until && (
274+
<p>
275+
<span className="text-muted-foreground">Until: </span>
276+
{m.until.toLocaleString()}
277+
</p>
278+
)}
279+
<p>
280+
<span className="text-muted-foreground">Reason:</span>
281+
{m.reason}
282+
</p>
283+
</CardContent>
284+
</Card>
285+
)
286+
}

src/lib/utils/telegram.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { ApiOutput } from "../trpc/types"
2+
13
export function stripChatId(chatId: number): number {
24
if (chatId > 0) return chatId
35
const positive = -chatId
@@ -6,3 +8,12 @@ export function stripChatId(chatId: number): number {
68
if (str.length < 13) return positive
79
return parseInt(str.slice(1), 10)
810
}
11+
12+
type TgUser = NonNullable<ApiOutput["tg"]["users"]["get"]["user"]>
13+
export function fmtUser(user: TgUser, printId: boolean = true) {
14+
let string = user.firstName
15+
if (user.lastName) string += ` ${user.lastName}`
16+
if (user.username) string += ` @${user.username}`
17+
if (printId) string += ` [${user.id}]`
18+
return string
19+
}

0 commit comments

Comments
 (0)