- 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
299 lines
9.8 KiB
Markdown
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) |
|