Skip to contents

What is artutils?

artutils is the data access layer for the Artalytics platform. It provides the bridge between your application code and the database/CDN infrastructure.

┌─────────────────────────────────────┐
│  Your App (appPlatform, mod*)       │  ← Application logic
├─────────────────────────────────────┤
│  artutils                           │  ← Data access (this package)
├─────────────────────────────────────┤
│  artcore                            │  ← Infrastructure (DB, CDN, auth)
└─────────────────────────────────────┘

Key principle: artutils handles what data you need. artcore handles how to connect to databases and CDN. Your app handles why (business logic).

A Complete Workflow: Artist Profile Page

Let’s walk through building an artist profile page - a real workflow that demonstrates how artutils functions work together.

Step 1: Resolve the URL slug

When a user visits /artist/jane-doe, you need to convert that slug to data:

library(artutils)

# The URL gives us a slug
slug <- "jane-doe"

# Convert slug to artist record
artist_record <- get_artist_by_slug(slug)

if (is.null(artist_record)) {
  # Handle 404 - artist doesn't exist
  stop("Artist not found")
}

# Extract UUID for subsequent queries
# Convention: 'artist' holds the UUID, 'artist_record' holds the full row
artist <- artist_record$artist_uuid

cat("Found:", artist_record$artist_name, "\n")
cat("UUID:", artist, "\n")
cat("Bio:", substr(artist_record$bio, 1, 100), "...\n")

Step 2: Gather profile data

With the artist UUID, fetch everything needed for the profile header:

# Get aggregate statistics
stats <- get_artist_stats(artist)
cat("Artworks:", stats$artworks, "\n")
cat("Collections:", stats$collections, "\n")
cat("Member since:", format(stats$member_since, "%B %Y"), "\n")

# Get collections for navigation
collections <- get_artist_collections_summary(artist)
print(collections[, .(collection_name, artwork_count)])

# Get recent works for the gallery grid
recent <- get_artist_recent_works(artist, limit = 9)
print(recent[, .(art_title, created_utc)])

Step 3: Build asset URLs

Path functions construct CDN URLs without database queries - they’re pure string operations:

# Artist avatar for profile header
avatar_url <- pathArtistThumb(artist)
cat("Avatar:", avatar_url, "\n")

# Thumbnails for the gallery grid
for (i in seq_len(min(3, nrow(recent)))) {
  artwork <- recent$art_uuid[i]
  thumb <- pathArtworkThumb(artist, artwork)
  cat(recent$art_title[i], "->", thumb, "\n")
}

Step 4: Render the page

Now you have everything needed to render the profile:

profile_data <- list(
  header = list(
    name = artist_record$artist_name,
    bio = artist_record$bio,
    avatar = pathArtistThumb(artist),
    location = paste(artist_record$city, artist_record$state, sep = ", "),
    social = list(
      instagram = artist_record$url_ig,
      website = artist_record$url_site
    )
  ),
  stats = list(
    artworks = stats$artworks,
    collections = stats$collections,
    member_since = stats$member_since
  ),
  collections = collections,
  gallery = lapply(seq_len(nrow(recent)), function(i) {
    list(
      uuid = recent$art_uuid[i],
      title = recent$art_title[i],
      thumbnail = pathArtworkThumb(artist, recent$art_uuid[i])
    )
  })
)

# Pass to your Shiny UI or template engine
# renderArtistProfile(profile_data)

A Complete Workflow: Artwork Detail Page

Now let’s build the artwork detail view - the richest page in the platform.

The Easy Way: getAppdata()

For Shiny apps, getAppdata() aggregates everything in one call:

artist <- "746b8207-72f5-4ab6-8d19-a91d03daec3d"
artwork <- "99a61148-1d3b-4340-8cf6-92ad26046b0f"

# One call gets everything
appdata <- getAppdata(artist, artwork)

# Artist context (for header)
appdata$artist$info$artist_name
appdata$artist$stats$tot_artworks
appdata$artist$thumb

# Artwork details
appdata$artwork$info$basic$art_title
appdata$artwork$stats$brush_strokes
appdata$artwork$stats$drawing_hours
appdata$artwork$stats$n_unique_colors

# Benchmark scores (percentile vs artist's portfolio)
appdata$artwork$benchmarks$time_effort$score
appdata$artwork$benchmarks$time_effort$confidence

# Media paths for replay player
appdata$paths$frames      # Frame sequence prefix
appdata$config$n_frames   # Total frame count

# Certificate (if issued)
appdata$certificate$cert_id
appdata$certificate$path_pdf

The Manual Way: Individual Queries

For more control, query each piece separately:

# Core artwork record
index <- getArtworkIndex(artist, artwork)
cat("Title:", index$art_title, "\n")
cat("Created:", format(index$created_utc, "%Y-%m-%d"), "\n")

# Performance metrics
art_stats <- getArtworkStats(artist, artwork)
cat("Brush strokes:", format(art_stats$brush_strokes, big.mark = ","), "\n")
cat("Drawing hours:", round(art_stats$drawing_hours, 1), "\n")
cat("Unique colors:", art_stats$n_unique_colors, "\n")

# Image dimensions
meta <- getArtworkMeta(artist, artwork)
cat("Size:", meta$image_width, "x", meta$image_height, "\n")

# Style and description
profile <- getArtworkProfile(artist, artwork)
cat("Category:", profile$category, "\n")

# Frame-by-frame analytics (for replay player)
frames <- getFrameAnalytics(artist, artwork)
cat("Frames:", nrow(frames), "\n")
cat("Technique phases:", paste(unique(frames$technique_phase), collapse = ", "), "\n")

Connection Management

Auto-managed (Simple)

Most functions handle connections automatically:

# Connection opens, query runs, connection closes
stats <- get_artist_stats(artist)
# Nothing to clean up

Shared Connection (Efficient)

For multiple queries, share a connection:

# Open once
cn <- artcore::..dbc()
on.exit(artcore::..dbd(cn))  # Ensures cleanup on error

# Reuse for multiple queries
artist_record <- get_artist_by_slug("jane-doe", cn = cn)
artist <- artist_record$artist_uuid
stats <- get_artist_stats(artist, cn = cn)
collections <- get_artist_collections_summary(artist, cn = cn)
recent <- get_artist_recent_works(artist, limit = 6, cn = cn)

# Connection closes when function exits

Custom Queries

For queries not covered by helper functions:

# Direct SQL
result <- dbArtGet("
  SELECT artist_uuid, artist_name, created_utc
  FROM app.artist_index
  ORDER BY created_utc DESC
  LIMIT 10
")

# Single row as list
config <- dbArtGet(
  "SELECT * FROM app.artist_index WHERE slug = 'jane-doe'",
  unlist = TRUE
)
config$artist_name

Error Handling

artutils follows fail-fast philosophy. Missing data is an error, not a silent NULL:

# This will error if artwork doesn't exist
tryCatch({
  stats <- getArtworkStats(artist, "invalid-uuid")
}, error = function(e) {
  cat("Error:", e$message, "\n")
  # Log and investigate - don't silently ignore
})

Why fail-fast? Missing data usually means: 1. Invalid input (bad UUID) - fix the caller 2. Pipeline failure - data should exist but doesn’t 3. Race condition - record deleted between queries

Silent NULLs hide these issues. Errors surface them immediately.

Next Steps

Now that you understand the basics: