- Mark Story 1-2-collections-definition as DONE (100%)
- Update all Sprint 1 split stories to Done status
- Epic 1 completed_stories: 0 → 2
- NFR4 (load testing): planned → done
- NFR9 (audit logging): planned → done
Implementation already committed in 7fd73e0
9.8 KiB
Story 1.2-c: Complete Posts Collection (Story 1.2 split)
Status: Done Epic: Epic 1 - Webflow to Payload CMS + Astro Migration Priority: P1 (High - Required for Story 1.9 Blog System) Estimated Time: 0.5 hour (30 minutes)
Story
As a CMS Administrator, I want a complete Posts collection with all necessary fields including excerpt, social images, and status tracking, So that blog posts have complete metadata for display and Story 1.9 (Blog System) can proceed.
Context
This is a Sprint 1 split story from the original Story 1.2 (Payload CMS Collections Definition). The Posts collection currently exists but is incomplete - missing 4 critical fields needed for the Blog System (Story 1.9).
Story Source:
- Split from
docs/prd/05-epic-stories.md- Story 1.2 - Technical spec:
docs/prd/payload-cms-modification-plan.md- Task 1.2.3 - Execution plan:
docs/prd/epic-1-execution-plan.md- Story 1.2 Phase 3
Current State:
- File exists at
apps/backend/src/collections/Posts/index.ts - Has many fields including: title, heroImage, content, categories, publishedAt, authors, etc.
- Has SEO plugin with meta.image (but this is for SEO, not social sharing)
- Missing: excerpt, ogImage (social sharing), showInFooter, status
Acceptance Criteria
- AC1 - excerpt Field Added: Textarea field for article summary (200 char limit, multiline)
- AC2 - ogImage Field Added: Upload field for social sharing image (separate from SEO meta.image)
- AC3 - showInFooter Field Added: Checkbox field (default: false, sidebar position)
- AC4 - status Field Added: Select field with options: draft, review, published (default: draft, sidebar)
- AC5 - Fields in Correct Tabs: excerpt and ogImage in Content tab, showInFooter and status in sidebar
- AC6 - TypeScript Types Generated: Running
pnpm buildregenerates payload-types.ts without errors - AC7 - Admin UI Working: All fields visible and functional in Payload Admin panel
Previous Story Learnings (from Story 1.2)
From Story 1.2 execution (43% complete):
- Posts collection file already exists with complex tab structure
- Has Content tab with heroImage and content
- Has Meta tab with categories and relatedPosts (both sidebar position)
- Has SEO tab (from plugin) with meta.image
- Has publishedAt and authors in sidebar
- This is a modification task, not creation
Dev Technical Guidance
Current Posts Collection Structure (Key Parts)
fields: [
{ name: 'title', type: 'text', required: true },
{
type: 'tabs',
tabs: [
{
label: 'Content',
fields: [
{ name: 'heroImage', type: 'upload', relationTo: 'media' },
{ name: 'content', type: 'richText', required: true },
// ⭐ ADD excerpt HERE
// ⭐ ADD ogImage HERE (after heroImage)
],
},
{
label: 'Meta',
fields: [
{ name: 'relatedPosts', type: 'relationship', admin: { position: 'sidebar' } },
{ name: 'categories', type: 'relationship', admin: { position: 'sidebar' } },
// ⭐ ADD showInFooter HERE (with position: 'sidebar')
],
},
{
name: 'meta',
label: 'SEO',
fields: [/* SEO plugin fields including meta.image */],
},
],
},
{ name: 'publishedAt', type: 'date', admin: { position: 'sidebar' } },
{ name: 'authors', type: 'relationship', admin: { position: 'sidebar' } },
{ name: 'populatedAuthors', type: 'array', admin: { disabled: true, readOnly: true } },
...slugField(),
// ⭐ ADD status HERE (with position: 'sidebar')
]
Required Changes
1. Add excerpt field (in Content tab, after content field):
{
name: 'excerpt',
type: 'text',
label: '文章摘要',
admin: {
description: '顯示在文章列表頁,建議 150-200 字',
multiline: true,
},
maxLength: 200,
}
2. Add ogImage field (in Content tab, after heroImage field):
{
name: 'ogImage',
type: 'upload',
relationTo: 'media',
label: '社群分享圖片',
admin: {
description: 'Facebook/LINE 分享時顯示的預覽圖,建議 1200x630px',
},
}
3. Add showInFooter field (in Meta tab fields array):
{
name: 'showInFooter',
type: 'checkbox',
label: '顯示在頁腳',
defaultValue: false,
admin: {
position: 'sidebar',
},
}
4. Add status field (at root level, after slugField()):
{
name: 'status',
type: 'select',
label: '文章狀態',
defaultValue: 'draft',
options: [
{ label: '草稿', value: 'draft' },
{ label: '審核中', value: 'review' },
{ label: '已發布', value: 'published' },
],
admin: {
position: 'sidebar',
},
}
Field Placement Summary
| Field | Location | Notes |
|---|---|---|
| excerpt | Content tab | After content field, multiline, maxLength: 200 |
| ogImage | Content tab | After heroImage field |
| showInFooter | Meta tab | position: 'sidebar' |
| status | Root fields | position: 'sidebar' |
Important Notes
-
ogImage vs meta.image: The SEO plugin provides
meta.imagefor Open Graph, but this story adds a separateogImagefield specifically for social media sharing. They serve different purposes and both should be available. -
status vs _status: Payload CMS has a built-in
_statusfield (draft/published). The newstatusfield is for custom workflow (draft → review → published) and is independent of the built-in draft system. -
Tab Structure: The Posts collection uses tabs heavily. Ensure new fields are added to the correct tabs.
File Structure
apps/backend/src/
└── collections/
└── Posts/
└── index.ts ← Modify this file (ADD 4 fields)
No payload.config.ts changes needed - collection already registered.
Tasks / Subtasks
-
Task 1: Modify Posts/index.ts (AC: 1, 2, 3, 4, 5)
- Add excerpt field in Content tab (after content)
- Add ogImage field in Content tab (after heroImage)
- Add showInFooter field in Meta tab
- Add status field at root level
- Verify all fields in correct locations
-
Task 2: Verify TypeScript types (AC: 6)
- Run
pnpm buildin backend directory - Check that payload-types.ts regenerates without errors
- Verify new fields are in TypeScript types
- Run
-
Task 3: Test Admin UI (AC: 7)
- Start dev server:
pnpm dev - Login to Payload Admin
- Navigate to Posts collection
- Create test post with all new fields
- Verify excerpt accepts up to 200 characters
- Verify ogImage upload works
- Verify showInFooter checkbox works
- Verify status dropdown shows all options
- Verify sidebar position for showInFooter and status
- Start dev server:
-
Task 4: Verify Blog Integration
- Test that excerpt displays in article list
- Test that ogImage is used for social sharing
- Test that status filtering works (draft/review/published)
- Test that showInFooter filters footer articles
Testing Requirements
Unit Tests
// apps/backend/src/collections/Posts/__tests__/Posts.spec.ts
describe('Posts Collection - New Fields', () => {
it('should have excerpt field', () => {
const excerptField = Posts.fields.find(f => f.name === 'excerpt')
expect(excerptField).toBeDefined()
expect(excerptField?.maxLength).toBe(200)
})
it('should have ogImage field', () => {
const ogImageField = Posts.fields.find(f => f.name === 'ogImage')
expect(ogImageField).toBeDefined()
expect(ogImageField?.type).toBe('upload')
})
it('should have showInFooter field', () => {
const showInFooterField = Posts.fields.find(f => f.name === 'showInFooter')
expect(showInFooterField).toBeDefined()
expect(showInFooterField?.type).toBe('checkbox')
expect(showInFooterField?.defaultValue).toBe(false)
})
it('should have status field with correct options', () => {
const statusField = Posts.fields.find(f => f.name === 'status')
expect(statusField).toBeDefined()
expect(statusField?.type).toBe('select')
expect(statusField?.options).toHaveLength(3)
expect(statusField?.defaultValue).toBe('draft')
})
})
Manual Testing Checklist
- excerpt field is multiline textarea
- excerpt field limits to 200 characters
- excerpt shows description text
- ogImage uploads to media collection
- ogImage shows description about 1200x630px
- showInFooter checkbox works
- showInFooter default is unchecked
- showInFooter appears in sidebar
- status dropdown shows 3 options
- status default is "草稿"
- status appears in sidebar
- Can save post with all new fields
- Can edit post and change values
Risk Assessment
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| Tab structure confusion | Low | Medium | Follow exact tab placement instructions |
| ogImage vs meta.image confusion | Low | Low | Document difference clearly |
| status vs _status confusion | Low | Low | Document independence from built-in field |
| maxLength not working | Low | Low | Test with >200 character input |
Definition of Done
- All 4 new fields added to Posts/index.ts
- Fields placed in correct tabs
- TypeScript types generate successfully
- Admin UI functional with all fields
- Unit tests pass
- Code follows existing patterns
- No linting errors
- sprint-status.yaml updated to mark story as ready-for-dev
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) |