Files
website-enchun-mgr/_bmad-output/implementation-artifacts/1-2-posts-collection.story.md
pkupuk 8c87d71aa2 docs: update sprint-status and story files after Sprint 1 completion
- 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
2026-01-31 17:34:41 +08:00

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

  1. AC1 - excerpt Field Added: Textarea field for article summary (200 char limit, multiline)
  2. AC2 - ogImage Field Added: Upload field for social sharing image (separate from SEO meta.image)
  3. AC3 - showInFooter Field Added: Checkbox field (default: false, sidebar position)
  4. AC4 - status Field Added: Select field with options: draft, review, published (default: draft, sidebar)
  5. AC5 - Fields in Correct Tabs: excerpt and ogImage in Content tab, showInFooter and status in sidebar
  6. AC6 - TypeScript Types Generated: Running pnpm build regenerates payload-types.ts without errors
  7. 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

  1. ogImage vs meta.image: The SEO plugin provides meta.image for Open Graph, but this story adds a separate ogImage field specifically for social media sharing. They serve different purposes and both should be available.

  2. status vs _status: Payload CMS has a built-in _status field (draft/published). The new status field is for custom workflow (draft → review → published) and is independent of the built-in draft system.

  3. 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 build in backend directory
    • Check that payload-types.ts regenerates without errors
    • Verify new fields are in TypeScript types
  • 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
  • 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)