add models, migrations, and CRUD
Schema mirrors the webapp tasks module's ScheduledEvent + EventCompletion shape: - tasks.tasks (NIP-52 kind 31922 cache): (pubkey, d_tag) is the parameterized-replaceable key. JSON-encoded participants / categories / recurrence columns are decoded on read via _parse_task_row so each model can keep clean field validators. - tasks.completions (kind 31925 cache): unique on (task_address, pubkey, occurrence). occurrence is NULL for one-shot tasks; create_completion deletes any prior claim for the same triple so the latest event wins. - tasks.settings: singleton row with public_listing toggle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bc88b421b6
commit
6fbb6d4a42
3 changed files with 521 additions and 3 deletions
157
models.py
157
models.py
|
|
@ -1 +1,156 @@
|
|||
# Pydantic models are filled in by the next commit.
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field, validator
|
||||
|
||||
|
||||
TaskStatus = Literal["claimed", "in-progress", "completed", "blocked", "cancelled"]
|
||||
|
||||
|
||||
class Participant(BaseModel):
|
||||
pubkey: str
|
||||
type: str | None = None # "required" | "optional" | "organizer"
|
||||
|
||||
|
||||
class Recurrence(BaseModel):
|
||||
frequency: Literal["daily", "weekly"]
|
||||
day_of_week: str | None = None # for weekly: monday..sunday
|
||||
end_date: str | None = None # YYYY-MM-DD, optional cutoff
|
||||
|
||||
|
||||
class CreateTask(BaseModel):
|
||||
wallet: str | None = None # filled from caller's wallet if absent
|
||||
title: str
|
||||
start_date: str # YYYY-MM-DD or ISO datetime
|
||||
end_date: str | None = None
|
||||
description: str = ""
|
||||
location: str | None = None
|
||||
status: str = "pending"
|
||||
event_type: str = "task" # 'task' | 'announcement'
|
||||
participants: list[Participant] = Field(default_factory=list)
|
||||
categories: list[str] = Field(default_factory=list)
|
||||
recurrence: Recurrence | None = None
|
||||
|
||||
@validator("participants", pre=True)
|
||||
def parse_participants(cls, v):
|
||||
if isinstance(v, str):
|
||||
return json.loads(v) if v else []
|
||||
return v or []
|
||||
|
||||
@validator("categories", pre=True)
|
||||
def parse_categories(cls, v):
|
||||
if isinstance(v, str):
|
||||
return json.loads(v) if v else []
|
||||
return v or []
|
||||
|
||||
@validator("recurrence", pre=True)
|
||||
def parse_recurrence(cls, v):
|
||||
if isinstance(v, str):
|
||||
return json.loads(v) if v else None
|
||||
return v
|
||||
|
||||
|
||||
class Task(BaseModel):
|
||||
id: str
|
||||
wallet: str
|
||||
pubkey: str
|
||||
d_tag: str
|
||||
title: str
|
||||
start_date: str
|
||||
end_date: str | None = None
|
||||
description: str = ""
|
||||
location: str | None = None
|
||||
status: str = "pending"
|
||||
event_type: str = "task"
|
||||
participants: list[Participant] = Field(default_factory=list)
|
||||
categories: list[str] = Field(default_factory=list)
|
||||
recurrence: Recurrence | None = None
|
||||
nostr_event_id: str | None = None
|
||||
nostr_event_created_at: int | None = None
|
||||
time: datetime
|
||||
|
||||
@validator("participants", pre=True)
|
||||
def parse_participants(cls, v):
|
||||
if isinstance(v, str):
|
||||
return json.loads(v) if v else []
|
||||
return v or []
|
||||
|
||||
@validator("categories", pre=True)
|
||||
def parse_categories(cls, v):
|
||||
if isinstance(v, str):
|
||||
return json.loads(v) if v else []
|
||||
return v or []
|
||||
|
||||
@validator("recurrence", pre=True)
|
||||
def parse_recurrence(cls, v):
|
||||
if isinstance(v, str):
|
||||
return json.loads(v) if v else None
|
||||
return v
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
"""NIP-52 replaceable-event address (31922:pubkey:d-tag)."""
|
||||
return f"31922:{self.pubkey}:{self.d_tag}"
|
||||
|
||||
|
||||
class PublicTask(BaseModel):
|
||||
"""Trimmed task payload for the public/anonymous endpoint."""
|
||||
|
||||
id: str
|
||||
pubkey: str
|
||||
d_tag: str
|
||||
title: str
|
||||
start_date: str
|
||||
end_date: str | None = None
|
||||
description: str = ""
|
||||
location: str | None = None
|
||||
status: str
|
||||
event_type: str
|
||||
participants: list[Participant] = Field(default_factory=list)
|
||||
categories: list[str] = Field(default_factory=list)
|
||||
recurrence: Recurrence | None = None
|
||||
|
||||
@validator("participants", pre=True)
|
||||
def parse_participants(cls, v):
|
||||
if isinstance(v, str):
|
||||
return json.loads(v) if v else []
|
||||
return v or []
|
||||
|
||||
@validator("categories", pre=True)
|
||||
def parse_categories(cls, v):
|
||||
if isinstance(v, str):
|
||||
return json.loads(v) if v else []
|
||||
return v or []
|
||||
|
||||
@validator("recurrence", pre=True)
|
||||
def parse_recurrence(cls, v):
|
||||
if isinstance(v, str):
|
||||
return json.loads(v) if v else None
|
||||
return v
|
||||
|
||||
|
||||
class CreateTaskCompletion(BaseModel):
|
||||
task_address: str # "31922:pubkey:d_tag"
|
||||
task_status: TaskStatus = "claimed"
|
||||
occurrence: str | None = None # YYYY-MM-DD for recurring tasks
|
||||
completed_at: int | None = None # unix ts; set when task_status == 'completed'
|
||||
notes: str = ""
|
||||
|
||||
|
||||
class TaskCompletion(BaseModel):
|
||||
id: str # Nostr event id (or local hash for not-yet-published)
|
||||
task_address: str
|
||||
pubkey: str # claimer
|
||||
occurrence: str | None = None
|
||||
task_status: TaskStatus = "claimed"
|
||||
completed_at: int | None = None
|
||||
notes: str = ""
|
||||
nostr_created_at: int
|
||||
time: datetime
|
||||
|
||||
|
||||
class TasksSettings(BaseModel):
|
||||
"""Extension-level settings singleton."""
|
||||
|
||||
public_listing: bool = False # expose /api/v1/tasks/public if enabled
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue