league #1

Merged
h merged 41 commits from league into master 2026-02-15 19:59:31 +11:00
6 changed files with 83 additions and 107 deletions
Showing only changes of commit aaf532b835 - Show all commits

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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))
})
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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>