Implement Sprint 1 stories: collections, RBAC, audit logging, load testing

Complete 6 Sprint 1 stories for Epic 1 web migration infrastructure.

Portfolio Collection:
- Add 7 fields: title, slug, url, image, description, websiteType, tags
- Configure R2 storage and authenticated access control

Categories Collection:
- Add nameEn, order, textColor, backgroundColor fields
- Add color picker UI configuration

Posts Collection:
- Add excerpt with 200 char limit and ogImage for social sharing
- Add showInFooter checkbox and status select (draft/review/published)

Role-Based Access Control:
- Add role field to Users collection (admin/editor)
- Create adminOnly and authenticated access functions
- Apply access rules to Portfolio, Categories, Posts, Users collections

Audit Logging System (NFR9):
- Create Audit collection with timestamps for 90-day retention
- Add auditLogger utility for login/logout/content change tracking
- Add auditChange and auditGlobalChange hooks to all collections and globals
- Add cleanupAuditLogs job with 90-day retention policy

Load Testing Framework (NFR4):
- Add k6 load testing with 3 scripts: public-browsing, admin-operations, api-performance
- Configure targets: p95 < 500ms, error rate < 1%, 100 concurrent users
- Add verification script and comprehensive documentation

Other Changes:
- Remove unused Form blocks
- Add Header/Footer audit hooks
- Regenerate Payload TypeScript types
This commit is contained in:
2026-01-31 17:20:35 +08:00
parent 0846318d6e
commit 7fd73e0e3d
48 changed files with 19497 additions and 5261 deletions

View File

@@ -1,111 +0,0 @@
import { RequiredDataFromCollectionSlug } from 'payload'
export const contactForm: RequiredDataFromCollectionSlug<'forms'> = {
confirmationMessage: {
root: {
type: 'root',
children: [
{
type: 'heading',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'The contact form has been submitted successfully.',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
tag: 'h2',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
},
confirmationType: 'message',
createdAt: '2023-01-12T21:47:41.374Z',
emails: [
{
emailFrom: '"Payload" \u003Cdemo@payloadcms.com\u003E',
emailTo: '{{email}}',
message: {
root: {
type: 'root',
children: [
{
type: 'paragraph',
children: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: 'Your contact form submission was successfully received.',
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
textFormat: 0,
version: 1,
},
],
direction: 'ltr',
format: '',
indent: 0,
version: 1,
},
},
subject: "You've received a new message.",
},
],
fields: [
{
name: 'full-name',
blockName: 'full-name',
blockType: 'text',
label: 'Full Name',
required: true,
width: 100,
},
{
name: 'email',
blockName: 'email',
blockType: 'email',
label: 'Email',
required: true,
width: 100,
},
{
name: 'phone',
blockName: 'phone',
blockType: 'number',
label: 'Phone',
required: false,
width: 100,
},
{
name: 'message',
blockName: 'message',
blockType: 'textarea',
label: 'Message',
required: true,
width: 100,
},
],
redirect: undefined,
submitButtonLabel: 'Submit',
title: 'Contact Form',
updatedAt: '2023-01-12T21:47:41.374Z',
}

View File

@@ -1,6 +1,5 @@
import type { CollectionSlug, GlobalSlug, Payload, PayloadRequest, File } from 'payload'
import { contactForm as contactFormData } from './contact-form'
import { contact as contactPageData } from './contact-page'
import { home } from './home'
import { image1 } from './image-1'
@@ -15,8 +14,6 @@ const collections: CollectionSlug[] = [
'media',
'pages',
'posts',
'forms',
'form-submissions',
'search',
]
const globals: GlobalSlug[] = ['header', 'footer']
@@ -257,14 +254,6 @@ export const seed = async ({
},
})
payload.logger.info(`— Seeding contact form...`)
const contactForm = await payload.create({
collection: 'forms',
depth: 0,
data: contactFormData,
})
payload.logger.info(`— Seeding pages...`)
const [_, contactPage] = await Promise.all([