({});
return (
handleSearchChange(e.target.value)}
className="w-64"
/>
handleFilterChange("due_before", date)}
/>
);
}
```
## Event Publishing
```python
# backend/src/services/task_events.py
from dapr.clients import DaprClient
import json
class TaskEventPublisher:
"""Publish task events to Kafka via Dapr."""
async def publish_task_created(self, task: Task):
await self._publish("task.created", task)
async def publish_task_updated(self, task: Task):
await self._publish("task.updated", task)
async def publish_task_deleted(self, task_id: str, user_id: str):
with DaprClient() as client:
client.publish_event(
pubsub_name="taskpubsub",
topic_name="task-events",
data=json.dumps({
"event_type": "task.deleted",
"task_id": task_id,
"user_id": user_id,
"timestamp": datetime.utcnow().isoformat()
})
)
async def _publish(self, event_type: str, task: Task):
with DaprClient() as client:
client.publish_event(
pubsub_name="taskpubsub",
topic_name="task-events",
data=json.dumps({
"event_type": event_type,
"task_id": str(task.id),
"user_id": str(task.user_id),
"task": task.model_dump(mode="json"),
"timestamp": datetime.utcnow().isoformat()
})
)
```
## Alembic Migration
```python
# backend/alembic/versions/xxx_add_advanced_features.py
"""Add advanced task features
Revision ID: xxx
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
def upgrade():
# Add new columns to task table
op.add_column('task', sa.Column('priority', sa.String(10), default='medium'))
op.add_column('task', sa.Column('due_date', sa.DateTime(), nullable=True))
op.add_column('task', sa.Column('recurrence_type', sa.String(10), default='none'))
op.add_column('task', sa.Column('recurrence_interval', sa.Integer(), nullable=True))
op.add_column('task', sa.Column('next_occurrence', sa.DateTime(), nullable=True))
# Create tag table
op.create_table(
'tag',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('name', sa.String(50), nullable=False),
sa.Column('color', sa.String(7), default='#6B7280'),
sa.Column('user_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('user.id'))
)
# Create task_tag junction table
op.create_table(
'task_tag',
sa.Column('task_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('task.id'), primary_key=True),
sa.Column('tag_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('tag.id'), primary_key=True)
)
# Create reminder table
op.create_table(
'reminder',
sa.Column('id', postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column('task_id', postgresql.UUID(as_uuid=True), sa.ForeignKey('task.id')),
sa.Column('remind_at', sa.DateTime(), nullable=False),
sa.Column('message', sa.Text(), nullable=True),
sa.Column('is_sent', sa.Boolean(), default=False),
sa.Column('created_at', sa.DateTime(), default=sa.func.now())
)
# Create indexes
op.create_index('ix_task_priority', 'task', ['priority'])
op.create_index('ix_task_due_date', 'task', ['due_date'])
op.create_index('ix_tag_name', 'tag', ['name'])
op.create_index('ix_reminder_remind_at', 'reminder', ['remind_at'])
def downgrade():
op.drop_index('ix_reminder_remind_at')
op.drop_index('ix_tag_name')
op.drop_index('ix_task_due_date')
op.drop_index('ix_task_priority')
op.drop_table('reminder')
op.drop_table('task_tag')
op.drop_table('tag')
op.drop_column('task', 'next_occurrence')
op.drop_column('task', 'recurrence_interval')
op.drop_column('task', 'recurrence_type')
op.drop_column('task', 'due_date')
op.drop_column('task', 'priority')
```
## Verification Checklist
- [ ] Database models updated (Task, Tag, TaskTag, Reminder)
- [ ] Alembic migration created and applied
- [ ] Tags API endpoints working
- [ ] Reminders API endpoints working
- [ ] Filter/Search/Sort working on tasks list
- [ ] Priority badges displayed in UI
- [ ] Tag management UI working
- [ ] Due date picker working
- [ ] Events published to Kafka
- [ ] All tests passing
## References
- [Phase 5 Spec](../../../spec-prompt-phase-5.md) - User stories
- [SQLModel Relationships](https://sqlmodel.tiangolo.com/tutorial/relationship-attributes/)
- [FastAPI Query Parameters](https://fastapi.tiangolo.com/tutorial/query-params/)
- [Phase 5 Constitution](../../../constitution-prompt-phase-5.md)