diff --git a/internal/contexts/permissions.go b/internal/contexts/permissions.go index 8950073..37ea512 100644 --- a/internal/contexts/permissions.go +++ b/internal/contexts/permissions.go @@ -21,3 +21,44 @@ type PermissionCache struct { Roles map[roles.Role]bool HasWildcard bool } + +// HasPermission returns true if the cache contains the provided permission +func (p *PermissionCache) HasPermission(perm permissions.Permission) bool { + if p.HasWildcard { + return true + } + _, exists := p.Permissions[perm] + return exists +} + +// HasAnyPermission returns true if the cache contains any of the provided permissions +func (p *PermissionCache) HasAnyPermission(perms []permissions.Permission) bool { + if p.HasWildcard { + return true + } + for _, perm := range perms { + _, exists := p.Permissions[perm] + if exists { + return true + } + } + return false +} + +// HasAllPermissions returns true only if more than one permission is provided and the cache +// contains all the provided permissions +func (p *PermissionCache) HasAllPermissions(perms []permissions.Permission) bool { + if p.HasWildcard { + return true + } + if len(perms) == 0 { + return false + } + for _, perm := range perms { + _, exists := p.Permissions[perm] + if !exists { + return false + } + } + return true +} diff --git a/internal/embedfs/web/css/output.css b/internal/embedfs/web/css/output.css index 5141f60..4fe268b 100644 --- a/internal/embedfs/web/css/output.css +++ b/internal/embedfs/web/css/output.css @@ -45,36 +45,6 @@ --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); --default-font-family: var(--font-sans); --default-mono-font-family: var(--font-mono); - --color-rosewater: var(--rosewater); - --color-flamingo: var(--flamingo); - --color-pink: var(--pink); - --color-mauve: var(--mauve); - --color-red: var(--red); - --color-dark-red: var(--dark-red); - --color-maroon: var(--maroon); - --color-peach: var(--peach); - --color-yellow: var(--yellow); - --color-dark-yellow: var(--dark-yellow); - --color-green: var(--green); - --color-dark-green: var(--dark-green); - --color-teal: var(--teal); - --color-sky: var(--sky); - --color-sapphire: var(--sapphire); - --color-blue: var(--blue); - --color-dark-blue: var(--dark-blue); - --color-lavender: var(--lavender); - --color-text: var(--text); - --color-subtext1: var(--subtext1); - --color-subtext0: var(--subtext0); - --color-overlay2: var(--overlay2); - --color-overlay1: var(--overlay1); - --color-overlay0: var(--overlay0); - --color-surface2: var(--surface2); - --color-surface1: var(--surface1); - --color-surface0: var(--surface0); - --color-base: var(--base); - --color-mantle: var(--mantle); - --color-crust: var(--crust); } } @layer base { @@ -273,9 +243,6 @@ .top-0 { top: calc(var(--spacing) * 0); } - .top-1 { - top: calc(var(--spacing) * 1); - } .top-1\/2 { top: calc(1/2 * 100%); } @@ -309,24 +276,6 @@ .z-50 { z-index: 50; } - .container { - width: 100%; - @media (width >= 40rem) { - max-width: 40rem; - } - @media (width >= 48rem) { - max-width: 48rem; - } - @media (width >= 64rem) { - max-width: 64rem; - } - @media (width >= 80rem) { - max-width: 80rem; - } - @media (width >= 96rem) { - max-width: 96rem; - } - } .mx-auto { margin-inline: auto; } @@ -493,22 +442,9 @@ .flex-1 { flex: 1; } - .flex-shrink { - flex-shrink: 1; - } .shrink-0 { flex-shrink: 0; } - .grow { - flex-grow: 1; - } - .border-collapse { - border-collapse: collapse; - } - .-translate-y-1 { - --tw-translate-y: calc(var(--spacing) * -1); - translate: var(--tw-translate-x) var(--tw-translate-y); - } .-translate-y-1\/2 { --tw-translate-y: calc(calc(1/2 * 100%) * -1); translate: var(--tw-translate-x) var(--tw-translate-y); @@ -610,11 +546,6 @@ border-color: var(--surface2); } } - .truncate { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } .overflow-hidden { overflow: hidden; } @@ -907,9 +838,6 @@ .italic { font-style: italic; } - .underline { - text-decoration-line: underline; - } .opacity-50 { opacity: 50%; } @@ -925,10 +853,6 @@ --tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } - .outline { - outline-style: var(--tw-outline-style); - outline-width: 1px; - } .filter { filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); } @@ -1513,11 +1437,6 @@ inherits: false; initial-value: 0 0 #0000; } -@property --tw-outline-style { - syntax: "*"; - inherits: false; - initial-value: solid; -} @property --tw-blur { syntax: "*"; inherits: false; @@ -1602,7 +1521,6 @@ --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; - --tw-outline-style: solid; --tw-blur: initial; --tw-brightness: initial; --tw-contrast: initial; diff --git a/internal/rbac/cache_middleware.go b/internal/rbac/cache_middleware.go index a834b82..148f803 100644 --- a/internal/rbac/cache_middleware.go +++ b/internal/rbac/cache_middleware.go @@ -20,9 +20,17 @@ func (c *Checker) LoadPermissionsMiddleware() hws.Middleware { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := db.CurrentUser(r.Context()) + // Build permission cache + cache := &contexts.PermissionCache{ + Permissions: make(map[permissions.Permission]bool), + Roles: make(map[roles.Role]bool), + } + defer func() { + ctx := context.WithValue(r.Context(), contexts.PermissionCacheKey, cache) + next.ServeHTTP(w, r.WithContext(ctx)) + }() + if user == nil { - // No authenticated user - continue without permissions - next.ServeHTTP(w, r) return } @@ -45,16 +53,9 @@ func (c *Checker) LoadPermissionsMiddleware() hws.Middleware { Error: err, Level: hws.ErrorERROR, }) - next.ServeHTTP(w, r) return } - // Build permission cache - cache := &contexts.PermissionCache{ - Permissions: make(map[permissions.Permission]bool), - Roles: make(map[roles.Role]bool), - } - // Check for wildcard permission hasWildcard := false for _, perm := range perms { @@ -68,10 +69,6 @@ func (c *Checker) LoadPermissionsMiddleware() hws.Middleware { for _, role := range roles_ { cache.Roles[role.Name] = true } - - // Add cache to context (type-safe) - ctx := context.WithValue(r.Context(), contexts.PermissionCacheKey, cache) - next.ServeHTTP(w, r.WithContext(ctx)) }) } } diff --git a/internal/validation/stringfield.go b/internal/validation/stringfield.go index 5b97e4e..00c07db 100644 --- a/internal/validation/stringfield.go +++ b/internal/validation/stringfield.go @@ -104,3 +104,9 @@ func (s *StringField) TrimSpace() *StringField { s.Value = strings.TrimSpace(s.Value) return s } + +// Replace replaces all occurances of the given substring +func (s *StringField) Replace(old, new string) *StringField { + s.Value = strings.ReplaceAll(s.Value, old, new) + return s +} diff --git a/internal/view/seasonsview/detail_page.templ b/internal/view/seasonsview/detail_page.templ index 93c0da3..f39fa58 100644 --- a/internal/view/seasonsview/detail_page.templ +++ b/internal/view/seasonsview/detail_page.templ @@ -4,6 +4,8 @@ import "git.haelnorr.com/h/oslstats/internal/db" import "git.haelnorr.com/h/oslstats/internal/view/baseview" import "time" import "strconv" +import "git.haelnorr.com/h/oslstats/internal/permissions" +import "git.haelnorr.com/h/oslstats/internal/contexts" templ DetailPage(season *db.Season) { @baseview.Layout(season.Name) { @@ -14,6 +16,10 @@ templ DetailPage(season *db.Season) { } templ SeasonDetails(season *db.Season) { + {{ + permCache := contexts.Permissions(ctx) + canEditSeason := permCache.HasPermission(permissions.SeasonsUpdate) + }}
@@ -25,13 +31,15 @@ templ SeasonDetails(season *db.Season) {
- - Edit - + if canEditSeason { + + Edit + + }
Seasons - Add season + if canAddSeason { + Add season + }
@sort.Dropdown(seasons.PageOpts, sortOpts)