Skip to contents

Overview

artcdn is the centralized storage layer for all DigitalOcean Spaces operations across the Artalytics platform and agent infrastructure. It replaces the CDN functions previously embedded in artcore with a registry-driven design: buckets are declared in a single YAML file, the S3 client is cached per session, and all operations are generic (no domain assumptions baked in).

Adding a new bucket is a one-line change to inst/buckets.yml.


Where This Package Fits (Artalytics Ecosystem)

Artalytics follows a layered architecture with strict dependency boundaries:

+---------------------------------------------------------+
|                     appPlatform                         |
|              (Main Shiny Application)                   |
+---------------------------------------------------------+
|   modArtist  |  modBrowse  |  modUpload  |  modGallery  |
|                    (Shiny Modules)                      |
+---------------------------------------------------------+
|  artopenai  |  artgemini  | artpixeltrace | artcurator  |
|               (API & Domain Packages)                   |
+---------------------------------------------------------+
|                       artutils                          |
|           (High-level utilities & data access)          |
+---------------------------------------------------------+
|               artcore    |    artcdn                    |
|      (DB, UUID, HTTP)    | (CDN/S3 storage)            |
+---------------------------------------------------------+

Dependency rules (internal Artalytics packages):

  • artcore: must not depend on any other Artalytics package
  • artcdn: must not depend on any other Artalytics package
  • artutils: may depend on artcore and/or artcdn
  • API/domain packages (artopenai, artgemini, etc.): may depend on artcore, artcdn, and/or artutils
  • Shiny module packages (mod*): may depend on artutils (and thus artcore/artcdn), but never on other modules
  • appPlatform: may depend on modules + utilities as needed

If your package violates these boundaries, it will create circular dependencies and break deploy/install flows.


Installation

Install from GitHub (internal Artalytics packages)

Use pak for fast, reliable installs:

install.packages("pak")
pak::pkg_install("artalytics/artcdn")

If your package depends on private repos, you will need a GitHub PAT available as GITHUB_PAT.

Install dependencies (development)

install.packages("pak")
pak::pkg_install(c("devtools", "roxygen2", "testthat", "lintr", "covr"))

Usage

# Registry: see what buckets are configured
artcdn::cdn_buckets()
#> [1] "art-public" "art-coa" "art-data" "art-vault" "art-corpus"

artcdn::cdn_bucket_info("art-corpus")
#> $visibility
#> [1] "private"
#> $description
#> [1] "Agent-optimized codebase documentation and source files"

# Upload a file
artcdn::cdn_upload("art-corpus", "source/artcore/R/database.R", "/tmp/database.R")

# Upload a directory
artcdn::cdn_upload_dir("art-data", "processed/uuid-a/uuid-b/", "/tmp/artwork_files/")

# Check existence
artcdn::cdn_has_object("art-public", "thumbnails/artist/artwork.jpeg")
artcdn::cdn_has_prefix("art-data", "processed/uuid-a/uuid-b")

# List and count
artcdn::cdn_list_keys("art-vault", "uploads/uuid-a/uuid-b")
artcdn::cdn_count_keys("art-public", "thumbnails/uuid-a")

# Get a URL (auto-presigns private buckets)
artcdn::cdn_url("art-public", "thumbnails/artist/artwork.jpeg")
artcdn::cdn_url("art-data", "processed/uuid-a/uuid-b/img.png")

# Delete
artcdn::cdn_delete("art-data", "processed/uuid-a/uuid-b/file.txt")
artcdn::cdn_delete_prefix("art-data", "processed/uuid-a/uuid-b", recursive = TRUE)

# Platform write helpers (UUID-validated, canonical key paths)
artcdn::write_art_vault(artist, artwork, "path/to/bundle/")
artcdn::write_art_data(artist, artwork, "path/to/processed/")
artcdn::write_art_public(artist, artwork, "path/to/thumb.jpeg")
artcdn::write_art_coa(artist, artwork, "path/to/certificate.pdf")

# Soft-delete artwork across all 4 platform buckets
artcdn::cdn_soft_delete(artist, artwork, dry_run = TRUE)

API Reference

Full function documentation: docs.artalytics.dev/r/artcdn/reference


Bucket Registry

All buckets are declared in inst/buckets.yml:

endpoint: "https://sfo3.digitaloceanspaces.com"
region: "sfo3"

buckets:
  art-public:
    visibility: public
    description: Public thumbnails and static site assets

  art-coa:
    visibility: private
    description: Certificates of authenticity

  art-data:
    visibility: private
    description: Processed artwork data and analysis outputs

  art-vault:
    visibility: private
    description: Original artist uploads and source bundles

  art-corpus:
    visibility: private
    description: Agent-optimized codebase documentation and source files

To add a new bucket, add an entry to this file. Every function in the package validates against this registry.


Configuration (Environment Variables)

# Required for all CDN operations. Put in ~/.Renviron (never commit).
ART_BUCKETS_KEY_ID="..."
ART_BUCKETS_KEY_SECRET="..."

Documentation

  • Roxygen2 for exported functions and user-facing objects
  • Optional vignettes for tutorials / deep guides

Local commands:

devtools::document()
devtools::build_vignettes()

If this package has a website, configure pkgdown in _pkgdown.yml and build locally:

pkgdown::build_site()

Package Layout (Standard)

Artalytics packages follow the standard R package structure:

  • R/ – R source code (functions, module definitions, internal helpers)
  • man/ – generated .Rd docs (via roxygen2)
  • tests/testthat/ – unit tests (testthat, edition 3)
  • inst/ – installed assets (data, templates, JS/CSS, etc.)
  • vignettes/ – optional long-form docs (Rmd/Quarto)
  • .github/workflows/ – CI: R CMD check, lint, coverage, etc.

Keep “source of truth” logic in R/. Anything in man/ should be treated as generated output.


Coding Standards (Artalytics Defaults)

These are the platform expectations for all packages:

  • data.table-first: use data.table for tabular data (avoid tibbles/dplyr).
  • Native pipe: use base R |> (avoid magrittr %>%).
  • String ops: prefer stringr::str_* functions over base gsub/grepl/... (except paste0() for concatenation).
  • Explicit namespaces: prefer pkg::fn() throughout (avoid attaching many packages).
  • snake_case: for function and variable names.
  • Compact code: optimize for readability + fewer lines, especially in Shiny/module code.

For sensitive operations, ensure you follow:

  • Env vars for secrets: never commit keys/tokens; use .Renviron locally and CI secrets in workflows.

Testing

All packages should have tests for exported functions.

Unit tests (testthat)

Run tests:

devtools::test()

Coverage

Mocking policy (high-level)

  • Allowed: httptest2 (external HTTP APIs only), shinytest2 (Shiny apps/modules)
  • Never mock: database connections, CDN assets, internal platform infrastructure

Development Commands (Local)

The minimum local loop:

devtools::document()
lintr::lint_package()
devtools::test()
devtools::check()

R CMD check (CI parity)

devtools::check() is the closest to what CI runs; use it before PRs.


CI (GitHub Actions)

This template ships standard workflows under .github/workflows/:

  • R-CMD-check.yaml – build + test gate
  • lint.yamllintr::lint_package()
  • test-coverage.yaml – coverage reporting (if configured)

Secrets policy:

  • Put dev secrets in .Renviron (never commit)
  • Put CI secrets in GitHub Actions secrets (e.g., GITHUB_PAT, CODECOV_TOKEN)

Release Process (Suggested)

  1. Update NEWS.md with user-visible changes.
  2. Bump version in DESCRIPTION (prefer semver-like discipline).
  3. Ensure devtools::check() passes locally.
  4. Merge to main.
  5. Tag a release if the package is consumed by pinned dependencies.

Support / Ownership

  • File bugs via the repo issue tracker (BugReports in DESCRIPTION).
  • For security concerns, do not open public issues – follow the internal reporting process.