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

299 lines
9.8 KiB
Markdown

# 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)
```typescript
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):
```typescript
{
name: 'excerpt',
type: 'text',
label: '文章摘要',
admin: {
description: '顯示在文章列表頁,建議 150-200 字',
multiline: true,
},
maxLength: 200,
}
```
**2. Add ogImage field** (in Content tab, after heroImage field):
```typescript
{
name: 'ogImage',
type: 'upload',
relationTo: 'media',
label: '社群分享圖片',
admin: {
description: 'Facebook/LINE 分享時顯示的預覽圖,建議 1200x630px',
},
}
```
**3. Add showInFooter field** (in Meta tab fields array):
```typescript
{
name: 'showInFooter',
type: 'checkbox',
label: '顯示在頁腳',
defaultValue: false,
admin: {
position: 'sidebar',
},
}
```
**4. Add status field** (at root level, after slugField()):
```typescript
{
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
- [x] **Task 1: Modify Posts/index.ts** (AC: 1, 2, 3, 4, 5)
- [x] Add excerpt field in Content tab (after content)
- [x] Add ogImage field in Content tab (after heroImage)
- [x] Add showInFooter field in Meta tab
- [x] Add status field at root level
- [x] Verify all fields in correct locations
- [x] **Task 2: Verify TypeScript types** (AC: 6)
- [x] Run `pnpm build` in backend directory
- [x] Check that payload-types.ts regenerates without errors
- [x] Verify new fields are in TypeScript types
- [x] **Task 3: Test Admin UI** (AC: 7)
- [x] Start dev server: `pnpm dev`
- [x] Login to Payload Admin
- [x] Navigate to Posts collection
- [x] Create test post with all new fields
- [x] Verify excerpt accepts up to 200 characters
- [x] Verify ogImage upload works
- [x] Verify showInFooter checkbox works
- [x] Verify status dropdown shows all options
- [x] Verify sidebar position for showInFooter and status
- [x] **Task 4: Verify Blog Integration**
- [x] Test that excerpt displays in article list
- [x] Test that ogImage is used for social sharing
- [x] Test that status filtering works (draft/review/published)
- [x] Test that showInFooter filters footer articles
## Testing Requirements
### Unit Tests
```typescript
// 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) |