/** * Audit & Blockchain Database Service * Operations for audit logging and blockchain block storage */ import prisma from './prisma'; import type { AuditLog, BlockchainBlock, Prisma } from '@prisma/client'; import type { PaginationOptions, PaginatedResult, DateRangeFilter } from './types'; import { createPaginatedResult } from './types'; // ============================================ // AUDIT LOG OPERATIONS // ============================================ // Create an audit log entry export async function createAuditLog(data: { userId?: string; action: string; entityType: string; entityId?: string; previousValue?: Record; newValue?: Record; metadata?: Record; ipAddress?: string; userAgent?: string; }): Promise { return prisma.auditLog.create({ data }); } // Get audit logs with pagination export async function getAuditLogs( options: PaginationOptions = {}, filters?: { userId?: string; action?: string; entityType?: string; entityId?: string; dateRange?: DateRangeFilter; } ): Promise> { const page = options.page || 1; const limit = options.limit || 50; const skip = (page - 1) * limit; const where: Prisma.AuditLogWhereInput = {}; if (filters?.userId) where.userId = filters.userId; if (filters?.action) where.action = { contains: filters.action, mode: 'insensitive' }; if (filters?.entityType) where.entityType = filters.entityType; if (filters?.entityId) where.entityId = filters.entityId; if (filters?.dateRange) { where.timestamp = { gte: filters.dateRange.start, lte: filters.dateRange.end, }; } const [logs, total] = await Promise.all([ prisma.auditLog.findMany({ where, skip, take: limit, orderBy: { timestamp: 'desc' }, include: { user: true }, }), prisma.auditLog.count({ where }), ]); return createPaginatedResult(logs, total, page, limit); } // Get audit logs for entity export async function getEntityAuditLogs( entityType: string, entityId: string ): Promise { return prisma.auditLog.findMany({ where: { entityType, entityId }, orderBy: { timestamp: 'desc' }, include: { user: true }, }); } // Get audit logs by user export async function getUserAuditLogs( userId: string, options: PaginationOptions = {} ): Promise> { const page = options.page || 1; const limit = options.limit || 50; const skip = (page - 1) * limit; const [logs, total] = await Promise.all([ prisma.auditLog.findMany({ where: { userId }, skip, take: limit, orderBy: { timestamp: 'desc' }, }), prisma.auditLog.count({ where: { userId } }), ]); return createPaginatedResult(logs, total, page, limit); } // Get recent audit logs summary export async function getAuditLogsSummary(hours: number = 24) { const since = new Date(Date.now() - hours * 60 * 60 * 1000); const [ totalLogs, actionBreakdown, entityBreakdown, recentLogs, ] = await Promise.all([ prisma.auditLog.count({ where: { timestamp: { gte: since } }, }), prisma.auditLog.groupBy({ by: ['action'], _count: { action: true }, where: { timestamp: { gte: since } }, }), prisma.auditLog.groupBy({ by: ['entityType'], _count: { entityType: true }, where: { timestamp: { gte: since } }, }), prisma.auditLog.findMany({ where: { timestamp: { gte: since } }, orderBy: { timestamp: 'desc' }, take: 10, include: { user: true }, }), ]); return { totalLogs, actionBreakdown: Object.fromEntries( actionBreakdown.map(a => [a.action, a._count.action]) ), entityBreakdown: Object.fromEntries( entityBreakdown.map(e => [e.entityType, e._count.entityType]) ), recentLogs, }; } // ============================================ // BLOCKCHAIN BLOCK OPERATIONS // ============================================ // Create a blockchain block export async function createBlockchainBlock(data: { index: number; timestamp: Date; previousHash: string; hash: string; nonce: number; blockType: string; plantId?: string; transportEventId?: string; content: Record; }): Promise { return prisma.blockchainBlock.create({ data }); } // Get blockchain block by index export async function getBlockchainBlockByIndex(index: number): Promise { return prisma.blockchainBlock.findUnique({ where: { index }, }); } // Get blockchain block by hash export async function getBlockchainBlockByHash(hash: string): Promise { return prisma.blockchainBlock.findUnique({ where: { hash }, }); } // Get latest blockchain block export async function getLatestBlockchainBlock(): Promise { return prisma.blockchainBlock.findFirst({ orderBy: { index: 'desc' }, }); } // Get blockchain blocks with pagination export async function getBlockchainBlocks( options: PaginationOptions = {}, filters?: { blockType?: string; plantId?: string; transportEventId?: string; } ): Promise> { const page = options.page || 1; const limit = options.limit || 50; const skip = (page - 1) * limit; const where: Prisma.BlockchainBlockWhereInput = {}; if (filters?.blockType) where.blockType = filters.blockType; if (filters?.plantId) where.plantId = filters.plantId; if (filters?.transportEventId) where.transportEventId = filters.transportEventId; const [blocks, total] = await Promise.all([ prisma.blockchainBlock.findMany({ where, skip, take: limit, orderBy: { index: 'desc' }, }), prisma.blockchainBlock.count({ where }), ]); return createPaginatedResult(blocks, total, page, limit); } // Verify blockchain integrity export async function verifyBlockchainIntegrity(): Promise<{ isValid: boolean; invalidBlocks: number[]; totalBlocks: number; }> { const blocks = await prisma.blockchainBlock.findMany({ orderBy: { index: 'asc' }, }); const invalidBlocks: number[] = []; for (let i = 1; i < blocks.length; i++) { const currentBlock = blocks[i]; const previousBlock = blocks[i - 1]; // Check if previous hash matches if (currentBlock.previousHash !== previousBlock.hash) { invalidBlocks.push(currentBlock.index); } } return { isValid: invalidBlocks.length === 0, invalidBlocks, totalBlocks: blocks.length, }; } // Get blockchain statistics export async function getBlockchainStats() { const [ totalBlocks, blocksByType, latestBlock, oldestBlock, ] = await Promise.all([ prisma.blockchainBlock.count(), prisma.blockchainBlock.groupBy({ by: ['blockType'], _count: { blockType: true }, }), prisma.blockchainBlock.findFirst({ orderBy: { index: 'desc' } }), prisma.blockchainBlock.findFirst({ orderBy: { index: 'asc' } }), ]); return { totalBlocks, blocksByType: Object.fromEntries( blocksByType.map(b => [b.blockType, b._count.blockType]) ), latestBlockIndex: latestBlock?.index ?? -1, latestBlockHash: latestBlock?.hash, oldestBlockTimestamp: oldestBlock?.timestamp, latestBlockTimestamp: latestBlock?.timestamp, }; } // Helper to log entity changes export async function logEntityChange>( userId: string | undefined, action: 'CREATE' | 'UPDATE' | 'DELETE', entityType: string, entityId: string, previousValue: T | null, newValue: T | null, metadata?: Record ): Promise { return createAuditLog({ userId, action, entityType, entityId, previousValue: previousValue || undefined, newValue: newValue || undefined, metadata, }); }