Skip to content

Architecture overview

Updated 13 Jul 2026jirkamotejl

Knowspread is a Rails application with two frontend generations and several API surfaces. This page is an orientation map for developers, managers, and technical owners. It does not replace code-level documentation, but it should make the main flows and source files easier to navigate.

SurfaceLocationAudienceNotes
Rails backendapp/All clientsOwns persistence, authorization, APIs, jobs, billing, and domain logic.
Legacy AngularJS appclient/Existing product UIOlder frontend surface still present in the product.
React appfrontend/Newer product UINewer frontend surface.
Internal JSON APIapp/controllers/api/v1/Product frontendsCompany-space API, content editor/viewer, reporting, webhooks, settings.
Public REST APIapp/controllers/rest/v1/External clientsAuth token, users, groups, content participations, contents, learning events.
flowchart LR
  Frontend[AngularJS or React UI] --> Api[api/v1 controllers]
  External[External system] --> Rest[rest/v1 controllers]
  Api --> Policy[Pundit policies]
  Rest --> Auth[REST auth]
  Policy --> Interaction[Interactions and domain services]
  Auth --> Interaction
  Interaction --> Models[ActiveRecord models]
  Models --> Jobs[Background jobs]
  Models --> Events[Wisper events]
  Jobs --> Models
  Events --> Listeners[Listeners]
  Models --> Serializers[Serializers]
  Serializers --> Frontend
  Serializers --> External

The preferred implementation style is thin controllers, authorization in Pundit policies, command-like flows in app/interactions/, persistent domain state in models, and response shaping through serializers.

flowchart TD
  CompanySpace[CompanySpace] --> UICS[UserInCompanySpace]
  User[User] --> UICS
  CompanySpace --> UserGroup[UserGroup]
  UICS --> Membership[UserInUserGroup]
  UserGroup --> Membership

  CompanySpace --> CICS[ContentInCompanySpace]
  Content[Content] --> CICS
  Content --> Version[ContentVersion]
  CICS --> Assigned[AssignedContent]
  UserGroup --> Assigned
  UICS --> Assigned

  Assigned --> CA[ContentAssigner job]
  CA --> Source[ParticipationSource]
  Source --> Participation[ContentParticipation]
  Version --> Participation
  Participation --> Reporting[Reporting, certificates, exports]

The important product distinction is intent versus effective study state:

ConceptMeaning
AssignedContentIntent: this content should be assigned to a user or group.
ParticipationSourceExplanation: why a concrete study record exists.
ContentParticipationEffective study record for a user and content version.
ContentLicenseAvailability constraint for a user and content in a company space.

Content assignment is reconciled by the ContentAssigner family of classes:

ComponentSourceResponsibility
ContentAssigner::Assignerapp/models/content_assigner/assigner.rbProcesses one assignment job and locks per user while reconciling state.
ContentAssigner::AssignmentResolverapp/models/content_assigner/assignment_resolver.rbExpands group and user assignment intent.
ContentAssigner::SourceProducerapp/models/content_assigner/source_producer.rbProduces desired participation sources.
ContentAssigner::ParticipationResolverapp/models/content_assigner/participation_resolver.rbDiffs desired sources against existing participations.
ContentAssigner::LicenseResolverapp/models/content_assigner/license_resolver.rbEnsures license availability is respected.
ContentAssigner::DataSaverapp/models/content_assigner/data_saver.rbPersists participation, source, and license changes.

Operationally, this means assignment changes are not just simple CRUD writes. They trigger reconciliation that can add sources, update deadlines, reset some in-progress participations, preserve completed participations, and create license records.

The current public REST routes include:

AreaRoute familyPurpose
Authentication/rest/v1/auth/tokenObtain token for external API access.
Users/rest/v1/usersList, read, create, update, archive, and unarchive users.
User groups/rest/v1/user_groupsList, update, add users, remove users.
Company space/rest/v1/company_space/infoRead company-space information.
Content participations/rest/v1/content_participationsRead study state and certificate.
Contents/rest/v1/contentsList, create, read, and update content.
Learning events/rest/v1/learning_eventsList, create, read, update events and days.

Generated or source API documentation should still be treated as the endpoint contract. This page explains where those routes fit in the product.

  • A global User can exist in multiple company spaces through UserInCompanySpace.
  • A revoked user should remain traceable for study history, reports, and audit.
  • A group should have one clear operational meaning because group membership can change assignment state.
  • A content participation can have multiple participation sources.
  • Completed participations should generally be preserved even when assignment sources change.
  • Content version upgrades are not automatically equivalent to rewriting every in-progress study record.
  • Public API integrations need a stable user identifier that survives e-mail or name changes.