diff --git a/web/src/app/mapfixes/[mapfixId]/_comments.tsx b/web/src/app/mapfixes/[mapfixId]/_comments.tsx index ca46e40..091092e 100644 --- a/web/src/app/mapfixes/[mapfixId]/_comments.tsx +++ b/web/src/app/mapfixes/[mapfixId]/_comments.tsx @@ -52,7 +52,7 @@ function LeaveAComment() { ) } -export default function Comments(stats: CommentersProps) { +export function Comments(stats: CommentersProps) { return (<> <section className="comments"> {stats.comments_data.comments.length===0 @@ -66,5 +66,6 @@ export default function Comments(stats: CommentersProps) { } export { - type CreatorAndReviewStatus + type CreatorAndReviewStatus, + type Comment, } diff --git a/web/src/app/mapfixes/[mapfixId]/page.tsx b/web/src/app/mapfixes/[mapfixId]/page.tsx index 6d17840..0422a11 100644 --- a/web/src/app/mapfixes/[mapfixId]/page.tsx +++ b/web/src/app/mapfixes/[mapfixId]/page.tsx @@ -5,7 +5,8 @@ import type { CreatorAndReviewStatus } from "./_comments"; import { MapImage } from "./_mapImage"; import { useParams } from "next/navigation"; import ReviewButtons from "./_reviewButtons"; -import Comments from "./_comments"; +import { Comments, Comment } from "./_comments"; +import { AuditEvent, decodeAuditEvent as auditEventMessage } from "@/app/ts/AuditEvent"; import Webpage from "@/app/_components/webpage"; import Link from "next/link"; import { useState, useEffect } from "react"; @@ -62,19 +63,35 @@ function TitleAndComments(stats: CreatorAndReviewStatus) { } export default function MapfixInfoPage() { - const dynamicId = useParams<{mapfixId: string}>() + const { mapfixId } = useParams < { mapfixId: string } >() const [mapfix, setMapfix] = useState<MapfixInfo | null>(null) + const [auditEvents, setAuditEvents] = useState<AuditEvent[]>([]) useEffect(() => { // needs to be client sided since server doesn't have a session, nextjs got mad at me for exporting an async function: (https://nextjs.org/docs/messages/no-async-client-component) async function getMapfix() { - const res = await fetch(`/api/mapfixes/${dynamicId.mapfixId}`) + const res = await fetch(`/api/mapfixes/${mapfixId}`) if (res.ok) { setMapfix(await res.json()) } } - getMapfix() - }, [dynamicId.mapfixId]) + async function getAuditEvents() { + const res = await fetch(`/api/mapfixes/${mapfixId}/audit-events?Page=1&Limit=100`) + if (res.ok) { + setAuditEvents(await res.json()) + } + } + getMapfix() + getAuditEvents() + }, [mapfixId]) + + const comments:Comment[] = auditEvents.map((auditEvent) => { + return { + date: auditEvent.CreatedAt, + name: auditEvent.User.toString(), + comment: auditEventMessage(auditEvent), + } + }) if (!mapfix) { return <Webpage> @@ -85,7 +102,7 @@ export default function MapfixInfoPage() { <Webpage> <main className="map-page-main"> <section className="review-section"> - <RatingArea mapfixId={dynamicId.mapfixId} mapfixStatus={mapfix.StatusID} mapfixSubmitter={mapfix.Submitter} mapfixAssetId={mapfix.AssetID} mapfixTargetAssetId={mapfix.TargetAssetID} /> + <RatingArea mapfixId={mapfixId} mapfixStatus={mapfix.StatusID} mapfixSubmitter={mapfix.Submitter} mapfixAssetId={mapfix.AssetID} mapfixTargetAssetId={mapfix.TargetAssetID} /> <TitleAndComments name={mapfix.DisplayName} creator={mapfix.Creator} @@ -94,7 +111,7 @@ export default function MapfixInfoPage() { submitter={mapfix.Submitter} target_asset_id={mapfix.TargetAssetID} description={mapfix.Description} - comments={[]} + comments={comments} /> </section> </main> diff --git a/web/src/app/ts/AuditEvent.ts b/web/src/app/ts/AuditEvent.ts new file mode 100644 index 0000000..eabcaff --- /dev/null +++ b/web/src/app/ts/AuditEvent.ts @@ -0,0 +1,91 @@ +// Shared audit event types +export enum AuditEventType { + Action = 0, + Comment = 1, + ChangeModel = 2, + ChangeValidatedModel = 3, + ChangeDisplayName = 4, + ChangeCreator = 5, + Error = 6, +} + +// Discriminated union types for each event +export type AuditEventData = + | { EventType: AuditEventType.Action; EventData: AuditEventDataAction } + | { EventType: AuditEventType.Comment; EventData: AuditEventDataComment } + | { EventType: AuditEventType.ChangeModel; EventData: AuditEventDataChangeModel } + | { EventType: AuditEventType.ChangeValidatedModel; EventData: AuditEventDataChangeValidatedModel; } + | { EventType: AuditEventType.ChangeDisplayName; EventData: AuditEventDataChangeName; } + | { EventType: AuditEventType.ChangeCreator; EventData: AuditEventDataChangeName; } + | { EventType: AuditEventType.Error; EventData: AuditEventDataError }; + +// Concrete data interfaces +export interface AuditEventDataAction { + target_status: number; +} + +export interface AuditEventDataComment { + comment: string; +} + +export interface AuditEventDataChangeModel { + old_model_id: number; + old_model_version: number; + new_model_id: number; + new_model_version: number; +} + +export interface AuditEventDataChangeValidatedModel { + validated_model_id: number; + validated_model_version: number; +} + +export interface AuditEventDataChangeName { + old_name: string; + new_name: string; +} + +export interface AuditEventDataError { + error: string; +} + +// Full audit event type (mirroring the Go struct) +export interface AuditEvent { + Id: number; + CreatedAt: string; // ISO string, can convert to Date if needed + User: number; + ResourceType: string; // Assuming this is a string enum or similar + ResourceId: number; + EventType: AuditEventType; + EventData: unknown; // You'll decode this into a specific AuditEventData based on `event_type` +} + +// Optional: decode function to parse event_data into strongly-typed structure +export function decodeAuditEvent(event: AuditEvent): string { + switch (event.EventType) { + case AuditEventType.Action:{ + const data = event.EventData as AuditEventDataAction; + return `status changed to ${data.target_status}`; + }case AuditEventType.Comment:{ + const data = event.EventData as AuditEventDataComment; + return data.comment; + }case AuditEventType.ChangeModel:{ + const data = event.EventData as AuditEventDataChangeModel; + return `model changed to asset id = ${data.new_model_id}`; + }case AuditEventType.ChangeValidatedModel:{ + const data = event.EventData as AuditEventDataChangeValidatedModel; + return `model validated as asset id = ${data.validated_model_id}`; + }case AuditEventType.ChangeDisplayName:{ + const data = event.EventData as AuditEventDataChangeName; + return `DisplayName changed to ${data.new_name}`; + }case AuditEventType.ChangeCreator:{ + const data = event.EventData as AuditEventDataChangeName; + return `Creator changed to ${data.new_name}`; + }case AuditEventType.Error:{ + const data = event.EventData as AuditEventDataError; + return `Error: ${data.error}`; + } + default: + throw new Error(`Unknown EventType: ${event.EventType}`); + } +}