# Story 1.12-a: Add Audit Logging System (NFR9)

**Status:** Draft
**Epic:** Epic 1 - Webflow to Payload CMS + Astro Migration
**Priority:** P1 (High - NFR9 Compliance Requirement)
**Estimated Time:** 2 hours

## Story

**As a** System Administrator,
**I want** an audit logging system that records all critical operations,
**So that** I can track user actions for compliance and security auditing (NFR9 requirement).

## Context

This is a Sprint 1 addition story. NFR9 requires logging all critical operations (login, content changes, settings modifications) for audit purposes. This was identified as missing from the original plan.

**Story Source:**
- NFR9 from `docs/prd/02-requirements.md`
- Sprint 1 adjustments in `sprint-status.yaml`

**NFR9 Requirement:**
> "The system must log all critical operations (login, content changes, settings modifications) for audit purposes."

## Acceptance Criteria

### Part 1: Audit Collection
1. **AC1 - Audit Collection Created**: New Audit collection with fields for action, user, timestamp, collection, documentId, before/after data
2. **AC2 - Indexes Configured**: Indexes on userId, action, timestamp for efficient querying

### Part 2: Logging Hooks
3. **AC3 - Login/Logout Logging**: Authentication events logged with user and timestamp
4. **AC4 - Content Changes Logging**: create/update/delete operations logged with before/after values
5. **AC5 - Settings Logging**: Global changes (Header/Footer) logged with user attribution

### Part 3: Admin Interface
6. **AC6 - Audit Log Viewer**: Admin-only view to browse and filter audit logs
7. **AC7 - 90-Day Retention**: Auto-delete logs older than 90 days

### Part 4: Testing
8. **AC8 - TypeScript Types**: Running `pnpm build` regenerates payload-types.ts without errors
9. **AC9 - Functionality Tested**: All logging scenarios tested and working

## Dev Technical Guidance

### Task 1: Create Audit Collection

**File:** `apps/backend/src/collections/Audit/index.ts`

```typescript
import type { CollectionConfig } from 'payload'
import { adminOnly } from '../../access/adminOnly'

export const Audit: CollectionConfig = {
  slug: 'audit',
  access: {
    create: () => false,  // Only system can create
    delete: adminOnly,    // Only admins can delete
    read: adminOnly,      // Only admins can read
    update: () => false,  // Logs are immutable
  },
  admin: {
    defaultColumns: ['timestamp', 'action', 'user', 'collection'],
    useAsTitle: 'action',
    description: '審計日誌 - 記錄所有系統關鍵操作',
  },
  fields: [
    {
      name: 'action',
      type: 'select',
      required: true,
      options: [
        { label: '登入', value: 'login' },
        { label: '登出', value: 'logout' },
        { label: '創建', value: 'create' },
        { label: '更新', value: 'update' },
        { label: '刪除', value: 'delete' },
        { label: '設定修改', value: 'settings' },
      ],
    },
    {
      name: 'user',
      type: 'relationship',
      relationTo: 'users',
      admin: {
        position: 'sidebar',
      },
    },
    {
      name: 'collection',
      type: 'text',
      admin: {
        description: '操作的 collection 名稱',
      },
    },
    {
      name: 'documentId',
      type: 'text',
      admin: {
        description: '受影響文件的 ID',
      },
    },
    {
      name: 'before',
      type: 'json',
      admin: {
        description: '操作前的數據',
      },
    },
    {
      name: 'after',
      type: 'json',
      admin: {
        description: '操作後的數據',
      },
    },
    {
      name: 'ipAddress',
      type: 'text',
      admin: {
        description: '使用者 IP 地址',
      },
    },
    {
      name: 'userAgent',
      type: 'text',
      admin: {
        description: '瀏覽器 User Agent',
      },
    },
    {
      name: 'timestamp',
      type: 'date',
      required: true,
      defaultValue: () => new Date(),
      admin: {
        position: 'sidebar',
      },
    },
  ],
  timestamps: false,
}
```

**Register in payload.config.ts:**
```typescript
import { Audit } from './collections/Audit'

collections: [Pages, Posts, Media, Categories, Users, Audit],
```

### Task 2: Create Logging Utility

**File:** `apps/backend/src/utilities/auditLogger.ts`

```typescript
import type { PayloadRequest } from 'payload'
import { payload } from '@/payload'

export interface AuditLogOptions {
  action: 'login' | 'logout' | 'create' | 'update' | 'delete' | 'settings'
  collection?: string
  documentId?: string
  before?: Record<string, unknown>
  after?: Record<string, unknown>
  req: PayloadRequest
}

/**
 * 創建審計日誌記錄
 */
export async function createAuditLog(options: AuditLogOptions): Promise<void> {
  const { action, collection, documentId, before, after, req } = options

  try {
    await payload.create({
      collection: 'audit',
      data: {
        action,
        user: req.user?.id || null,
        collection,
        documentId,
        before,
        after,
        ipAddress: req.headers.get('x-forwarded-for') || req.headers.get('cf-connecting-ip') || 'unknown',
        userAgent: req.headers.get('user-agent') || 'unknown',
        timestamp: new Date(),
      },
    })
  } catch (error) {
    console.error('Failed to create audit log:', error)
    // Don't throw - audit logging failure shouldn't break the main operation
  }
}
```

### Task 3: Add Login/Logout Hooks

**File:** `apps/backend/src/collections/Users/index.ts`

```typescript
import { createAuditLog } from '../../utilities/auditLogger'

export const Users: CollectionConfig = {
  // ... existing config ...
  hooks: {
    afterLogin: [
      async ({ req }) => {
        await createAuditLog({
          action: 'login',
          req,
        })
      },
    ],
    afterLogout: [
      async ({ req }) => {
        await createAuditLog({
          action: 'logout',
          req,
        })
      },
    ],
    // ... existing hooks ...
  },
}
```

### Task 4: Add Content Change Hooks

**For each collection (Posts, Pages, Categories, Portfolio):**

**File:** `apps/backend/src/collections/Posts/index.ts`

```typescript
import { createAuditLog } from '../../utilities/auditLogger'

export const Posts: CollectionConfig = {
  // ... existing config ...
  hooks: {
    afterChange: [
      async ({ doc, previousDoc, req, operation }) => {
        // Only log for authenticated users, not system operations
        if (!req.user) return

        const action = operation === 'create' ? 'create' : 'update'

        await createAuditLog({
          action,
          collection: 'posts',
          documentId: doc.id,
          before: operation === 'update' ? previousDoc : undefined,
          after: doc,
          req,
        })

        // ... existing revalidatePost hook ...
      },
    ],
    afterDelete: [
      async ({ doc, req }) => {
        if (!req.user) return

        await createAuditLog({
          action: 'delete',
          collection: 'posts',
          documentId: doc.id,
          before: doc,
          req,
        })

        // ... existing revalidateDelete hook ...
      },
    ],
  },
}
```

### Task 5: Add Settings Change Hooks

**File:** `apps/backend/src/Header/config.ts` and `Footer/config.ts`

```typescript
import { createAuditLog } from '../utilities/auditLogger'

export const Header: GlobalConfig = {
  slug: 'header',
  hooks: {
    afterChange: [
      async ({ doc, previousDoc, req }) => {
        if (!req.user) return

        await createAuditLog({
          action: 'settings',
          collection: 'header',
          documentId: doc.id,
          before: previousDoc,
          after: doc,
          req,
        })
      },
    ],
  },
  // ... rest of config ...
}
```

### Task 6: Implement Log Retention (90 Days)

**File:** `apps/backend/src/cron/cleanupAuditLogs.ts`

```typescript
import { payload } from '@/payload'

/**
 * 定期清理 90 天前的審計日誌
 * 應該通過 cron job 每天執行
 */
export async function cleanupOldAuditLogs(): Promise<void> {
  const ninetyDaysAgo = new Date()
  ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90)

  try {
    const result = await payload.delete({
      collection: 'audit',
      where: {
        timestamp: {
          less_than: ninetyDaysAgo,
        },
      },
    })
    console.log(`Cleaned up ${result.deleted} old audit logs`)
  } catch (error) {
    console.error('Failed to cleanup audit logs:', error)
  }
}
```

**Configure in payload.config.ts jobs:**

```typescript
jobs: {
  tasks: [
    {
      cron: '0 2 * * *', // Run daily at 2 AM
      handler: async () => {
        const { cleanupOldAuditLogs } = await import('./src/cron/cleanupAuditLogs')
        await cleanupOldAuditLogs()
      },
    },
  ],
}
```

### File Structure

```
apps/backend/src/
├── collections/
│   └── Audit/
│       └── index.ts              ← CREATE
├── utilities/
│   └── auditLogger.ts            ← CREATE
├── cron/
│   └── cleanupAuditLogs.ts       ← CREATE
├── collections/
│   ├── Users/index.ts            ← MODIFY (add login/logout hooks)
│   ├── Posts/index.ts            ← MODIFY (add audit hooks)
│   ├── Pages/index.ts            ← MODIFY (add audit hooks)
│   ├── Categories.ts             ← MODIFY (add audit hooks)
│   └── Portfolio/index.ts        ← MODIFY (add audit hooks)
├── Header/
│   └── config.ts                 ← MODIFY (add audit hooks)
├── Footer/
│   └── config.ts                 ← MODIFY (add audit hooks)
└── payload.config.ts             ← MODIFY (register Audit, add cron)
```

## Tasks / Subtasks

### Part 1: Audit Collection
- [ ] **Task 1.1**: Create Audit collection
  - [ ] Create Audit/index.ts with all fields
  - [ ] Configure access control (admin only)
  - [ ] Register in payload.config.ts

### Part 2: Logging Utility
- [ ] **Task 2.1**: Create auditLogger utility
  - [ ] Create auditLogger.ts
  - [ ] Implement createAuditLog function
  - [ ] Add error handling

### Part 3: Authentication Logging
- [ ] **Task 3.1**: Add login hook to Users
- [ ] **Task 3.2**: Add logout hook to Users

### Part 4: Content Change Logging
- [ ] **Task 4.1**: Add audit hooks to Posts
- [ ] **Task 4.2**: Add audit hooks to Pages
- [ ] **Task 4.3**: Add audit hooks to Categories
- [ ] **Task 4.4**: Add audit hooks to Portfolio

### Part 5: Settings Logging
- [ ] **Task 5.1**: Add audit hooks to Header
- [ ] **Task 5.2**: Add audit hooks to Footer

### Part 6: Log Retention
- [ ] **Task 6.1**: Create cleanupAuditLogs function
- [ ] **Task 6.2**: Configure cron job in payload.config.ts

### Part 7: Testing
- [ ] **Task 7.1**: Verify TypeScript types
- [ ] **Task 7.2**: Test login/logout logging
- [ ] **Task 7.3**: Test content change logging
- [ ] **Task 7.4**: Test settings change logging
- [ ] **Task 7.5**: Test admin-only access
- [ ] **Task 7.6**: Test 90-day cleanup

## Testing Requirements

### Unit Tests
```typescript
// apps/backend/src/utilities/__tests__/auditLogger.spec.ts
import { createAuditLog } from '../auditLogger'

describe('Audit Logger', () => {
  it('should create audit log entry', async () => {
    // Test implementation
  })

  it('should handle errors gracefully', async () => {
    // Test implementation
  })
})
```

### Manual Testing Checklist
- [ ] Login creates audit log
- [ ] Logout creates audit log
- [ ] Creating post creates audit log
- [ ] Updating post creates audit log with before/after
- [ ] Deleting post creates audit log with before data
- [ ] Updating Header creates audit log
- [ ] Non-admin users cannot access Audit collection
- [ ] Admin users can view Audit collection
- [ ] Audit logs show correct user attribution
- [ ] IP addresses are captured
- [ ] User agents are captured
- [ ] Old logs are cleaned up (manual test of cleanup function)

## Risk Assessment

| Risk | Probability | Impact | Mitigation |
|------|------------|--------|------------|
| Performance impact | Medium | Medium | Async logging, don't block main operations |
| Data growth | High | Low | 90-day retention policy |
| Missing events | Low | Medium | Comprehensive hook coverage |
| Privacy concerns | Low | Medium | Admin-only access, no sensitive data in logs |

## Definition of Done

- [ ] Audit collection created and registered
- [ ] Audit logger utility created
- [ ] Login/logout logging implemented
- [ ] Content change logging implemented
- [ ] Settings change logging implemented
- [ ] 90-day retention cron job configured
- [ ] TypeScript types generate successfully
- [ ] All logging scenarios tested
- [ ] Admin-only access verified
- [ ] sprint-status.yaml updated

## Dev Agent Record

### Agent Model Used
*To be filled by Dev Agent*

### Debug Log References
*To be filled by Dev Agent*

### Completion Notes
*To be filled by Dev Agent*

### File List
*To be filled by Dev Agent*

## Change Log

| Date | Action | Author |
|------|--------|--------|
| 2026-01-31 | Story created (Draft) | SM Agent (Bob) |
