mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-05 00:46:00 +08:00
feat(pm): add bug-report button with screenshot annotation and admin panel
- New BugReport MongoDB model (comment, contact, userId, screenshot, pmState, status) - POST /api/bug-reports — authenticated submit; emails emanuel@faktorxmensch.com on receipt - GET/PATCH /api/admin/bug-reports + /[id] — admin list, detail with screenshot, status toggle - /pm: "Bug" button in HUD captures viewport screenshot (html2canvas), shows annotation canvas where testers can draw arrows; submits comment + contact + state snapshot - /admin: new "Bug Reports" tab with open-count badge, screenshot expand, "Erledigt" toggle, and "In /pm öffnen" link that restores captured engine state via ?restoreBugReport=<id> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
27
server/api/admin/bug-reports/[id].get.ts
Normal file
27
server/api/admin/bug-reports/[id].get.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { defineEventHandler, getRouterParam, createError } from 'h3'
|
||||
import { requireAdmin } from '../../../utils/auth'
|
||||
import { BugReport } from '../../../models/BugReport'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await requireAdmin(event)
|
||||
|
||||
const id = getRouterParam(event, 'id')
|
||||
const doc = await BugReport.findById(id).populate('userId', 'email name role').lean() as any
|
||||
|
||||
if (!doc) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Bug report not found' })
|
||||
}
|
||||
|
||||
return {
|
||||
id: String(doc._id),
|
||||
comment: doc.comment,
|
||||
contact: doc.contact,
|
||||
user: doc.userId
|
||||
? { id: String((doc.userId as any)._id), email: (doc.userId as any).email, name: (doc.userId as any).name }
|
||||
: undefined,
|
||||
screenshot: doc.screenshot || null,
|
||||
pmState: doc.pmState || null,
|
||||
status: doc.status,
|
||||
createdAt: doc.createdAt ? new Date(doc.createdAt).toISOString() : null,
|
||||
}
|
||||
})
|
||||
27
server/api/admin/bug-reports/[id].patch.ts
Normal file
27
server/api/admin/bug-reports/[id].patch.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { defineEventHandler, getRouterParam, readBody, createError } from 'h3'
|
||||
import { requireAdmin } from '../../../utils/auth'
|
||||
import { BugReport } from '../../../models/BugReport'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await requireAdmin(event)
|
||||
|
||||
const id = getRouterParam(event, 'id')
|
||||
const body = await readBody(event)
|
||||
|
||||
const status = body?.status
|
||||
if (status !== 'open' && status !== 'resolved') {
|
||||
throw createError({ statusCode: 400, statusMessage: 'status must be "open" or "resolved"' })
|
||||
}
|
||||
|
||||
const doc = await BugReport.findByIdAndUpdate(
|
||||
id,
|
||||
{ $set: { status } },
|
||||
{ new: true },
|
||||
).lean() as any
|
||||
|
||||
if (!doc) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Bug report not found' })
|
||||
}
|
||||
|
||||
return { id: String(doc._id), status: doc.status }
|
||||
})
|
||||
78
server/api/admin/bug-reports/index.get.ts
Normal file
78
server/api/admin/bug-reports/index.get.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { defineEventHandler, getQuery } from 'h3'
|
||||
import { requireAdmin } from '../../../utils/auth'
|
||||
import { BugReport } from '../../../models/BugReport'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
await requireAdmin(event)
|
||||
|
||||
const query = getQuery(event)
|
||||
const page = Math.max(parseInt(String(query.page ?? '1'), 10) || 1, 1)
|
||||
const pageSize = 20
|
||||
const skip = (page - 1) * pageSize
|
||||
const status = String(query.status ?? 'open')
|
||||
|
||||
const filter: Record<string, any> = {}
|
||||
if (status === 'open' || status === 'resolved') {
|
||||
filter.status = status
|
||||
}
|
||||
|
||||
const [docs, total] = await Promise.all([
|
||||
BugReport.aggregate([
|
||||
{ $match: filter },
|
||||
{ $sort: { createdAt: -1 } },
|
||||
{ $skip: skip },
|
||||
{ $limit: pageSize },
|
||||
{
|
||||
$lookup: {
|
||||
from: 'users',
|
||||
localField: 'userId',
|
||||
foreignField: '_id',
|
||||
as: 'userDoc',
|
||||
pipeline: [{ $project: { email: 1, name: 1, role: 1 } }],
|
||||
},
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
comment: 1,
|
||||
contact: 1,
|
||||
userId: 1,
|
||||
userDoc: 1,
|
||||
status: 1,
|
||||
createdAt: 1,
|
||||
'pmState.flowSlug': 1,
|
||||
'pmState.scenarioId': 1,
|
||||
'pmState.currentStateId': 1,
|
||||
hasScreenshot: { $gt: [{ $strLenCP: { $ifNull: ['$screenshot', ''] } }, 0] },
|
||||
},
|
||||
},
|
||||
]).exec(),
|
||||
BugReport.countDocuments(filter),
|
||||
])
|
||||
|
||||
const items = docs.map((doc: any) => ({
|
||||
id: String(doc._id),
|
||||
comment: doc.comment,
|
||||
contact: doc.contact,
|
||||
user: doc.userDoc?.[0]
|
||||
? { id: String(doc.userDoc[0]._id), email: doc.userDoc[0].email, name: doc.userDoc[0].name }
|
||||
: undefined,
|
||||
pmState: {
|
||||
flowSlug: doc.pmState?.flowSlug,
|
||||
scenarioId: doc.pmState?.scenarioId,
|
||||
currentStateId: doc.pmState?.currentStateId,
|
||||
},
|
||||
status: doc.status,
|
||||
createdAt: doc.createdAt ? new Date(doc.createdAt).toISOString() : null,
|
||||
hasScreenshot: Boolean(doc.hasScreenshot),
|
||||
}))
|
||||
|
||||
return {
|
||||
items,
|
||||
pagination: {
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
pages: Math.ceil(total / pageSize) || 1,
|
||||
},
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user