Skip to content

Commit 4568856

Browse files
Merge pull request #42 from aa-replicated/demo/tier4
feat: gate custom branding behind license field (Tier 4.7)
2 parents ecd8f19 + bc28252 commit 4568856

9 files changed

Lines changed: 57 additions & 7 deletions

File tree

chart/gameshelf/templates/configmap.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ metadata:
77
data:
88
port: "8080"
99
site-name: {{ .Values.siteName | quote }}
10+
site-color: {{ .Values.siteColor | default "#3B82F6" | quote }}
11+
custom-branding-enabled: {{ .Values.customBrandingEnabled | quote }}

chart/gameshelf/templates/deployment.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ spec:
6161
secretKeyRef:
6262
name: {{ include "gameshelf.fullname" . }}
6363
key: admin-secret
64+
- name: SITE_COLOR
65+
valueFrom:
66+
configMapKeyRef:
67+
name: {{ include "gameshelf.fullname" . }}
68+
key: site-color
69+
- name: CUSTOM_BRANDING_ENABLED
70+
valueFrom:
71+
configMapKeyRef:
72+
name: {{ include "gameshelf.fullname" . }}
73+
key: custom-branding-enabled
6474
- name: SDK_SERVICE_URL
6575
value: "http://replicated:3000"
6676
livenessProbe:

chart/gameshelf/values.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ resources:
6363

6464
siteName: "GameShelf"
6565
adminSecret: "changeme" # REQUIRED — set a strong secret, e.g. --set adminSecret=... or in a values override
66+
siteColor: "#3B82F6"
67+
customBrandingEnabled: "false"
6668

6769
# --- Embedded PostgreSQL (Bitnami subchart) ---
6870
postgresql:

helmchart.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ spec:
1111
integrationLicenseID: repl{{ LicenseFieldValue `licenseID` }}
1212
adminSecret: repl{{ ConfigOption `admin_secret`}}
1313
siteName: repl{{ ConfigOption `site_name`}}
14+
siteColor: repl{{ ConfigOption `site_color`}}
15+
customBrandingEnabled: repl{{ LicenseFieldValue `custom_branding_enabled` }}
1416
builder:
1517
image:
1618
tag: latest

internal/api/admin.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ func (s *Server) toggleGameHandler(w http.ResponseWriter, r *http.Request) {
9292

9393
// POST /admin/branding — update site branding
9494
func (s *Server) updateBrandingHandler(w http.ResponseWriter, r *http.Request) {
95+
if !s.cfg.CustomBrandingEnabled {
96+
http.Error(w, "Custom branding requires an upgraded license", http.StatusForbidden)
97+
return
98+
}
9599
if err := r.ParseForm(); err != nil {
96100
http.Error(w, "bad request", http.StatusBadRequest)
97101
return
@@ -141,6 +145,10 @@ func (s *Server) logoHandler(w http.ResponseWriter, r *http.Request) {
141145

142146
// POST /admin/logo — upload a new logo image
143147
func (s *Server) uploadLogoHandler(w http.ResponseWriter, r *http.Request) {
148+
if !s.cfg.CustomBrandingEnabled {
149+
http.Error(w, "Custom branding requires an upgraded license", http.StatusForbidden)
150+
return
151+
}
144152
r.Body = http.MaxBytesReader(w, r.Body, 2<<20) // 2MB
145153
if err := r.ParseMultipartForm(2 << 20); err != nil {
146154
http.Error(w, "file too large (max 2MB)", http.StatusBadRequest)

internal/api/handlers.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ type PageData struct {
3333
DBScores []db.Score
3434
AllGames []db.Game
3535
Site *db.Site
36-
IdentitySecretMasked string // shown (masked) on admin panel
36+
IdentitySecretMasked string // shown (masked) on admin panel
37+
CustomBrandingEnabled bool // true when custom_branding_enabled license field is "true"
3738
}
3839

3940
// pageBase fills the branding fields from the DB and SDK banner state.
@@ -43,7 +44,7 @@ func (s *Server) pageBase(r *http.Request) PageData {
4344
if err != nil || site == nil {
4445
data = PageData{
4546
SiteName: s.cfg.SiteName,
46-
PrimaryColor: "#3B82F6",
47+
PrimaryColor: s.cfg.SiteColor,
4748
SecondaryColor: "#1E40AF",
4849
BackgroundColor: "#F9FAFB",
4950
FontFamily: "system",
@@ -60,6 +61,8 @@ func (s *Server) pageBase(r *http.Request) PageData {
6061
}
6162
}
6263

64+
data.CustomBrandingEnabled = s.cfg.CustomBrandingEnabled
65+
6366
// Populate SDK banners (fail-open: errors are logged and ignored)
6467
if s.sdk.Available() {
6568
expiresAt, err := s.sdk.GetExpiresAt(r.Context())

internal/config/config.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ type Config struct {
99
Port string
1010
SiteName string
1111
IdentitySecret string // optional; auto-generated and stored in DB if empty
12-
SDKServiceURL string // URL of Replicated SDK sidecar, e.g. http://localhost:3000
13-
LocalDev bool // LOCAL_DEV=true bypasses SDK gates when SDK_SERVICE_URL is unset
12+
SDKServiceURL string // URL of Replicated SDK sidecar, e.g. http://localhost:3000
13+
LocalDev bool // LOCAL_DEV=true bypasses SDK gates when SDK_SERVICE_URL is unset
14+
SiteColor string // default primary color (hex), overridden by DB branding settings
15+
CustomBrandingEnabled bool // set by LicenseFieldValue custom_branding_enabled via KOTS
1416
}
1517

1618
func Load() Config {
@@ -22,6 +24,10 @@ func Load() Config {
2224
if siteName == "" {
2325
siteName = "GameShelf"
2426
}
27+
siteColor := os.Getenv("SITE_COLOR")
28+
if siteColor == "" {
29+
siteColor = "#3B82F6"
30+
}
2531
adminSecret := os.Getenv("ADMIN_SECRET")
2632
if adminSecret == "" {
2733
adminSecret = "changeme"
@@ -32,8 +38,10 @@ func Load() Config {
3238
AdminSecret: adminSecret,
3339
Port: port,
3440
SiteName: siteName,
35-
IdentitySecret: os.Getenv("IDENTITY_SECRET"),
36-
SDKServiceURL: os.Getenv("SDK_SERVICE_URL"),
37-
LocalDev: os.Getenv("LOCAL_DEV") == "true",
41+
IdentitySecret: os.Getenv("IDENTITY_SECRET"),
42+
SDKServiceURL: os.Getenv("SDK_SERVICE_URL"),
43+
LocalDev: os.Getenv("LOCAL_DEV") == "true",
44+
SiteColor: siteColor,
45+
CustomBrandingEnabled: os.Getenv("CUSTOM_BRANDING_ENABLED") == "true",
3846
}
3947
}

kots-config.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,16 @@ spec:
1717
type: text
1818
default: "GameShelf"
1919
help_text: "The name displayed in the browser title and header."
20+
- name: branding
21+
title: Branding
22+
when: '{{repl LicenseFieldValue "custom_branding_enabled" | eq "true"}}'
23+
items:
24+
- name: site_color
25+
title: Primary Color
26+
type: text
27+
default: "#3B82F6"
28+
help_text: "Primary color for the GameShelf UI (hex format, e.g. #3B82F6). Requires the Custom Branding license entitlement."
29+
validation:
30+
regex:
31+
pattern: '^#[0-9A-Fa-f]{6}$'
32+
message: "Must be a valid hex color code (e.g. #3B82F6)"

templates/admin.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
{{ define "content" }}
66
<h1 class="text-3xl font-extrabold text-gray-900 mb-8">Admin Panel</h1>
77

8+
{{ if .CustomBrandingEnabled }}
89
<!-- Branding Section -->
910
<section class="bg-white rounded-2xl shadow-sm border border-gray-100 p-6 mb-6">
1011
<h2 class="text-lg font-bold text-gray-900 mb-4">Site Branding</h2>
@@ -83,6 +84,7 @@ <h2 class="text-lg font-bold text-gray-900 mb-4">Logo</h2>
8384
</button>
8485
</form>
8586
</section>
87+
{{ end }}
8688

8789
<!-- Identity Section -->
8890
<section class="bg-white rounded-2xl shadow-sm border border-gray-100 p-6 mb-6">

0 commit comments

Comments
 (0)