Release — F-Droid repo + Gitea/Codeberg release + Play / detect (push) Successful in 7s
Release — F-Droid repo + Gitea/Codeberg release + Play / release (push) Skipped
Release — F-Droid repo + Gitea/Codeberg release + Play / play (push) Skipped
Renovate / renovate (push) Successful in 1m1s
Reminders set in Agendula never reached the server, so a Mac or any other client saw none, and alarms set there never rang here. Reminders now sync as VALARMs in both directions. **What syncs** - Alarms Agendula can fire exactly as written become reminders: DISPLAY/AUDIO, relative to start or due, or absolute on a non-recurring task. - Everything else stays in the residue and round-trips untouched: Apple default alarms, location alarms, repeating alarms, RFC 9074 snoozes, and triggers after their anchor. - An unchanged alarm is re-emitted verbatim. An edited one is authored fresh under its existing uid. All-day leads go out as whole days. - `ACKNOWLEDGED` is only added once a reminder is past the planner's missed-reminder grace period. **Behaviour changes** - Default reminders are stored on the task when it first gets a due date, so other clients see them. While untouched, they swap with the other default when the task flips between timed and all-day. - Removing a task's alarms in another app turns them off here instead of falling back to the default. Tasks from clients that never write alarms keep the default. - "All occurrences" reminder edits now reach occurrences that still had the series' old reminders. - `.ics` import keeps VALARMs as reminders. **Database v3** - Alarms gain a `uid` and `raw` VALARM. Tasks gain `reminders_off`. - The migration lifts claimable alarms out of the residue. It queues a one-time upload for tasks with local-only reminders on writable, non-confidential lists. **Testing** - Unit tests: full 662, offline 489. All instrumented tests pass on the emulator, including the migration. Lint is clean for both flavours. - Not covered: the repository has no unit-test harness, so storing defaults at creation and the timed/all-day swap have no automated tests. Nothing has been checked against a real Mac yet. Closes #52 Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de> Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/81
548 lines
17 KiB
JSON
548 lines
17 KiB
JSON
{
|
|
"formatVersion": 1,
|
|
"database": {
|
|
"version": 3,
|
|
"identityHash": "725292b38bce25ce9032c4650573ae85",
|
|
"entities": [
|
|
{
|
|
"tableName": "accounts",
|
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `display_name` TEXT NOT NULL, `principal_url` TEXT, `home_set_url` TEXT, `username` TEXT, `last_sync_at` INTEGER, `last_sync_error` TEXT)",
|
|
"fields": [
|
|
{
|
|
"fieldPath": "id",
|
|
"columnName": "id",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "displayName",
|
|
"columnName": "display_name",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "principalUrl",
|
|
"columnName": "principal_url",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "homeSetUrl",
|
|
"columnName": "home_set_url",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "username",
|
|
"columnName": "username",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "lastSyncAt",
|
|
"columnName": "last_sync_at",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "lastSyncError",
|
|
"columnName": "last_sync_error",
|
|
"affinity": "TEXT"
|
|
}
|
|
],
|
|
"primaryKey": {
|
|
"autoGenerate": true,
|
|
"columnNames": [
|
|
"id"
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"tableName": "task_lists",
|
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `color` INTEGER NOT NULL, `account_id` INTEGER, `is_visible` INTEGER NOT NULL DEFAULT 1, `is_synced` INTEGER NOT NULL DEFAULT 1, `owner` TEXT, `is_read_only` INTEGER NOT NULL DEFAULT 0, `sort_order` INTEGER NOT NULL DEFAULT 0, `href` TEXT, `ctag` TEXT, `sync_token` TEXT, `is_dirty` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`account_id`) REFERENCES `accounts`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
|
|
"fields": [
|
|
{
|
|
"fieldPath": "id",
|
|
"columnName": "id",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "name",
|
|
"columnName": "name",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "color",
|
|
"columnName": "color",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "accountId",
|
|
"columnName": "account_id",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "isVisible",
|
|
"columnName": "is_visible",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "1"
|
|
},
|
|
{
|
|
"fieldPath": "isSynced",
|
|
"columnName": "is_synced",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "1"
|
|
},
|
|
{
|
|
"fieldPath": "owner",
|
|
"columnName": "owner",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "isReadOnly",
|
|
"columnName": "is_read_only",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "0"
|
|
},
|
|
{
|
|
"fieldPath": "sortOrder",
|
|
"columnName": "sort_order",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "0"
|
|
},
|
|
{
|
|
"fieldPath": "href",
|
|
"columnName": "href",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "ctag",
|
|
"columnName": "ctag",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "syncToken",
|
|
"columnName": "sync_token",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "isDirty",
|
|
"columnName": "is_dirty",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "0"
|
|
}
|
|
],
|
|
"primaryKey": {
|
|
"autoGenerate": true,
|
|
"columnNames": [
|
|
"id"
|
|
]
|
|
},
|
|
"indices": [
|
|
{
|
|
"name": "index_task_lists_account_id",
|
|
"unique": false,
|
|
"columnNames": [
|
|
"account_id"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_task_lists_account_id` ON `${TABLE_NAME}` (`account_id`)"
|
|
}
|
|
],
|
|
"foreignKeys": [
|
|
{
|
|
"table": "accounts",
|
|
"onDelete": "SET NULL",
|
|
"onUpdate": "NO ACTION",
|
|
"columns": [
|
|
"account_id"
|
|
],
|
|
"referencedColumns": [
|
|
"id"
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"tableName": "tasks",
|
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `list_id` INTEGER NOT NULL, `uid` TEXT NOT NULL, `href` TEXT, `etag` TEXT, `title` TEXT, `description` TEXT, `location` TEXT, `url` TEXT, `color` INTEGER, `categories` TEXT NOT NULL DEFAULT '', `status` INTEGER NOT NULL DEFAULT 0, `percent_complete` INTEGER, `completed_at` INTEGER, `priority` INTEGER NOT NULL DEFAULT 0, `classification` INTEGER, `dtstart` INTEGER, `due` INTEGER, `duration` TEXT, `is_all_day` INTEGER NOT NULL DEFAULT 0, `timezone` TEXT, `rrule` TEXT, `rdate` TEXT, `exdate` TEXT, `recurrence_id` INTEGER, `master_id` INTEGER, `parent_id` INTEGER, `sort_order` INTEGER NOT NULL DEFAULT 0, `created_at` INTEGER, `last_modified` INTEGER, `sequence` INTEGER NOT NULL DEFAULT 0, `is_dirty` INTEGER NOT NULL DEFAULT 0, `is_deleted` INTEGER NOT NULL DEFAULT 0, `unknown_properties` TEXT, `reminders_off` INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(`list_id`) REFERENCES `task_lists`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`master_id`) REFERENCES `tasks`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`parent_id`) REFERENCES `tasks`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL )",
|
|
"fields": [
|
|
{
|
|
"fieldPath": "id",
|
|
"columnName": "id",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "listId",
|
|
"columnName": "list_id",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "uid",
|
|
"columnName": "uid",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "href",
|
|
"columnName": "href",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "etag",
|
|
"columnName": "etag",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "title",
|
|
"columnName": "title",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "description",
|
|
"columnName": "description",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "location",
|
|
"columnName": "location",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "url",
|
|
"columnName": "url",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "color",
|
|
"columnName": "color",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "categories",
|
|
"columnName": "categories",
|
|
"affinity": "TEXT",
|
|
"notNull": true,
|
|
"defaultValue": "''"
|
|
},
|
|
{
|
|
"fieldPath": "status",
|
|
"columnName": "status",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "0"
|
|
},
|
|
{
|
|
"fieldPath": "percentComplete",
|
|
"columnName": "percent_complete",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "completedAt",
|
|
"columnName": "completed_at",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "priority",
|
|
"columnName": "priority",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "0"
|
|
},
|
|
{
|
|
"fieldPath": "classification",
|
|
"columnName": "classification",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "dtstart",
|
|
"columnName": "dtstart",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "due",
|
|
"columnName": "due",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "duration",
|
|
"columnName": "duration",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "isAllDay",
|
|
"columnName": "is_all_day",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "0"
|
|
},
|
|
{
|
|
"fieldPath": "timezone",
|
|
"columnName": "timezone",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "rrule",
|
|
"columnName": "rrule",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "rdate",
|
|
"columnName": "rdate",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "exdate",
|
|
"columnName": "exdate",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "recurrenceId",
|
|
"columnName": "recurrence_id",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "masterId",
|
|
"columnName": "master_id",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "parentId",
|
|
"columnName": "parent_id",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "sortOrder",
|
|
"columnName": "sort_order",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "0"
|
|
},
|
|
{
|
|
"fieldPath": "createdAt",
|
|
"columnName": "created_at",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "lastModified",
|
|
"columnName": "last_modified",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "sequence",
|
|
"columnName": "sequence",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "0"
|
|
},
|
|
{
|
|
"fieldPath": "isDirty",
|
|
"columnName": "is_dirty",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "0"
|
|
},
|
|
{
|
|
"fieldPath": "isDeleted",
|
|
"columnName": "is_deleted",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "0"
|
|
},
|
|
{
|
|
"fieldPath": "unknownProperties",
|
|
"columnName": "unknown_properties",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "remindersOff",
|
|
"columnName": "reminders_off",
|
|
"affinity": "INTEGER",
|
|
"notNull": true,
|
|
"defaultValue": "0"
|
|
}
|
|
],
|
|
"primaryKey": {
|
|
"autoGenerate": true,
|
|
"columnNames": [
|
|
"id"
|
|
]
|
|
},
|
|
"indices": [
|
|
{
|
|
"name": "index_tasks_list_id_is_deleted",
|
|
"unique": false,
|
|
"columnNames": [
|
|
"list_id",
|
|
"is_deleted"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_tasks_list_id_is_deleted` ON `${TABLE_NAME}` (`list_id`, `is_deleted`)"
|
|
},
|
|
{
|
|
"name": "index_tasks_parent_id",
|
|
"unique": false,
|
|
"columnNames": [
|
|
"parent_id"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_tasks_parent_id` ON `${TABLE_NAME}` (`parent_id`)"
|
|
},
|
|
{
|
|
"name": "index_tasks_master_id_recurrence_id",
|
|
"unique": false,
|
|
"columnNames": [
|
|
"master_id",
|
|
"recurrence_id"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_tasks_master_id_recurrence_id` ON `${TABLE_NAME}` (`master_id`, `recurrence_id`)"
|
|
},
|
|
{
|
|
"name": "index_tasks_is_dirty",
|
|
"unique": false,
|
|
"columnNames": [
|
|
"is_dirty"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_tasks_is_dirty` ON `${TABLE_NAME}` (`is_dirty`)"
|
|
},
|
|
{
|
|
"name": "index_tasks_list_id_uid_recurrence_id",
|
|
"unique": true,
|
|
"columnNames": [
|
|
"list_id",
|
|
"uid",
|
|
"recurrence_id"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tasks_list_id_uid_recurrence_id` ON `${TABLE_NAME}` (`list_id`, `uid`, `recurrence_id`)"
|
|
}
|
|
],
|
|
"foreignKeys": [
|
|
{
|
|
"table": "task_lists",
|
|
"onDelete": "CASCADE",
|
|
"onUpdate": "NO ACTION",
|
|
"columns": [
|
|
"list_id"
|
|
],
|
|
"referencedColumns": [
|
|
"id"
|
|
]
|
|
},
|
|
{
|
|
"table": "tasks",
|
|
"onDelete": "CASCADE",
|
|
"onUpdate": "NO ACTION",
|
|
"columns": [
|
|
"master_id"
|
|
],
|
|
"referencedColumns": [
|
|
"id"
|
|
]
|
|
},
|
|
{
|
|
"table": "tasks",
|
|
"onDelete": "SET NULL",
|
|
"onUpdate": "NO ACTION",
|
|
"columns": [
|
|
"parent_id"
|
|
],
|
|
"referencedColumns": [
|
|
"id"
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"tableName": "task_alarms",
|
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task_id` INTEGER NOT NULL, `minutes_before` INTEGER NOT NULL, `reference` TEXT NOT NULL DEFAULT 'DUE', `message` TEXT, `uid` TEXT NOT NULL DEFAULT '', `raw` TEXT, FOREIGN KEY(`task_id`) REFERENCES `tasks`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
|
"fields": [
|
|
{
|
|
"fieldPath": "id",
|
|
"columnName": "id",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "taskId",
|
|
"columnName": "task_id",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "minutesBefore",
|
|
"columnName": "minutes_before",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "reference",
|
|
"columnName": "reference",
|
|
"affinity": "TEXT",
|
|
"notNull": true,
|
|
"defaultValue": "'DUE'"
|
|
},
|
|
{
|
|
"fieldPath": "message",
|
|
"columnName": "message",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "uid",
|
|
"columnName": "uid",
|
|
"affinity": "TEXT",
|
|
"notNull": true,
|
|
"defaultValue": "''"
|
|
},
|
|
{
|
|
"fieldPath": "raw",
|
|
"columnName": "raw",
|
|
"affinity": "TEXT"
|
|
}
|
|
],
|
|
"primaryKey": {
|
|
"autoGenerate": true,
|
|
"columnNames": [
|
|
"id"
|
|
]
|
|
},
|
|
"indices": [
|
|
{
|
|
"name": "index_task_alarms_task_id",
|
|
"unique": false,
|
|
"columnNames": [
|
|
"task_id"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_task_alarms_task_id` ON `${TABLE_NAME}` (`task_id`)"
|
|
}
|
|
],
|
|
"foreignKeys": [
|
|
{
|
|
"table": "tasks",
|
|
"onDelete": "CASCADE",
|
|
"onUpdate": "NO ACTION",
|
|
"columns": [
|
|
"task_id"
|
|
],
|
|
"referencedColumns": [
|
|
"id"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"setupQueries": [
|
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '725292b38bce25ce9032c4650573ae85')"
|
|
]
|
|
}
|
|
} |