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
81 lines
1.7 KiB
TypeScript
81 lines
1.7 KiB
TypeScript
import type { CollectionConfig } from 'payload'
|
|
|
|
import { anyone } from '../../access/anyone'
|
|
import { authenticated } from '../../access/authenticated'
|
|
import { auditChange } from '../../collections/Audit/hooks/auditHooks'
|
|
import { slugField } from '@/fields/slug'
|
|
|
|
export const Portfolio: CollectionConfig = {
|
|
slug: 'portfolio',
|
|
access: {
|
|
create: authenticated,
|
|
read: anyone,
|
|
update: authenticated,
|
|
delete: authenticated,
|
|
},
|
|
admin: {
|
|
useAsTitle: 'title',
|
|
defaultColumns: ['title', 'websiteType', 'updatedAt'],
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'title',
|
|
type: 'text',
|
|
required: true,
|
|
},
|
|
{
|
|
name: 'url',
|
|
type: 'text',
|
|
admin: {
|
|
description: 'Website URL (e.g., https://example.com)',
|
|
},
|
|
},
|
|
{
|
|
name: 'image',
|
|
type: 'upload',
|
|
relationTo: 'media',
|
|
required: true,
|
|
admin: {
|
|
description: 'Preview image stored in R2',
|
|
},
|
|
},
|
|
{
|
|
name: 'description',
|
|
type: 'textarea',
|
|
},
|
|
{
|
|
name: 'websiteType',
|
|
type: 'select',
|
|
options: [
|
|
{ label: '企業官網', value: 'corporate' },
|
|
{ label: '電商網站', value: 'ecommerce' },
|
|
{ label: '活動頁面', value: 'landing' },
|
|
{ label: '品牌網站', value: 'brand' },
|
|
{ label: '其他', value: 'other' },
|
|
],
|
|
required: true,
|
|
},
|
|
{
|
|
name: 'tags',
|
|
type: 'array',
|
|
fields: [
|
|
{
|
|
name: 'tag',
|
|
type: 'text',
|
|
},
|
|
],
|
|
},
|
|
...slugField(),
|
|
],
|
|
hooks: {
|
|
afterChange: [auditChange('portfolio')],
|
|
afterDelete: [auditChange('portfolio')],
|
|
},
|
|
versions: {
|
|
drafts: {
|
|
autosave: true,
|
|
},
|
|
maxPerDoc: 10,
|
|
},
|
|
}
|