diff --git a/config/config.go b/config/config.go index ea8754f..d59543a 100644 --- a/config/config.go +++ b/config/config.go @@ -1,97 +1,99 @@ package config import ( "gopkg.in/ini.v1" ) const ( FileName = "config.ini" ) type ( ServerCfg struct { Port int `ini:"port"` } DatabaseCfg struct { Type string `ini:"type"` User string `ini:"username"` Password string `ini:"password"` Database string `ini:"database"` Host string `ini:"host"` Port int `ini:"port"` } AppCfg struct { SiteName string `ini:"site_name"` Host string `ini:"host"` // Site appearance Theme string `ini:"theme"` JSDisabled bool `ini:"disable_js"` WebFonts bool `ini:"webfonts"` // Users SingleUser bool `ini:"single_user"` OpenRegistration bool `ini:"open_registration"` MinUsernameLen int `ini:"min_username_len"` + MaxBlogs int `ini:"max_blogs"` // Federation Federation bool `ini:"federation"` PublicStats bool `ini:"public_stats"` Private bool `ini:"private"` } Config struct { Server ServerCfg `ini:"server"` Database DatabaseCfg `ini:"database"` App AppCfg `ini:"app"` } ) func New() *Config { return &Config{ Server: ServerCfg{ Port: 8080, }, Database: DatabaseCfg{ Type: "mysql", Host: "localhost", Port: 3306, }, App: AppCfg{ Host: "http://localhost:8080", Theme: "write", WebFonts: true, SingleUser: true, MinUsernameLen: 3, + MaxBlogs: 1, Federation: true, PublicStats: true, }, } } func Load() (*Config, error) { cfg, err := ini.Load(FileName) if err != nil { return nil, err } // Parse INI file uc := &Config{} err = cfg.MapTo(uc) if err != nil { return nil, err } return uc, nil } func Save(uc *Config) error { cfg := ini.Empty() err := ini.ReflectFrom(cfg, uc) if err != nil { return err } return cfg.SaveTo(FileName) } diff --git a/config/funcs.go b/config/funcs.go new file mode 100644 index 0000000..2fc130a --- /dev/null +++ b/config/funcs.go @@ -0,0 +1,17 @@ +package config + +import ( + "strings" +) + +// FriendlyHost returns the app's Host sans any schema +func (ac AppCfg) FriendlyHost() string { + return ac.Host[strings.Index(ac.Host, "://")+len("://"):] +} + +func (ac AppCfg) CanCreateBlogs(currentlyUsed uint64) bool { + if ac.MaxBlogs <= 0 { + return true + } + return int(currentlyUsed) < ac.MaxBlogs +} diff --git a/config/setup.go b/config/setup.go index 5ba2053..a89a148 100644 --- a/config/setup.go +++ b/config/setup.go @@ -1,181 +1,216 @@ package config import ( "fmt" "github.com/fatih/color" "github.com/manifoldco/promptui" "github.com/mitchellh/go-wordwrap" "strconv" ) func Configure() error { c, err := Load() if err != nil { fmt.Println("No configuration yet. Creating new.") c = New() } else { fmt.Println("Configuration loaded.") } title := color.New(color.Bold, color.BgGreen).PrintlnFunc() intro := color.New(color.Bold, color.FgWhite).PrintlnFunc() fmt.Println() intro(" ✍ Write Freely Configuration ✍") fmt.Println() fmt.Println(wordwrap.WrapString(" This quick configuration process will generate the application's config file, "+FileName+".\n\n It validates your input along the way, so you can be sure any future errors aren't caused by a bad configuration. If you'd rather configure your server manually, instead run: writefreely --create-config and edit that file.", 75)) fmt.Println() title(" Server setup ") fmt.Println() - prompt := promptui.Prompt{ - Label: "Local port", - Validate: validatePort, - Default: fmt.Sprintf("%d", c.Server.Port), + tmpls := &promptui.PromptTemplates{ + Success: "{{ . | bold | faint }}: ", } - port, err := prompt.Run() - if err != nil { - return err + selTmpls := &promptui.SelectTemplates{ + Selected: fmt.Sprintf(`{{.Label}} {{ . | faint }}`, promptui.IconGood), } - c.Server.Port, _ = strconv.Atoi(port) // Ignore error, as we've already validated number - prompt = promptui.Prompt{ - Label: "Public-facing host", - Validate: validateDomain, - Default: c.App.Host, + prompt := promptui.Prompt{ + Templates: tmpls, + Label: "Local port", + Validate: validatePort, + Default: fmt.Sprintf("%d", c.Server.Port), } - c.App.Host, err = prompt.Run() + port, err := prompt.Run() if err != nil { return err } + c.Server.Port, _ = strconv.Atoi(port) // Ignore error, as we've already validated number fmt.Println() title(" Database setup ") fmt.Println() prompt = promptui.Prompt{ - Label: "Username", - Validate: validateNonEmpty, - Default: c.Database.User, + Templates: tmpls, + Label: "Username", + Validate: validateNonEmpty, + Default: c.Database.User, } c.Database.User, err = prompt.Run() if err != nil { return err } prompt = promptui.Prompt{ - Label: "Password", - Validate: validateNonEmpty, - Default: c.Database.Password, - Mask: '*', + Templates: tmpls, + Label: "Password", + Validate: validateNonEmpty, + Default: c.Database.Password, + Mask: '*', } c.Database.Password, err = prompt.Run() if err != nil { return err } prompt = promptui.Prompt{ - Label: "Database name", - Validate: validateNonEmpty, - Default: c.Database.Database, + Templates: tmpls, + Label: "Database name", + Validate: validateNonEmpty, + Default: c.Database.Database, } c.Database.Database, err = prompt.Run() if err != nil { return err } prompt = promptui.Prompt{ - Label: "Host", - Validate: validateNonEmpty, - Default: c.Database.Host, + Templates: tmpls, + Label: "Host", + Validate: validateNonEmpty, + Default: c.Database.Host, } c.Database.Host, err = prompt.Run() if err != nil { return err } prompt = promptui.Prompt{ - Label: "Port", - Validate: validatePort, - Default: fmt.Sprintf("%d", c.Database.Port), + Templates: tmpls, + Label: "Port", + Validate: validatePort, + Default: fmt.Sprintf("%d", c.Database.Port), } dbPort, err := prompt.Run() if err != nil { return err } c.Database.Port, _ = strconv.Atoi(dbPort) // Ignore error, as we've already validated number fmt.Println() title(" App setup ") fmt.Println() selPrompt := promptui.Select{ - Label: "Site type", - Items: []string{"Single user", "Multiple users"}, + Templates: selTmpls, + Label: "Site type", + Items: []string{"Single user blog", "Multi-user instance"}, } _, usersType, err := selPrompt.Run() if err != nil { return err } c.App.SingleUser = usersType == "Single user" + // TODO: if c.App.SingleUser { + // prompt for username + // prompt for password + // create blog siteNameLabel := "Instance name" if c.App.SingleUser { siteNameLabel = "Blog name" } prompt = promptui.Prompt{ - Label: siteNameLabel, - Validate: validateNonEmpty, - Default: c.App.SiteName, + Templates: tmpls, + Label: siteNameLabel, + Validate: validateNonEmpty, + Default: c.App.SiteName, } c.App.SiteName, err = prompt.Run() if err != nil { return err } + prompt = promptui.Prompt{ + Templates: tmpls, + Label: "Public URL", + Validate: validateDomain, + Default: c.App.Host, + } + c.App.Host, err = prompt.Run() + if err != nil { + return err + } + if !c.App.SingleUser { selPrompt = promptui.Select{ - Label: "Registration", - Items: []string{"Open", "Closed"}, + Templates: selTmpls, + Label: "Registration", + Items: []string{"Open", "Closed"}, } _, regType, err := selPrompt.Run() if err != nil { return err } c.App.OpenRegistration = regType == "Open" + + prompt = promptui.Prompt{ + Templates: tmpls, + Label: "Max blogs per user", + Default: fmt.Sprintf("%d", c.App.MaxBlogs), + } + maxBlogs, err := prompt.Run() + if err != nil { + return err + } + c.App.MaxBlogs, _ = strconv.Atoi(maxBlogs) // Ignore error, as we've already validated number } selPrompt = promptui.Select{ - Label: "Federation", - Items: []string{"Enabled", "Disabled"}, + Templates: selTmpls, + Label: "Federation", + Items: []string{"Enabled", "Disabled"}, } _, fedType, err := selPrompt.Run() if err != nil { return err } c.App.Federation = fedType == "Enabled" if c.App.Federation { selPrompt = promptui.Select{ - Label: "Federation usage stats", - Items: []string{"Public", "Private"}, + Templates: selTmpls, + Label: "Federation usage stats", + Items: []string{"Public", "Private"}, } _, fedStatsType, err := selPrompt.Run() if err != nil { return err } c.App.PublicStats = fedStatsType == "Public" selPrompt = promptui.Select{ - Label: "Instance metadata privacy", - Items: []string{"Public", "Private"}, + Templates: selTmpls, + Label: "Instance metadata privacy", + Items: []string{"Public", "Private"}, } _, fedStatsType, err = selPrompt.Run() if err != nil { return err } c.App.Private = fedStatsType == "Private" } return Save(c) }