This commit is contained in:
2025-10-25 09:03:03 +04:00
parent 569d980336
commit 9d2dedb2b6
5 changed files with 194 additions and 20 deletions

View File

@@ -13,6 +13,7 @@ pub struct Atlas {
pub mut:
name string
collections map[string]&Collection
groups map[string]&Group // name -> Group mapping
}
@[params]
@@ -129,3 +130,32 @@ pub fn (mut a Atlas) fix_links() ! {
col.fix_links()!
}
}
// Add a group to the atlas
pub fn (mut a Atlas) group_add(mut group Group) ! {
if group.name in a.groups {
return error('Group ${group.name} already exists')
}
a.groups[group.name] = &group
}
// Get a group by name
pub fn (a Atlas) group_get(name string) !&Group {
name_lower := texttools.name_fix(name)
return a.groups[name_lower] or { return error('Group ${name} not found') }
}
// Get all groups matching a session's email
pub fn (a Atlas) groups_get(session Session) []&Group {
mut matching := []&Group{}
email_lower := session.email.to_lower()
for _, group in a.groups {
if group.matches(email_lower) {
matching << group
}
}
return matching
}

View File

@@ -3,23 +3,33 @@ module atlas
import incubaid.herolib.core.pathlib
import incubaid.herolib.core.texttools
import incubaid.herolib.core.base
import incubaid.herolib.data.paramsparser { Params }
import incubaid.herolib.ui.console
import os
pub struct Session {
pub mut:
user string // username
email string // user's email (lowercase internally)
params Params // additional context from request/webserver
}
@[heap]
pub struct Collection {
pub mut:
name string @[required]
path pathlib.Path @[required]
pages map[string]&Page
images map[string]&File
files map[string]&File
atlas &Atlas @[skip; str: skip]
errors []CollectionError
error_cache map[string]bool
name string @[required]
path pathlib.Path @[required]
pages map[string]&Page
images map[string]&File
files map[string]&File
atlas &Atlas @[skip; str: skip]
errors []CollectionError
error_cache map[string]bool
git_url string // NEW: URL to the git repository for editing
git_branch string // NEW: Git branch for this collection
git_edit_url string @[skip]
acl_read []string // Group names allowed to read (lowercase)
acl_write []string // Group names allowed to write (lowercase)
}
@[params]
@@ -235,23 +245,23 @@ pub fn (mut c Collection) error(args CollectionErrorArgs) {
message: args.message
file: args.file
}
// Calculate hash for deduplication
hash := err.hash()
// Check if this error was already reported
if hash in c.error_cache {
return // Skip duplicate
return
}
// Mark this error as reported
c.error_cache[hash] = true
// Log to errors array if requested
if args.log_error {
c.errors << err
}
// Show in console if requested
if args.show_console {
console.print_stderr('[${c.name}] ${err.str()}')
@@ -277,11 +287,11 @@ pub fn (mut c Collection) clear_errors() {
// Get error summary by category
pub fn (c Collection) error_summary() map[CollectionErrorCategory]int {
mut summary := map[CollectionErrorCategory]int{}
for err in c.errors {
summary[err.category] = summary[err.category] + 1
}
return summary
}
@@ -291,9 +301,9 @@ pub fn (c Collection) print_errors() {
console.print_green('Collection ${c.name}: No errors')
return
}
console.print_header('Collection ${c.name} - Errors (${c.errors.len})')
for err in c.errors {
console.print_stderr(' ${err.str()}')
}
@@ -311,13 +321,57 @@ pub fn (mut c Collection) fix_links() ! {
for _, mut page in c.pages {
// Read original content
content := page.read_content()!
// Fix links
fixed_content := page.fix_links(content)!
// Write back if changed
if fixed_content != content {
page.path.write(fixed_content)!
}
}
}
// Check if session can read this collection
pub fn (c Collection) can_read(session Session) bool {
// If no ACL set, everyone can read
if c.acl_read.len == 0 {
return true
}
// Get user's groups
mut atlas := c.atlas
groups := atlas.groups_get(session)
group_names := groups.map(it.name)
// Check if any of user's groups are in read ACL
for acl_group in c.acl_read {
if acl_group in group_names {
return true
}
}
return false
}
// Check if session can write this collection
pub fn (c Collection) can_write(session Session) bool {
// If no ACL set, no one can write
if c.acl_write.len == 0 {
return false
}
// Get user's groups
mut atlas := c.atlas
groups := atlas.groups_get(session)
group_names := groups.map(it.name)
// Check if any of user's groups are in write ACL
for acl_group in c.acl_write {
if acl_group in group_names {
return true
}
}
return false
}

View File

@@ -11,6 +11,7 @@ pub enum CollectionErrorCategory {
file_not_found
invalid_collection
general_error
acl_denied // NEW: Access denied by ACL
}
pub struct CollectionError {
@@ -57,5 +58,6 @@ pub fn (e CollectionError) category_str() string {
.file_not_found { 'File Not Found' }
.invalid_collection { 'Invalid Collection' }
.general_error { 'General Error' }
.acl_denied { 'ACL Access Denied' }
}
}

62
lib/data/atlas/group.v Normal file
View File

@@ -0,0 +1,62 @@
module atlas
import incubaid.herolib.core.texttools
@[heap]
pub struct Group {
pub mut:
name string // normalized to lowercase
patterns []string // email patterns, normalized to lowercase
}
@[params]
pub struct GroupNewArgs {
pub mut:
name string @[required]
patterns []string @[required]
}
// Create a new Group
pub fn new_group(args GroupNewArgs) !Group {
mut name := texttools.name_fix(args.name)
mut patterns := args.patterns.map(it.to_lower())
return Group{
name: name
patterns: patterns
}
}
// Check if email matches any pattern in this group
pub fn (g Group) matches(email string) bool {
email_lower := email.to_lower()
for pattern in g.patterns {
if matches_pattern(email_lower, pattern) {
return true
}
}
return false
}
// Helper: match email against wildcard pattern
// '*@domain.com' matches 'user@domain.com'
// 'exact@email.com' matches only 'exact@email.com'
fn matches_pattern(email string, pattern string) bool {
if pattern == '*' {
return true
}
if !pattern.contains('*') {
return email == pattern
}
// Handle wildcard patterns like '*@domain.com'
if pattern.starts_with('*') {
suffix := pattern[1..] // Remove the '*'
return email.ends_with(suffix)
}
// Could add more complex patterns here if needed
return false
}

View File

@@ -95,6 +95,7 @@ fn should_skip_dir(entry pathlib.Path) bool {
// Scan collection directory for files
fn (mut c Collection) scan() ! {
c.scan_path(mut c.path)!
c.scan_acl()! // NEW: scan ACL files
c.detect_git_url() or {
console.print_debug('Could not detect git URL for collection ${c.name}: ${err}')
}
@@ -133,3 +134,28 @@ fn (mut c Collection) scan_path(mut dir pathlib.Path) ! {
}
}
}
// Scan for ACL files
fn (mut c Collection) scan_acl() ! {
// Look for read.acl in collection directory
read_acl_path := '${c.path.path}/read.acl'
if os.exists(read_acl_path) {
content := os.read_file(read_acl_path)!
// Split by newlines and normalize
c.acl_read = content.split('\n')
.map(it.trim_space())
.filter(it.len > 0)
.map(it.to_lower())
}
// Look for write.acl in collection directory
write_acl_path := '${c.path.path}/write.acl'
if os.exists(write_acl_path) {
content := os.read_file(write_acl_path)!
// Split by newlines and normalize
c.acl_write = content.split('\n')
.map(it.trim_space())
.filter(it.len > 0)
.map(it.to_lower())
}
}