feat: Migration v0 to v1.

Move 'tasklist' table to 'taskslist' for compatibility.
Add migration sql script.
Add 'taskslist_info' view.
Add 'completed_in' to tasks.
This commit is contained in:
2025-10-02 20:32:00 +10:00
parent daf72be82e
commit 48eda11e1d
5 changed files with 151 additions and 14 deletions

12
data.py
View File

@@ -9,7 +9,7 @@ class Task(RowModel):
Schema
------
"""
_tablename_ = 'tasklist'
_tablename_ = 'taskslist'
_cache_ = {}
taskid = Integer(primary=True)
@@ -20,6 +20,7 @@ class Task(RowModel):
duration = Integer()
started_at = Timestamp()
completed_at = Timestamp()
completed_in = Integer()
_timestamp = Timestamp()
@@ -32,10 +33,12 @@ class TaskProfile(RowModel):
_cache_ = {}
profileid = Integer(primary=True)
show_tips = Bool()
show_encouragement = Bool()
class TaskInfo(RowModel):
_tablename_ = 'tasklist_info'
_tablename_ = 'taskslist_info'
taskid = Integer(primary=True)
profileid = Integer()
@@ -44,14 +47,17 @@ class TaskInfo(RowModel):
duration = Integer()
started_at = Timestamp()
completed_at = Timestamp()
completed_in = Integer()
last_started = Timestamp()
nowid = Integer()
order_idx = Integer()
is_running = Bool()
is_planned = Bool()
is_complete = Bool()
show_tips = Bool()
show_encouragement = Bool()
tasklabel = Integer()
@property

95
data/migrationv0-v1.sql Normal file
View File

@@ -0,0 +1,95 @@
BEGIN;
-- Tasklist data {{{
CREATE TABLE taskslist(
taskid INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
profileid INTEGER NOT NULL REFERENCES user_profiles(profileid) ON DELETE CASCADE ON UPDATE CASCADE,
content TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
duration INTEGER NOT NULL DEFAULT 0,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
completed_in INTEGER NOT NULL REFERENCES communities(communityid) ON DELETE SET NULL,
_timestamp TIMESTAMPTZ DEFAILT NOW()
);
CREATE TRIGGER tasklist_timestamp BEFORE UPDATE ON tasklist
FOR EACH ROW EXECUTE FUNCTION update_timestamp_column();
CREATE TABLE nowlist(
taskid INTEGER PRIMARY KEY REFERENCES taskslist(taskid) ON DELETE CASCADE ON UPDATE CASCADE,
last_started TIMESTAMPTZ NOT NULL
);
CREATE TABLE taskplan(
taskid INTEGER PRIMARY KEY REFERENCES taskslist(taskid) ON DELETE CASCADE ON UPDATE CASCADE,
order_idx INTEGER NOT NULL
);
CREATE TABLE task_profiles(
profileid INTEGER PRIMARY KEY REFERENCES user_profiles(profileid) ON DELETE CASCADE ON UPDATE CASCADE,
show_tips BOOLEAN,
show_encouragement BOOLEAN
);
CREATE VIEW
taskslist_info,
AS
SELECT
taskslist.taskid AS taskid,
taskslist.profileid AS profileid,
taskslist.content AS content,
taskslist.created_at AS created_at,
taskslist.duration AS duration,
taskslist.started_at AS started_at,
taskslist.completed_at AS completed_at,
taskslist.completed_in AS completed_in,
nowlist.last_started AS last_started,
taskplan.order_idx AS order_idx,
task_profiles.show_tips AS show_tips,
task_profiles.show_encouragement AS show_encouragement,
(taskslist.completed_at IS NOT NULL) AS is_complete,
(nowlist.taskid IS NOT NULL) AS is_running,
(taskplan.order_idx IS NOT NULL) AS is_planned,
(row_number() OVER (PARITION BY profileid ORDER BY taskid ASC)) AS tasklabel
FROM
taskslist
LEFT JOIN nowlist USING (taskid)
LEFT JOIN taskplan USING (taskid)
LEFT JOIN task_profiles USING (profileid)
WHERE deleted_at IS NULL
ORDER BY (profileid, taskid);
-- }}}
INSERT INTO taskslist (
profileid,
content,
started_at,
completed_at
) VALUES (
SELECT
userid,
task,
started_at,
done_at
FROM
nowlist_tasks
);
INSERT INTO nowlist (
taskid,
last_started
) VALUES (
SELECT
taskid,
CASE WHEN completed_at IS NOT NULL THEN NULL
ELSE started_at
END
FROM
taskslist
);
COMMIT;

View File

@@ -1,8 +1,9 @@
BEGIN;
-- Tasklist data {{{
INSERT INTO version_history (component, from_version, to_version, author) VALUES ('TASKLIST', 0, 1, 'Initial Creation');
CREATE TABLE tasklist(
CREATE TABLE taskslist(
taskid INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
profileid INTEGER NOT NULL REFERENCES user_profiles(profileid) ON DELETE CASCADE ON UPDATE CASCADE,
content TEXT NOT NULL,
@@ -11,6 +12,7 @@ CREATE TABLE tasklist(
duration INTEGER NOT NULL DEFAULT 0,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
completed_in INTEGER NOT NULL REFERENCES communities(communityid) ON DELETE SET NULL,
_timestamp TIMESTAMPTZ DEFAILT NOW()
);
CREATE TRIGGER tasklist_timestamp BEFORE UPDATE ON tasklist
@@ -18,18 +20,49 @@ CREATE TRIGGER tasklist_timestamp BEFORE UPDATE ON tasklist
CREATE TABLE nowlist(
taskid INTEGER PRIMARY KEY REFERENCES tasklist(taskid) ON DELETE CASCADE ON UPDATE CASCADE,
taskid INTEGER PRIMARY KEY REFERENCES taskslist(taskid) ON DELETE CASCADE ON UPDATE CASCADE,
last_started TIMESTAMPTZ NOT NULL
);
CREATE TABLE taskplan(
taskid INTEGER PRIMARY KEY REFERENCES tasklist(taskid) ON DELETE CASCADE ON UPDATE CASCADE,
taskid INTEGER PRIMARY KEY REFERENCES taskslist(taskid) ON DELETE CASCADE ON UPDATE CASCADE,
order_idx INTEGER NOT NULL
);
CREATE TABLE task_profiles(
profileid INTEGER PRIMARY KEY REFERENCES user_profiles(profileid) ON DELETE CASCADE ON UPDATE CASCADE
profileid INTEGER PRIMARY KEY REFERENCES user_profiles(profileid) ON DELETE CASCADE ON UPDATE CASCADE,
show_tips BOOLEAN,
show_encouragement BOOLEAN
);
CREATE VIEW
taskslist_info,
AS
SELECT
taskslist.taskid AS taskid,
taskslist.profileid AS profileid,
taskslist.content AS content,
taskslist.created_at AS created_at,
taskslist.duration AS duration,
taskslist.started_at AS started_at,
taskslist.completed_at AS completed_at,
taskslist.completed_in AS completed_in,
nowlist.last_started AS last_started,
taskplan.order_idx AS order_idx,
task_profiles.show_tips AS show_tips,
task_profiles.show_encouragement AS show_encouragement,
(taskslist.completed_at IS NOT NULL) AS is_complete,
(nowlist.taskid IS NOT NULL) AS is_running,
(taskplan.order_idx IS NOT NULL) AS is_planned,
(row_number() OVER (PARITION BY profileid ORDER BY taskid ASC)) AS tasklabel
FROM
taskslist
LEFT JOIN nowlist USING (taskid)
LEFT JOIN taskplan USING (taskid)
LEFT JOIN task_profiles USING (profileid)
WHERE deleted_at IS NULL
ORDER BY (profileid, taskid);
-- }}}
COMMIT;

View File

@@ -127,24 +127,28 @@ class Tasklist:
async def unset_now(self):
# Unset the current task and update the duration correctly
# Does not put the current task on the plan
# TODO: Transaction
current = self.get_current()
if current is not None:
now = utc_now()
assert current.is_running or (current.last_started is None)
if current.is_running:
if current.last_started is not None:
duration = (now - current.last_started).total_seconds()
duration += current.duration
await self.data.tasklist.update_where(taskid=current.taskid).set(duration=duration)
await self.data.nowlist.delete_where(taskid=current.taskid)
await self.on_update()
async def complete_tasks(self, *taskids) -> list[TaskInfo]:
async def complete_tasks(self, *taskids, communityid: int|None = None) -> list[TaskInfo]:
# Remove any tasks which are already complete
# TODO: Transaction
# TODO: Uncomplete tasks
taskids = [id for id in taskids if not self.id_tasks[id].is_complete]
if taskids:
now = utc_now()
await self.data.tasklist.update_where(taskid=taskids).set(completed_at=now)
await self.data.tasklist.update_where(taskid=taskids).set(
completed_at=now,
completed_in=communityid
)
if self.current in taskids:
current = self.get_current()
assert current is not None

View File

@@ -58,8 +58,8 @@ class TaskComponent(cmds.Component):
await self.tasker.setup()
# TODO: Add the IPC task update callback
async def get_profile_for(self, user):
...
async def get_profile_for(self, user: twitchio.PartialUser):
return await self.bot.profiles.fetch_profile(user)
@cmds.command(name='now', aliases=['task', 'check', 'add'])
async def cmd_now(self, ctx: cmds.Context, *, args: Optional[str]):
@@ -72,7 +72,6 @@ class TaskComponent(cmds.Component):
# TODO: Breaking change: check and add should change behaviour.
# Check shows the status of the given task (does not allow creation)
# Add adds the task(s) to the end of the plan.
# TODOTODO: Support newline breaking on multi-tasks!! Although prefer semicolons
profileid = (await self.get_profile_for(ctx.author)).profileid