Files
linkbeam/internal/config/config.go

148 lines
3.9 KiB
Go

// config.go
/*
* Copyright (c) - All Rights Reserved.
*
* See the LICENSE file for more information.
*/
package config
import (
"errors"
"fmt"
"os"
"strings"
)
type Config struct {
Name string `yaml:"name"`
Bio string `yaml:"bio"`
Avatar string `yaml:"avatar"`
Theme string `yaml:"theme"`
Content []ContentBlock `yaml:"content"`
}
// ContentBlock represents a container section with a specific display type
// that can hold any number of named collections of items
type ContentBlock struct {
Type string `yaml:"type"`
// Collections holds dynamic named groups of items (e.g., "links", "gaming", "socials")
// We need to manually unmarshal this to handle arbitrary keys
Collections map[string][]Item `yaml:",inline"`
}
// Item represents a universal content item that can be a link, copyable text, or plain text
type Item struct {
Title string `yaml:"title,omitempty"`
URL string `yaml:"url,omitempty"` // Clickable link
CopyText string `yaml:"copy-text,omitempty"` // Click to copy text
Text string `yaml:"text,omitempty"` // Plain display text
Icon string `yaml:"icon,omitempty"`
Image string `yaml:"image,omitempty"` // Image URL or path
AltText string `yaml:"alt-text,omitempty"` // Accessibility text for images
}
func (c *Config) LinksCount() int {
count := 0
for _, block := range c.Content {
for _, items := range block.Collections {
for _, item := range items {
if item.URL != "" {
count++
}
}
}
}
return count
}
// GetAvailableThemes discovers available themes from the themes directory.
// It returns a list of theme names (without the .css extension).
func GetAvailableThemes(themesDir string) ([]string, error) {
entries, err := os.ReadDir(themesDir)
if err != nil {
// If themes directory doesn't exist, return empty list
if os.IsNotExist(err) {
return []string{}, nil
}
return nil, err
}
var themes []string
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
if strings.HasSuffix(name, ".css") {
themeName := strings.TrimSuffix(name, ".css")
themes = append(themes, themeName)
}
}
return themes, nil
}
func (c *Config) Validate() error {
return c.ValidateWithThemes("")
}
// ValidateWithThemes validates the config with theme discovery.
// If themesDir is empty, theme validation is skipped.
func (c *Config) ValidateWithThemes(themesDir string) error {
if c.Name == "" {
return errors.New("name cannot be empty")
}
// Validate content blocks
validBlockTypes := map[string]bool{
"vertical-list-text": true,
"horizontal-list-icons": true,
"vertical-list-images": true,
"footer": true,
}
for i, block := range c.Content {
if !validBlockTypes[block.Type] {
return fmt.Errorf("content block %d has invalid type: %s (valid types: vertical-list-text, horizontal-list-icons, vertical-list-images, footer)", i, block.Type)
}
// Validate items within collections
for collectionName, items := range block.Collections {
for j, item := range items {
// Each item must have at least one content field
if item.URL == "" && item.CopyText == "" && item.Text == "" && item.Image == "" {
return fmt.Errorf("content block %d, collection '%s', item %d must have at least one of: url, copy-text, text, or image", i, collectionName, j)
}
}
}
}
// Skip theme validation if no themes directory provided
if themesDir == "" {
return nil
}
availableThemes, err := GetAvailableThemes(themesDir)
if err != nil {
return err
}
// If no themes found, skip validation
if len(availableThemes) == 0 {
return nil
}
// Check if the configured theme is available
validThemes := make(map[string]bool)
for _, theme := range availableThemes {
validThemes[theme] = true
}
if !validThemes[c.Theme] {
return errors.New("theme '" + c.Theme + "' not found. Available themes: " + strings.Join(availableThemes, ", "))
}
return nil
}