added perm checks to season pages
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}}
|
||||
<div class="bg-mantle border border-surface1 rounded-lg overflow-hidden">
|
||||
<!-- Header Section -->
|
||||
<div class="bg-surface0 border-b border-surface1 px-6 py-8">
|
||||
@@ -25,13 +31,15 @@ templ SeasonDetails(season *db.Season) {
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a
|
||||
href={ templ.SafeURL("/seasons/" + season.ShortName + "/edit") }
|
||||
class="rounded-lg px-4 py-2 hover:cursor-pointer text-center
|
||||
bg-blue hover:bg-blue/75 text-mantle transition"
|
||||
>
|
||||
Edit
|
||||
</a>
|
||||
if canEditSeason {
|
||||
<a
|
||||
href={ templ.SafeURL("/seasons/" + season.ShortName + "/edit") }
|
||||
class="rounded-lg px-4 py-2 hover:cursor-pointer text-center
|
||||
bg-blue hover:bg-blue/75 text-mantle transition"
|
||||
>
|
||||
Edit
|
||||
</a>
|
||||
}
|
||||
<a
|
||||
href="/seasons"
|
||||
class="rounded-lg px-4 py-2 hover:cursor-pointer text-center
|
||||
|
||||
@@ -7,6 +7,8 @@ import "git.haelnorr.com/h/oslstats/internal/view/sort"
|
||||
import "fmt"
|
||||
import "time"
|
||||
import "github.com/uptrace/bun"
|
||||
import "git.haelnorr.com/h/oslstats/internal/contexts"
|
||||
import "git.haelnorr.com/h/oslstats/internal/permissions"
|
||||
|
||||
templ ListPage(seasons *db.List[db.Season]) {
|
||||
@baseview.Layout("Seasons") {
|
||||
@@ -18,6 +20,8 @@ templ ListPage(seasons *db.List[db.Season]) {
|
||||
|
||||
templ SeasonsList(seasons *db.List[db.Season]) {
|
||||
{{
|
||||
permCache := contexts.Permissions(ctx)
|
||||
canAddSeason := permCache.HasPermission(permissions.SeasonsCreate)
|
||||
sortOpts := []db.OrderOpts{
|
||||
{
|
||||
Order: bun.OrderDesc,
|
||||
@@ -59,11 +63,13 @@ templ SeasonsList(seasons *db.List[db.Season]) {
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6 px-4">
|
||||
<div class="flex gap-4 items-center">
|
||||
<span class="text-3xl font-bold">Seasons</span>
|
||||
<a
|
||||
href="/seasons/new"
|
||||
class="rounded-lg px-2 py-1 hover:cursor-pointer text-center text-sm
|
||||
bg-green hover:bg-green/75 text-mantle transition"
|
||||
>Add season</a>
|
||||
if canAddSeason {
|
||||
<a
|
||||
href="/seasons/new"
|
||||
class="rounded-lg px-2 py-1 hover:cursor-pointer text-center text-sm
|
||||
bg-green hover:bg-green/75 text-mantle transition"
|
||||
>Add season</a>
|
||||
}
|
||||
</div>
|
||||
@sort.Dropdown(seasons.PageOpts, sortOpts)
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user