diff --git a/data.py b/data.py index d30aba5..a77efb8 100644 --- a/data.py +++ b/data.py @@ -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 diff --git a/data/migrationv0-v1.sql b/data/migrationv0-v1.sql new file mode 100644 index 0000000..934b334 --- /dev/null +++ b/data/migrationv0-v1.sql @@ -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; diff --git a/data/schema.sql b/data/schema.sql index 30afb80..7232d63 100644 --- a/data/schema.sql +++ b/data/schema.sql @@ -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; diff --git a/tasklist.py b/tasklist.py index e0faedc..c5a90d0 100644 --- a/tasklist.py +++ b/tasklist.py @@ -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 diff --git a/twitch/component.py b/twitch/component.py index a462cd8..60e9a26 100644 --- a/twitch/component.py +++ b/twitch/component.py @@ -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