diff options
author | Vidhu Kant Sharma <vidhukant@vidhukant.com> | 2025-05-06 12:38:32 +0530 |
---|---|---|
committer | Vidhu Kant Sharma <vidhukant@vidhukant.com> | 2025-05-06 12:38:32 +0530 |
commit | 6c49d1ec3153852f0e5d4cff334a1bc0476f9dce (patch) | |
tree | 033bf3d95e5c9f8bd2205591355ddc468d4825ab |
first commitv1.0.0
-rw-r--r-- | LICENSE | 0 | ||||
-rw-r--r-- | README.md | 87 | ||||
-rw-r--r-- | cmd/dump.go | 85 | ||||
-rw-r--r-- | cmd/root.go | 51 | ||||
-rw-r--r-- | cmd/serve.go | 125 | ||||
-rw-r--r-- | db/db.go | 53 | ||||
-rw-r--r-- | go.mod | 28 | ||||
-rw-r--r-- | go.sum | 62 | ||||
-rw-r--r-- | guestbook.toml | 25 | ||||
-rw-r--r-- | main.go | 26 |
10 files changed, 542 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..d7259d7 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# Guestbook + +Standalone guestbook server meant to be paired up with a static website. + +# Usage + +## Configuration + +Edit `/etc/guestbook.toml` + +```toml +# the idea is to have a single database but different +# tables for each guestbook (if any) + +[database] +# Supported - MySQL/MariaDB +username = "guestbook" +password = "guestbookpass" +location = "tcp(127.0.0.1:3306)" +db_name = "guestbook" +params = "charset=utf8mb4&parseTime=True&loc=Local" + +# add as many as you like +# avoid having hyphens in the name +[my_guestbook] +port = 2222 +path = "/sign" +validation_string = "" +success_redirect = "/success" +error_redirect = "/error" +template = """ +{{range .}} +{{print "{{"}} + guestbook_entry( + name=\"{{.Name}}\", + website=\"{{.Website}}\", + message=\"{{.Message}}\" + ) +{{print "}}"}} +{{end}}""" +``` + +## Starting with Systemd + +Then use this Systemd service: + +`/etc/systemd/system/my_guestbook-guestbook.service` + +Replace `my_guestbook` with your guestbook's name (as defined in `/etc/guestbook.toml`), +create more `.service` files for as many guestbooks you want to have. + +``` +[Unit] +Description=Guestbook for my_guestbook +After=network.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +Restart=on-failure +RestartSec=1 +ExecStart=guestbook --guestbook my_guestbook + +[Install] +WantedBy=multi-user.target +``` + +Then, reload the daemon and start the guestbook: + +```fish +systemctl daemon-reload +systemctl enable --now my_guestbook-guestbook +``` + +## Reading entries + +```fish +guestbook dump -g my_guestbook +``` + +## Licence + +Licenced under GNU General Public Licence + +GNU GPL License: [LICENSE](LICENSE) + +Copyright (c) 2025 Vidhu Kant Sharma diff --git a/cmd/dump.go b/cmd/dump.go new file mode 100644 index 0000000..281b7d6 --- /dev/null +++ b/cmd/dump.go @@ -0,0 +1,85 @@ +/* guestbook - Standalone guestbook server for static websites + * Copyright (C) 2025 Vidhu Kant Sharma <vidhukant@vidhukant.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package cmd + +import ( + "fmt" + "log" + "bytes" + "text/template" + + "vidhukant.com/guestbook/db" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var dumpCmd = &cobra.Command{ + Use: "dump", + Short: "Dump the entries into STDOUT", + Long: "Dump the entries into STDOUT", + Run: func(cmd *cobra.Command, args []string) { + guestbookName, err := cmd.Flags().GetString("guestbook") + if err != nil { + log.Fatal(err) + } + + outputTemplate := viper.GetString(guestbookName + ".template") + + conn := db.Connect() + defer conn.Close() + + rows, err := conn.Query(fmt.Sprintf("SELECT name, website, message FROM %s", guestbookName)) + if err != nil { + log.Fatal(err) + } + defer rows.Close() + + + var entries []db.GuestbookEntry + + for rows.Next() { + var entry db.GuestbookEntry + if err := rows.Scan(&entry.Name, &entry.Website, &entry.Message); err != nil { + log.Fatal(err) + } + entries = append(entries, entry) + } + if rows.Err() != nil { + log.Fatal(err) + } + + tmpl, err := template.New("render").Parse(outputTemplate) + if err != nil { + log.Fatal(err) + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, entries); err != nil { + panic(err) + } + + fmt.Println(buf.String()) + }, +} + +func init() { + rootCmd.AddCommand(dumpCmd) + + dumpCmd.Flags().StringP("guestbook", "g", "my_guestbook", "Guestbook name (defined in /etc/guestbook.toml)") +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..2d1e63d --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,51 @@ +/* guestbook - Standalone guestbook server for static websites + * Copyright (C) 2025 Vidhu Kant Sharma <vidhukant@vidhukant.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package cmd + +import ( + "os" + "log" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var rootCmd = &cobra.Command{ + Use: "guestbook", + Version: "v1.0.0", + Short: "Standalone guestbook server for static websites", + Long: "Standalone guestbook server for static websites", +} + +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(func () { + viper.SetConfigName("guestbook") + viper.AddConfigPath("/etc") + + if err := viper.ReadInConfig(); err != nil { + log.Fatal(err) + } + }) +} diff --git a/cmd/serve.go b/cmd/serve.go new file mode 100644 index 0000000..e3d6b36 --- /dev/null +++ b/cmd/serve.go @@ -0,0 +1,125 @@ +/* guestbook - Standalone guestbook server for static websites + * Copyright (C) 2025 Vidhu Kant Sharma <vidhukant@vidhukant.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package cmd + +import ( + "fmt" + "log" + "net/http" + "encoding/json" + + "vidhukant.com/guestbook/db" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var serveCmd = &cobra.Command{ + Use: "serve", + Short: "Start the guestbook server", + Long: "Start the guestbook server", + Run: func(cmd *cobra.Command, args []string) { + guestbookName, err := cmd.Flags().GetString("guestbook") + if err != nil { + log.Fatal(err) + } + + port := viper.GetString(guestbookName + ".port") + validationString := viper.GetString(guestbookName + ".validation_string") + + path := viper.GetString(guestbookName + ".path") + if path == "/" { + log.Fatalf("Setting %s.path to / is not allowed as it may cause issues.", guestbookName) + } + + successRedirect := viper.GetString(guestbookName + ".success_redirect") + if successRedirect == "" { + log.Fatalf("%s.success_redirect cannot be blank.", guestbookName) + } + + errorRedirect := viper.GetString(guestbookName + ".error_redirect") + if errorRedirect == "" { + log.Fatalf("%s.error_redirect cannot be blank.", guestbookName) + } + + + conn := db.Connect() + defer conn.Close() + + _, err = conn.Exec( + fmt.Sprintf( + "CREATE TABLE IF NOT EXISTS %s (" + + "id INT AUTO_INCREMENT PRIMARY KEY," + + "name TEXT," + + "website TEXT," + + "message TEXT" + + ");", + guestbookName, + ), + ) + if err != nil { + log.Fatalf("Error while creating table: %v\n", err) + } + + http.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Redirect(w, r, errorRedirect, http.StatusSeeOther) + return + } + + var data db.GuestbookEntry + err := json.NewDecoder(r.Body).Decode(&data) + if err != nil { + log.Printf("Error occurred while decoding data: %v") + http.Redirect(w, r, errorRedirect, http.StatusSeeOther) + return + } + + if data.Validation != validationString || data.Name == "" || data.Message == "" { + http.Redirect(w, r, errorRedirect, http.StatusSeeOther) + return + } + + stmt, err := conn.Prepare(fmt.Sprintf("INSERT INTO %s (name, website, message) VALUES (?, ?, ?)", guestbookName)) + if err != nil { + log.Printf("Error occurred while preparing statement data: %v\n") + http.Redirect(w, r, errorRedirect, http.StatusSeeOther) + return + } + defer stmt.Close() + + _, err = stmt.Exec(data.Name, data.Website, data.Message) + if err != nil { + log.Printf("Error occurred while saving data: %v\n") + http.Redirect(w, r, errorRedirect, http.StatusSeeOther) + return + } + + http.Redirect(w, r, successRedirect, http.StatusSeeOther) + }) + + log.Printf("Guestbook %s listening to POST %s on PORT %s.\n", guestbookName, path, port) + log.Fatal(http.ListenAndServe(":" + port, nil)) + }, +} + +func init() { + rootCmd.AddCommand(serveCmd) + + serveCmd.Flags().StringP("guestbook", "g", "my_guestbook", "Guestbook name (defined in /etc/guestbook.toml)") +} diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..11b6fd4 --- /dev/null +++ b/db/db.go @@ -0,0 +1,53 @@ +/* guestbook - Standalone guestbook server for static websites + * Copyright (C) 2025 Vidhu Kant Sharma <vidhukant@vidhukant.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package db + +import ( + "fmt" + "log" + "database/sql" + + "github.com/spf13/viper" + _ "github.com/go-sql-driver/mysql" +) + +type GuestbookEntry struct { + Name string `json:"name"` + Website string `json:"website"` + Message string `json:"message"` + Validation string `json:"validation"` +} + +func Connect() *sql.DB { + conn, err := sql.Open( + "mysql", + fmt.Sprintf( + "%s:%s@%s/%s?%s", + viper.GetString("database.username"), + viper.GetString("database.password"), + viper.GetString("database.location"), + viper.GetString("database.db_name"), + viper.GetString("database.params"), + ), + ) + if err != nil { + log.Fatal(err) + } + + return conn +} @@ -0,0 +1,28 @@ +module vidhukant.com/guestbook + +go 1.24.2 + +require ( + github.com/go-sql-driver/mysql v1.9.2 + github.com/spf13/cobra v1.9.1 + github.com/spf13/viper v1.20.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) @@ -0,0 +1,62 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/guestbook.toml b/guestbook.toml new file mode 100644 index 0000000..0a1c7c0 --- /dev/null +++ b/guestbook.toml @@ -0,0 +1,25 @@ +# the idea is to have a single database but different +# tables for each guestbook (if any) +[database] +username = "guestbook" +password = "guestbookpass" +location = "tcp(127.0.0.1:3306)" +db_name = "guestbook" +params = "charset=utf8mb4&parseTime=True&loc=Local" + +[my_guestbook] +port = 2222 +path = "/sign" +validation_string = "" +success_redirect = "/success" +error_redirect = "/error" +template = """ +{{range .}} +{{print "{{"}} + guestbook_entry( + name=\"{{.Name}}\", + website=\"{{.Website}}\", + message=\"{{.Message}}\" + ) +{{print "}}"}} +{{end}}""" @@ -0,0 +1,26 @@ +/* guestbook - Standalone guestbook server for static websites + * Copyright (C) 2025 Vidhu Kant Sharma <vidhukant@vidhukant.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package main + +import ( + "vidhukant.com/guestbook/cmd" +) + +func main() { + cmd.Execute() +} |