Downgrade Pro Data File to Base
This document describes how to convert a Pro data.json file back to the base verrsion so you can continue using the free version of TaskTrove. This procedure targets data files from version v0.12.0 and newer.
What changes during downgrade
The conversion removes Pro-only fields so the result conforms to the base packages/types data file schema.
Steps
- Back up your Pro data file.
- Copy and save the following script as
convert.py:
python
#!/usr/bin/env python3
import argparse
import json
import sys
from typing import Any, Dict, List
def pick_user(user_field: Any) -> Dict[str, Any]:
if isinstance(user_field, list):
admins = [u for u in user_field if isinstance(u, dict) and u.get("role") == "admin"]
if admins:
return admins[0]
return user_field[0] if user_field else {}
if isinstance(user_field, dict):
return user_field
return {}
def clean_user(user: Dict[str, Any]) -> Dict[str, Any]:
user = dict(user)
user.pop("role", None)
user.pop("preferences", None)
return user
def clean_task(task: Dict[str, Any]) -> Dict[str, Any]:
task = dict(task)
task.pop("ownerId", None)
task.pop("assignees", None)
task.pop("reward", None)
comments = task.get("comments")
if isinstance(comments, list):
cleaned_comments: List[Dict[str, Any]] = []
for comment in comments:
if isinstance(comment, dict):
comment = dict(comment)
comment.pop("reactions", None)
cleaned_comments.append(comment)
else:
cleaned_comments.append(comment)
task["comments"] = cleaned_comments
return task
def clean_project(project: Dict[str, Any]) -> Dict[str, Any]:
project = dict(project)
project.pop("members", None)
return project
def clean_settings(settings: Dict[str, Any]) -> Dict[str, Any]:
settings = dict(settings)
general = settings.get("general")
if isinstance(general, dict):
general = dict(general)
general.pop("newTaskOwnership", None)
settings["general"] = general
data = settings.get("data")
if isinstance(data, dict):
data = dict(data)
data.pop("calendarSync", None)
data.pop("calendarSyncSchedule", None)
settings["data"] = data
settings.pop("productivity", None)
return settings
def convert(data: Dict[str, Any]) -> Dict[str, Any]:
tasks = data.get("tasks", [])
projects = data.get("projects", [])
labels = data.get("labels")
project_groups = data.get("projectGroups")
label_groups = data.get("labelGroups")
settings = data.get("settings", {})
user = pick_user(data.get("user"))
return {
"tasks": [clean_task(t) if isinstance(t, dict) else t for t in tasks],
"projects": [clean_project(p) if isinstance(p, dict) else p for p in projects],
"labels": labels,
"projectGroups": project_groups,
"labelGroups": label_groups,
"settings": clean_settings(settings) if isinstance(settings, dict) else settings,
"user": clean_user(user) if isinstance(user, dict) else user,
"version": data.get("version"),
"edition": "base",
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Convert a Pro TaskTrove data.json file to the base schema. "
"Outputs to stdout unless --output is provided."
)
)
parser.add_argument("input", help="Path to pro data.json")
parser.add_argument("-o", "--output", help="Write output to file instead of stdout")
return parser.parse_args()
def main() -> int:
args = parse_args()
try:
with open(args.input, "r", encoding="utf-8") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Input file not found: {args.input}", file=sys.stderr)
return 1
except json.JSONDecodeError as exc:
print(f"Invalid JSON in {args.input}: {exc}", file=sys.stderr)
return 1
if not isinstance(data, dict):
print("Input JSON must be an object at the top level.", file=sys.stderr)
return 1
converted = convert(data)
output = json.dumps(converted, indent=2, ensure_ascii=True)
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(output)
f.write("\n")
else:
print(output)
return 0
if __name__ == "__main__":
raise SystemExit(main())- Run the converter (note, this will override your data.json file):
bash
convert.py path/to/data.json -o path/to/data.json- Edit your docker compose file or other configuration to use the base image.
- Restart the docker container