Files
summercms/modules/towel
Jakub Zych 24cf6cd931 fix(pier): end only the revoked token's or client's MCP sessions and log the client address (B-WR-04, B-IN-04)
- pact.MCPSubjectChanged and pier.Event gain TokenID and ClientID; a
  narrowed revoked event closes only matching sessions and stateless servers
- cabana fires the admin token revoke with the token row id
- towel.WithClientIP/ClientIP; pier puts the address (surf applies
  http.trusted_proxies) on the resolve context and ResolveBackendToken logs it
2026-10-23 16:21:37 +02:00
..

towel

Request-scoped actor, organization, collection and locale values carried through context.Context.

import "git.golem15.com/golem15/summercms/modules/towel"

Name: You carry it everywhere and it marks your spot; it carries the actor, organization and locale through each request.

Overview

towel replaces the request-global state that WinterCMS reads through facades (the current locale, the acting user) with explicit values on the request context. Middleware stores a value once, and any code further down the call chain that receives the context reads it back without a global lookup. surf sets the locale from Accept-Language (or the signed-in user's preferred locale) and the organization for every request, phrasebook reads the locale when it translates, and cabana sets it while localizing admin schemas.

Role in the framework

A dependency-free leaf for request-scoped values. surf sets them, and phrasebook, cabana and plugin code read them. It replaces WinterCMS facades for per-request state, so it has to be importable by any module without cycles.

Use cases:

  • Read the acting user or organization in a service without passing them through every call.
  • Translate in the request's locale from deep inside a plugin.
  • Set a locale explicitly in a job or a test.

Features

  • Five independent string values, each with a setter and a getter: actor (towel.WithActor, towel.Actor), organization (towel.WithOrganization, towel.Organization), collection (towel.WithCollection, towel.Collection), locale (towel.WithLocale, towel.Locale) and client address (towel.WithClientIP, towel.ClientIP).
  • Unexported context key types, so no other package can read or overwrite the values by accident.
  • Getters return (value, ok): a value that was set to an empty string is distinguishable from one that was never set.
  • Nil-safe: a setter given a nil context starts from context.Background(), and a getter given a nil context reports false.

Usage

package blog

import (
	"fmt"
	"net/http"

	"git.golem15.com/golem15/summercms/modules/towel"
)

// withAcmeScope tags every request with the organization and collection it serves.
func withAcmeScope(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := towel.WithOrganization(r.Context(), "acme")
		ctx = towel.WithCollection(ctx, "blog")
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

func listPosts(w http.ResponseWriter, r *http.Request) {
	org, _ := towel.Organization(r.Context())
	locale, ok := towel.Locale(r.Context())
	if !ok {
		locale = "en"
	}
	fmt.Fprintf(w, "posts for %s in %s\n", org, locale)
}

API reference

Identifier Description
towel.WithActor / towel.Actor Store and read the acting user or client identifier.
towel.WithOrganization / towel.Organization Store and read the organization (tenant) the request belongs to.
towel.WithCollection / towel.Collection Store and read the collection the request operates on.
towel.WithLocale / towel.Locale Store and read the locale used for translations and localized responses.
towel.WithClientIP / towel.ClientIP Store and read the request's client address for code below the HTTP layer, such as a token resolver logging a failed attempt.

Dependencies

  • SummerCMS modules: none.
  • Third-party: none.
  • Standard library: context.

Testing

go test ./modules/towel/...

The tests exercise plain contexts and need no external services.