Skip to content

AuthorService architecture

Service path: /author. Auth: @requires: 'Tutorial.Author'. Source: srv/author-service.cds.

Overview

AuthorService is the OData V4 surface for tutorial authors. It exposes a read-only view of the author's own tutorials (MyTutorials), the tag taxonomy (Tags), branch analytics, and a handful of actions (review/snooze/OS-variant generation/slug-availability check).

The service is consumed by:

  • The Sage VS Code extension (primary consumer).
  • The admin shell at /admin-ui/ (indirectly — most admin reads go through AdminService).

Entities

EntityShapeNotes
Tutorialsprojection on Tutorials (ID, slug, title, primaryTag, status)Read-only. Lightweight metadata only.
Tagsprojection on Tags with virtual actualTagactualTag is HANA-native SUBSTR_AFTER(name, '>').
MyTutorialsprojection on MyTutorialsViewFiltered to ownerUserId == req.user.id by a before-handler.
AnalyticsBranchPerformance / AnalyticsBranchTopPickaggregated branch analyticsSee srv/analytics-service.cds for the canonical definition.

Actions

ActionInputsReturnsNotes
reviewTutorialtutorialId : UUID{ reviewedDate, notificationNumber }Owner-only. Resets the 4-nag state.
snoozeTutorialtutorialId, days{ notificationDate, notificationNumber }Owner-only. Pushes the next nag out.
generateOsVariantssourceMarkdown, sourceOS, targetOSes, context{ variants, model, tokensUsed, requestId }Per-user rate-limited (60/hr).
isSlugAvailableslug : StringBooleanServer-side case-insensitive uniqueness check. UX hint, not a lock.

#385 PR-3 field renames (2026-06-21)

The MyTutorials entity in AuthorService underwent renames as part of unifying field names with Sage's expectations. Consumers migrating from the previous schema can use this table:

Old name (pre-PR-3)New name (post-PR-3)Notes
ownerNameownerPure rename; underlying TutorialMeta.owner column unchanged.
lastNotificationDatenotificationDatePure rename; applies to MyTutorials AND snoozeTutorial action return. Underlying TutorialMeta.lastNotificationDate column unchanged.
outdated(deleted)Use daysSinceReview with a client-side threshold (Sage owns the UX).
(none — new)repositoryNameRepo group name from TutorialRepositories (#385 PR-1 schema, PR-2 backfill). NULL until backfill runs.
(none — new)monitoredBoolean: true iff monitoredStatus === 'ACTIVE'.
(none — new)daysSinceReviewInteger: DAYS_BETWEEN(reviewedDate, NOW). Server-side $filter/$orderby supported. NULL when reviewedDate is NULL.
(none — new on Tags)actualTagTags virtual; HANA-native SUBSTR_AFTER(name, '>'). Leaf after last >.

New action: isSlugAvailable

isSlugAvailable(slug : String) returns Boolean — server-side case-insensitive uniqueness check across all Tutorials.slug. Use before creating a new tutorial to surface conflicts to the user; the write-side @assert.unique.slug remains the source of truth at insert time.

The check is intentionally a UX hint, not a lock. A benign TOCTOU window exists between the check and a subsequent insert; the write-side constraint catches any race condition.

Authorization

Service-level @requires: 'Tutorial.Author' is the only gate. All entities, actions, and read paths require the caller's req.user.roles['Tutorial.Author'] to be true.

The MyTutorials projection additionally filters by ownerUserId == req.user.id via a this.before('READ', ...) handler. The ownership assertion for reviewTutorial and snoozeTutorial is enforced in handlers (srv/author-service.js).