Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cb003f1
v4
VincentGuyader Feb 8, 2026
5cde4af
temp
VincentGuyader Feb 8, 2026
d07b447
v0.4.0: Improve test coverage, fix bugs, and update CI
VincentGuyader Feb 8, 2026
c6c9737
Update .Rbuildignore
VincentGuyader Feb 9, 2026
823d955
Fix PR review feedback: security, maintainability and bug fixes
VincentGuyader Feb 9, 2026
d73171a
Fix PR review feedback: security, maintainability and bug fixes
VincentGuyader Feb 9, 2026
859e1ff
Apply consistent patterns across codebase
VincentGuyader Feb 9, 2026
040e023
Add JSON escaping to Sync API command builders (#15)
Copilot Feb 9, 2026
830ea10
Fix critical API bugs and JSON injection vulnerabilities from PR revi…
Copilot Feb 9, 2026
bf41593
Fix JSON escaping vulnerabilities in Sync API commands
VincentGuyader Feb 9, 2026
d8fda07
Initial plan (#16)
Copilot Feb 9, 2026
d3d221c
Initial plan (#17)
Copilot Feb 9, 2026
88c871a
Initial plan (#18)
Copilot Feb 9, 2026
86cdec7
Initial plan (#19)
Copilot Feb 9, 2026
9ff355d
Add escape_json() to all Sync API commands with ID parameters (#20)
Copilot Feb 10, 2026
8d5e589
Fix move_task() validation, test dataframe schemas, and URL query enc…
Copilot Feb 13, 2026
82ee5d9
update URL
VincentGuyader Feb 13, 2026
d463335
cleaning
VincentGuyader Feb 13, 2026
3f9e9bb
Fix CRAN compliance issues before submission
VincentGuyader Feb 13, 2026
def1a34
Improve security and robustness (Priority 2 fixes)
VincentGuyader Feb 13, 2026
21112b8
Update NEWS.md with comprehensive changelog for v0.4.0
VincentGuyader Feb 13, 2026
b60dd33
Remove lubridate dependency from vignettes
VincentGuyader Feb 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@
^dev$
^\.RData$
^\.Rhistory$
^CLAUDE\.md$
^PLAN_IMPLEMENTATION\.md$
^REVIEW\.md$
^\.claude$
84 changes: 24 additions & 60 deletions .github/workflows/R-CMD-check.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
# For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag.
# https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions
# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
on:
push:
branches:
- main
- master
- dev
branches: [main, master, dev]
pull_request:
branches:
- main
- master
- dev

branches: [main, master, dev]

name: R-CMD-check

permissions: read-all

jobs:
R-CMD-check:
runs-on: ${{ matrix.config.os }}
Expand All @@ -24,65 +20,33 @@ jobs:
fail-fast: false
matrix:
config:
- {os: macos-latest, r: 'release'}
- {os: windows-latest, r: 'release'}
- {os: macOS-latest, r: 'release'}
- {os: ubuntu-20.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"}
- {os: ubuntu-20.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"}
- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
- {os: ubuntu-latest, r: 'release'}
- {os: ubuntu-latest, r: 'oldrel-1'}

env:
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
RSPM: ${{ matrix.config.rspm }}
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
R_KEEP_PKG_SOURCE: yes

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- uses: r-lib/actions/setup-r@v1
- uses: r-lib/actions/setup-pandoc@v2

- uses: r-lib/actions/setup-r@v2
with:
r-version: ${{ matrix.config.r }}
http-user-agent: ${{ matrix.config.http-user-agent }}
use-public-rspm: true

- uses: r-lib/actions/setup-pandoc@v1

- name: Query dependencies
run: |
install.packages('remotes')
saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2)
writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version")
shell: Rscript {0}

- name: Restore R package cache
if: runner.os != 'Windows'
uses: actions/cache@v2
- uses: r-lib/actions/setup-r-dependencies@v2
with:
path: ${{ env.R_LIBS_USER }}
key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }}
restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-

- name: Install system dependencies
if: runner.os == 'Linux'
run: |
while read -r cmd
do
eval sudo $cmd
done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))')

- name: Install dependencies
run: |
remotes::install_deps(dependencies = TRUE)
remotes::install_cran("rcmdcheck")
shell: Rscript {0}

- name: Check
env:
_R_CHECK_CRAN_INCOMING_REMOTE_: false
run: |
options(crayon.enabled = TRUE)
rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check")
shell: Rscript {0}
extra-packages: any::rcmdcheck
needs: check

- name: Upload check results
if: failure()
uses: actions/upload-artifact@main
- uses: r-lib/actions/check-r-package@v2
with:
name: ${{ runner.os }}-r${{ matrix.config.r }}-results
path: check
upload-snapshots: true
build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ test.R
docs/
*.xlsx
.claude/
check/
105 changes: 105 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Package Overview

rtodoist is an R package that wraps the Todoist API v1 for programmatic management of projects, tasks, sections, and collaborators. The package uses secure token storage via keyring.

## Common Commands

```r
# Install dependencies
remotes::install_deps(dependencies = TRUE)

# Generate documentation (NAMESPACE and man/*.Rd files)
devtools::document()

# Run all tests
devtools::test()

# Run a single test file
testthat::test_file("tests/testthat/test-tasks.R")

# Run R CMD CHECK
rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning")

# Build package
R CMD build .
```

## Architecture

### Two API Communication Patterns

1. **Sync API** (`call_api()` in `R/utils.R`): Used for write operations via `/api/v1/sync`. Commands are sent as JSON with UUID-based temp_id generation.

2. **REST API** (`call_api_rest()` in `R/utils.R`): Used for read operations with cursor-based pagination support.

### Key Design Patterns

- **Lazy parameter evaluation**: Functions accept both `project_name` or `project_id`. Default parameters call lookup functions (e.g., `get_project_id()`), so use `force()` before API calls.

- **Pipe-friendly returns**: Write functions return IDs invisibly for chaining:
```r
add_project("test") %>% add_tasks_in_project(c("task1", "task2"))
```

- **Vector parameter matching**: `add_tasks_in_project()` accepts vectors for tasks, responsible users, due dates, and sections. Single values are recycled; otherwise lengths must match.

### Source File Organization

| File | Purpose |
|------|---------|
| `R/projects.R` | Project CRUD operations |
| `R/tasks.R` | Task operations (add, update, get, assign) |
| `R/section.R` | Section management |
| `R/users.R` | User/collaboration management |
| `R/token.R` | API token management via keyring |
| `R/utils.R` | `call_api()`, `call_api_rest()`, `escape_json()`, `random_key()` |
| `R/all_objects.R` | `get_all_data()` for full sync |
| `R/clean_tools.R` | Data cleaning utilities |

### Utility Functions to Reuse

- `call_api(commands, token)`: Sync API calls with JSON command batch
- `call_api_rest(endpoint, params, token)`: REST API with pagination
- `escape_json(x)`: Escape special characters for JSON strings
- `random_key()`: Generate UUIDs for Sync API commands
- `clean_due()`, `clean_section()`: Normalize NULL/"" to "null" for API

## Testing

Uses testthat v3 with two test strategies:

1. **Unit tests** (`test-unit-logic.R`): Tests pure logic functions using fixture data from `tests/testthat/fixtures/`. No API token required.

2. **Integration tests**: Require a valid API token. Skip automatically on CI/CRAN via helpers in `tests/testthat/helper.R`.

### Test Helpers

```r
skip_if_no_token() # Skip if no API token available
skip_on_ci_or_cran() # Skip on CI environments
```

## API Token Setup

For testing, get your token from https://www.todoist.com/prefs/integrations/developer then:
```r
rtodoist::set_todoist_api_token("your_token")
```

## Adding New API Functions

Follow the existing pattern:
```r
add_xxx <- function(name, ..., verbose = TRUE, token = get_todoist_api_token()) {
force(token)
# Build JSON command using glue() and escape_json()
# Call via call_api() or call_api_rest()
# Return ID invisibly for pipe chaining
}
```

See `PLAN_IMPLEMENTATION.md` for the roadmap of missing features (labels, comments, filters, reminders, etc.).
14 changes: 6 additions & 8 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
Package: rtodoist
Title: Create and Manage Todolist using 'Todoist.com' API
Version: 0.3
Version: 0.4.0
Authors@R: c(
person("Cervan", "Girard", , "cervan@thinkr.fr", role = "aut",
comment = c(ORCID = "0000-0002-4816-4624")),
comment = c(ORCID = "0000-0002-4816-4624", note = "previous maintainer")),
person("Vincent", "Guyader", , "vincent@thinkr.fr", role = c("cre", "aut"),
comment = c(ORCID = "0000-0003-0671-9270")),
person("ThinkR", role = c("cph", "fnd"))
)
Description: Allows you to interact with the API of the "Todoist"
platform. 'Todoist' <https://todoist.com/> provides an online task
platform. 'Todoist' <https://www.todoist.com/> provides an online task
manager service for teams.
License: MIT + file LICENSE
URL: https://github.com/ThinkR-open/rtodoist
BugReports: https://github.com/ThinkR-open/rtodoist/issues
Depends:
R (>= 3.5.0)
Imports:
Imports:
curl,
digest,
dplyr,
getPass,
glue,
httr,
httr2,
keyring,
magrittr,
Expand All @@ -31,11 +31,9 @@ Imports:
stringr,
utils
Suggests:
httptest2,
covr,
jsonlite,
knitr,
lubridate,
mockery,
rmarkdown,
testthat (>= 3.0.0)
Config/testthat/edition: 3
Expand Down
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
YEAR: 2019
COPYRIGHT HOLDER: Cervan Girard
YEAR: 2019-2026
COPYRIGHT HOLDER: ThinkR
73 changes: 71 additions & 2 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,27 +1,91 @@
# Generated by roxygen2: do not edit by hand

export("%>%")
export(accept_invitation)
export(add_comment)
export(add_filter)
export(add_label)
export(add_project)
export(add_reminder)
export(add_responsible_to_task)
export(add_section)
export(add_tasks_in_project)
export(add_tasks_in_project_from_df)
export(add_user_in_project)
export(add_users_in_project)
export(archive_project)
export(archive_section)
export(ask_todoist_api_token)
export(close_task)
export(delete_collaborator)
export(delete_comment)
export(delete_filter)
export(delete_invitation)
export(delete_label)
export(delete_project)
export(delete_reminder)
export(delete_section)
export(delete_task)
export(delete_todoist_api_token)
export(delete_upload)
export(download_backup)
export(export_template)
export(get_activity_logs)
export(get_all_data)
export(get_all_filters)
export(get_all_labels)
export(get_all_projects)
export(get_all_reminders)
export(get_all_sections)
export(get_all_users)
export(get_all_workspaces)
export(get_archived_projects)
export(get_backups)
export(get_comment)
export(get_comments)
export(get_completed_tasks)
export(get_filter)
export(get_filter_id)
export(get_label)
export(get_label_id)
export(get_productivity_stats)
export(get_project)
export(get_project_id)
export(get_section)
export(get_section_id)
export(get_shared_labels)
export(get_task)
export(get_tasks)
export(get_tasks_by_filter)
export(get_tasks_of_project)
export(get_todoist_api_token)
export(get_user_info)
export(get_users_in_project)
export(get_workspace_users)
export(import_template)
export(invite_to_workspace)
export(leave_workspace)
export(move_section)
export(move_task)
export(open_todoist_website_profile)
export(quick_add_task)
export(reject_invitation)
export(remove_shared_label)
export(rename_shared_label)
export(reopen_task)
export(set_todoist_api_token)
export(unarchive_project)
export(unarchive_section)
export(update_comment)
export(update_filter)
export(update_label)
export(update_project)
export(update_reminder)
export(update_section)
export(update_task)
export(update_todoist_api_token)
export(update_workspace)
export(upload_file)
import(glue)
import(keyring)
import(purrr)
Expand All @@ -37,17 +101,22 @@ importFrom(dplyr,pull)
importFrom(getPass,getPass)
importFrom(glue,glue)
importFrom(glue,glue_collapse)
importFrom(httr,POST)
importFrom(httr,content)
importFrom(httr2,req_body_json)
importFrom(httr2,req_body_multipart)
importFrom(httr2,req_error)
importFrom(httr2,req_headers)
importFrom(httr2,req_method)
importFrom(httr2,req_perform)
importFrom(httr2,req_url_query)
importFrom(httr2,request)
importFrom(httr2,resp_body_json)
importFrom(httr2,resp_body_raw)
importFrom(httr2,resp_body_string)
importFrom(httr2,resp_status)
importFrom(magrittr,"%>%")
importFrom(purrr,flatten)
importFrom(purrr,is_empty)
importFrom(purrr,keep)
importFrom(purrr,map)
importFrom(purrr,map_df)
importFrom(purrr,map_dfr)
Expand Down
Loading