aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCypher <cypher@server.ky>2023-09-30 16:56:04 -0500
committerCypher <cypher@server.ky>2023-10-02 09:36:52 -0500
commit8f31e6463df59a87ab660e1daa234c24547bca12 (patch)
tree1f42953d0b99ad0045a34874a4775fbf417ca13e
parente62ca945d169a9aa5ced9658f89266697c5fe014 (diff)
downloadlibrefund-master.tar.xz
Simplify rendering html pagesHEADmaster
Improve discovery by moving templates into the http package. Associate the source code for a template by locating it into a file named similar to the template.
-rw-r--r--http/html/about.go55
-rw-r--r--http/html/about.html.tmpl (renamed from asset/template/about.html.tmpl)0
-rw-r--r--http/html/contribute.go129
-rw-r--r--http/html/contribute.html.tmpl (renamed from asset/template/contribute.html.tmpl)0
-rw-r--r--http/html/contribute_bitcoin.go52
-rw-r--r--http/html/contribute_bitcoin.html.tmpl (renamed from asset/template/contribute_bitcoin.html.tmpl)0
-rw-r--r--http/html/documentation.html.tmpl (renamed from asset/template/documentation.html.tmpl)0
-rw-r--r--http/html/header.html.tmpl (renamed from asset/template/header.html.tmpl)0
-rw-r--r--http/html/helper.go50
-rw-r--r--http/html/html.go67
-rw-r--r--http/html/money.html.tmpl (renamed from asset/template/money.html.tmpl)2
-rw-r--r--http/html/objective.go54
-rw-r--r--http/html/objective.html.tmpl (renamed from asset/template/objective.html.tmpl)0
-rw-r--r--http/html/project-back-button.html.tmpl (renamed from asset/template/project-back-button.html.tmpl)0
-rw-r--r--http/html/project-contributions.go59
-rw-r--r--http/html/project-contributions.html.tmpl (renamed from asset/template/project-contributions.html.tmpl)0
-rw-r--r--http/html/project-header.html.tmpl (renamed from asset/template/project-header.html.tmpl)0
-rw-r--r--http/html/project-menu.html.tmpl (renamed from asset/template/project-menu.html.tmpl)0
-rw-r--r--http/html/project-updates.go61
-rw-r--r--http/html/project-updates.html.tmpl (renamed from asset/template/project-updates.html.tmpl)0
-rw-r--r--http/html/project.go103
-rw-r--r--http/html/project.html.tmpl (renamed from asset/template/project.html.tmpl)0
-rw-r--r--http/html/service-agreement.go46
-rw-r--r--http/html/service-agreement.html.tmpl (renamed from asset/template/service-agreement.html.tmpl)0
-rw-r--r--http/html_template.go529
-rw-r--r--http/html_template_test.go15
-rw-r--r--http/http.go58
27 files changed, 706 insertions, 574 deletions
diff --git a/http/html/about.go b/http/html/about.go
new file mode 100644
index 0000000..970d3be
--- /dev/null
+++ b/http/html/about.go
@@ -0,0 +1,55 @@
+package html
+
+import (
+ "fmt"
+ "io"
+
+ "git.server.ky/cypher/librefund"
+ "git.server.ky/cypher/librefund/service"
+)
+
+type About struct {
+ *librefund.Project
+
+ RootURL string
+ Selected string
+ Title string
+
+ TotalContributions int
+ TotalUpdates int
+}
+
+func NewAbout(
+ srv *service.Service,
+ project string,
+) (*About, error) {
+ var ret About
+
+ ret.RootURL = "../.."
+ ret.Selected = "About"
+ ret.Title = fmt.Sprintf("%s | About", project)
+
+ v, err := srv.GetProject(project)
+ if err != nil {
+ return nil, err
+ }
+ ret.Project = v
+
+ total, err := srv.GetProjectContributionsTotal(ret.Project.Name)
+ if err != nil {
+ return nil, err
+ }
+ ret.TotalContributions = total
+
+ total, err = srv.GetProjectUpdatesTotal(ret.Project.Name)
+ if err != nil {
+ return nil, err
+ }
+ ret.TotalUpdates = total
+
+ return &ret, nil
+}
+
+func (r *Renderer) RenderAbout(w io.Writer, arg *About) error {
+ return r.templates.ExecuteTemplate(w, "about.html.tmpl", arg)
+}
diff --git a/asset/template/about.html.tmpl b/http/html/about.html.tmpl
index 80a9e26..80a9e26 100644
--- a/asset/template/about.html.tmpl
+++ b/http/html/about.html.tmpl
diff --git a/http/html/contribute.go b/http/html/contribute.go
new file mode 100644
index 0000000..e8037cf
--- /dev/null
+++ b/http/html/contribute.go
@@ -0,0 +1,129 @@
+package html
+
+import (
+ "fmt"
+ "io"
+
+ "git.server.ky/cypher/librefund"
+ "git.server.ky/cypher/librefund/service"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ "github.com/shopspring/decimal"
+)
+
+type Contribute struct {
+ *librefund.Project
+
+ RootURL string
+ Title string
+
+ Currency string
+ MinContribution *librefund.Money
+ MaxContribution *librefund.Money
+ TransferMethod string
+ SuggestedContribution *librefund.Money
+ SuggestedContributionStep decimal.Decimal
+ SuggestedContributions []*librefund.Money
+
+ ShowTermsAndConditions bool
+ Objective *Objective
+ FieldErrors schema.MultiError
+ Error error
+}
+
+// getSuggestedContribution returns the suggested contribution amount for an objective.
+func getSuggestedContributions(
+ obj *librefund.Objective,
+ conf *librefund.Config,
+ min *librefund.Money,
+ max *librefund.Money,
+) []*librefund.Money {
+ cur := obj.Currency().Code
+
+ var suggestions []*librefund.Money
+ for _, v := range conf.SuggestedContributions[cur] {
+ m, _ := librefund.NewMoneyFromDecimal(v, cur)
+ if v, _ := m.Cmp(max); v > 0 {
+ continue
+ }
+ if v, _ := m.Cmp(min); v < 0 {
+ continue
+ }
+ suggestions = append(suggestions, m)
+ }
+
+ return suggestions
+}
+
+func NewContribute(
+ srv *service.Service,
+ project string,
+ objective librefund.Version,
+) (*Contribute, error) {
+ var ret Contribute
+ ret.RootURL = "../../../"
+
+ p, err := srv.GetProject(project)
+ if err != nil {
+ return nil, err
+ }
+ ret.Project = p
+
+ obj, err := srv.GetObjectiveByVersion(objective)
+ if err != nil {
+ return nil, err
+ }
+ ret.Objective, err = NewObjective(&srv.Config, obj, srv)
+ if err != nil {
+ return nil, err
+ }
+ ret.Title = fmt.Sprintf("%s | %s", project, ret.Objective.Name)
+
+ ret.Currency = obj.Currency().Grapheme
+ ret.MinContribution, err = librefund.MinAllowedContribution(
+ &srv.Config,
+ obj.Currency().Code,
+ )
+ if err != nil {
+ return nil, errors.Wrap(err, "minimum contribution")
+ }
+ ret.MaxContribution, err = librefund.MaxAllowedContribution(
+ &srv.Config,
+ obj,
+ obj.Currency().Code,
+ )
+ if err != nil {
+ return nil, errors.Wrap(err, "maximum contribution")
+ }
+ ret.SuggestedContribution = ret.MaxContribution
+ ret.SuggestedContributionStep = decimal.New(1, int32(-obj.Currency().Fraction))
+ ret.SuggestedContributions = getSuggestedContributions(
+ obj,
+ &srv.Config,
+ ret.MinContribution,
+ ret.MaxContribution,
+ )
+
+ if *srv.Config.ServiceAgreementEnable {
+ ret.ShowTermsAndConditions = true
+ }
+ ret.TransferMethod, err = service.TransferMethodFor(&srv.Config, obj.Currency().Code)
+ if err != nil {
+ return nil, errors.Wrap(err, "transfer method")
+ }
+
+ return &ret, nil
+}
+
+func (t *Contribute) AddError(err error) {
+ switch v := err.(type) {
+ case schema.MultiError:
+ t.FieldErrors = v
+ default:
+ t.Error = v
+ }
+}
+
+func (r *Renderer) RenderContribute(w io.Writer, arg *Contribute) error {
+ return r.templates.ExecuteTemplate(w, "contribute.html.tmpl", arg)
+}
diff --git a/asset/template/contribute.html.tmpl b/http/html/contribute.html.tmpl
index a5fbe25..a5fbe25 100644
--- a/asset/template/contribute.html.tmpl
+++ b/http/html/contribute.html.tmpl
diff --git a/http/html/contribute_bitcoin.go b/http/html/contribute_bitcoin.go
new file mode 100644
index 0000000..eb75b95
--- /dev/null
+++ b/http/html/contribute_bitcoin.go
@@ -0,0 +1,52 @@
+package html
+
+import (
+ "fmt"
+ "html/template"
+ "io"
+
+ "git.server.ky/cypher/librefund"
+ "git.server.ky/cypher/librefund/payment/bitcoin"
+ "git.server.ky/cypher/librefund/service"
+)
+
+type ContributeBitcoin struct {
+ RootURL string
+ Title string
+
+ BitcoinAddress string
+ BitcoinURI template.URL
+ Project *librefund.Project
+ Objective *librefund.Objective
+ PaymentInfo *bitcoin.PaymentInfo
+ PaymentReference string
+}
+
+func NewContributeBitcoin(
+ srv *service.Service,
+ payInfo *bitcoin.PaymentInfo,
+ project string,
+) (*ContributeBitcoin, error) {
+ var ret ContributeBitcoin
+ var err error
+
+ ret.RootURL = ".."
+ ret.Title = fmt.Sprintf("%s | Payment Bitcoin", project)
+ ret.PaymentInfo = payInfo
+ ret.PaymentReference = payInfo.ID
+
+ amount := librefund.Decimal(payInfo.Amount)
+ ret.BitcoinURI = template.URL(bitcoin.NewURI(ret.PaymentInfo.ReceiveAddress, &amount, "", "").String())
+
+ p, err := srv.GetProject(project)
+ if err != nil {
+ return nil, err
+ }
+ ret.Project = p
+
+ return &ret, nil
+}
+
+func (r *Renderer) RenderContributeBitcoin(w io.Writer, arg *ContributeBitcoin) error {
+ return r.templates.ExecuteTemplate(w, "contribute_bitcoin.html.tmpl", arg)
+}
diff --git a/asset/template/contribute_bitcoin.html.tmpl b/http/html/contribute_bitcoin.html.tmpl
index 4814938..4814938 100644
--- a/asset/template/contribute_bitcoin.html.tmpl
+++ b/http/html/contribute_bitcoin.html.tmpl
diff --git a/asset/template/documentation.html.tmpl b/http/html/documentation.html.tmpl
index bb6e76b..bb6e76b 100644
--- a/asset/template/documentation.html.tmpl
+++ b/http/html/documentation.html.tmpl
diff --git a/asset/template/header.html.tmpl b/http/html/header.html.tmpl
index dd03b6f..dd03b6f 100644
--- a/asset/template/header.html.tmpl
+++ b/http/html/header.html.tmpl
diff --git a/http/html/helper.go b/http/html/helper.go
new file mode 100644
index 0000000..9f314da
--- /dev/null
+++ b/http/html/helper.go
@@ -0,0 +1,50 @@
+package html
+
+// Additional methods which can be called within a template.
+
+import (
+ "encoding/base64"
+ "html/template"
+ "strings"
+
+ "git.server.ky/cypher/librefund"
+ "git.server.ky/cypher/librefund/service"
+)
+
+func (r *Renderer) helpers() template.FuncMap {
+ return template.FuncMap{
+ "add": Add,
+ "altCurrency": AltCurrency(r.pricer, r.Config.ShowAlternativeCurrency),
+ "qrcodeURL": r.QRCodeURL,
+ "url": r.urls.GetURL,
+ "toTitle": ToTitle,
+ }
+}
+
+func Add(a, b int) int {
+ return a + b
+}
+
+func AltCurrency(
+ pricer service.QuotePricer,
+ curCode *string,
+) func(*librefund.Money) *librefund.Money {
+ return func(arg *librefund.Money) *librefund.Money {
+ if curCode == nil {
+ return nil
+ }
+ res, _ := pricer.QuotePrice(*curCode, arg)
+ return res
+ }
+}
+
+func ToTitle(arg string) string {
+ title := strings.ReplaceAll(arg, "-", " ")
+ title = strings.Title(title)
+ return title
+}
+
+func (r *Renderer) QRCodeURL(payload template.URL) (string, error) {
+ buf := base64.URLEncoding.EncodeToString([]byte(payload))
+ return r.urls.GetURL("WriteQRCode", "address", buf)
+}
diff --git a/http/html/html.go b/http/html/html.go
new file mode 100644
index 0000000..c9c08f5
--- /dev/null
+++ b/http/html/html.go
@@ -0,0 +1,67 @@
+package html
+
+import (
+ "embed"
+ "html/template"
+
+ "git.server.ky/cypher/librefund"
+ "git.server.ky/cypher/librefund/service"
+ "github.com/pkg/errors"
+)
+
+//go:embed *.tmpl
+var templateFiles embed.FS
+
+type URLGetter interface {
+ GetURL(routeName string, attr ...string) (string, error)
+}
+
+type Renderer struct {
+ Config *librefund.Config
+ templates *template.Template
+
+ pricer service.QuotePricer
+ urls URLGetter
+}
+
+var templates = make(map[string]*string)
+
+func NewRenderer(
+ config *librefund.Config,
+ pricer service.QuotePricer,
+ urls URLGetter,
+) (*Renderer, error) {
+ r := Renderer{
+ Config: config,
+ pricer: pricer,
+ urls: urls,
+ }
+
+ entries, err := templateFiles.ReadDir(".")
+ if err != nil {
+ return nil, errors.Errorf("reading template files: %s", err)
+ }
+
+ helpers := r.helpers()
+ for _, entry := range entries {
+ buf, err := templateFiles.ReadFile(entry.Name())
+ if err != nil {
+ return nil, err
+ }
+
+ // Use the same naming convention as template.ParseFiles
+ tmplName := entry.Name()
+ if r.templates == nil {
+ r.templates = template.New(tmplName)
+ } else {
+ r.templates = r.templates.New(tmplName)
+ }
+
+ _, err = r.templates.Funcs(helpers).Parse(string(buf))
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return &r, nil
+}
diff --git a/asset/template/money.html.tmpl b/http/html/money.html.tmpl
index 8594b7d..d722514 100644
--- a/asset/template/money.html.tmpl
+++ b/http/html/money.html.tmpl
@@ -1,4 +1,4 @@
{{.Display}}
{{with altCurrency .}}
- ({{.Display}})
+ ({{.Display}})
{{end}}
diff --git a/http/html/objective.go b/http/html/objective.go
new file mode 100644
index 0000000..8ffbe42
--- /dev/null
+++ b/http/html/objective.go
@@ -0,0 +1,54 @@
+package html
+
+import (
+ _ "embed"
+ "html/template"
+
+ "git.server.ky/cypher/librefund"
+ "git.server.ky/cypher/librefund/service"
+ "github.com/russross/blackfriday/v2"
+)
+
+var objectiveHtmlTmplName = "objective"
+
+//go:embed objective.html.tmpl
+var objectiveHtmlTmplString string
+
+func init() {
+ templates[objectiveHtmlTmplName] = &objectiveHtmlTmplString
+}
+
+type Objective struct {
+ *librefund.Objective
+ Description template.HTML
+ ShortDescription template.HTML
+ RefundPerc librefund.Percentage
+ ImpactPerc librefund.Percentage
+}
+
+func NewObjective(
+ cfg *librefund.Config,
+ obj *librefund.Objective,
+ quotePricer service.QuotePricer,
+) (*Objective, error) {
+ htmlObj := Objective{
+ Objective: obj,
+ }
+ htmlObj.Description = template.HTML(string(blackfriday.Run([]byte(obj.Description))))
+ htmlObj.ShortDescription = template.HTML(string(blackfriday.Run([]byte(obj.ShortDescription))))
+
+ max, err := librefund.MaxAllowedContribution(
+ cfg,
+ obj,
+ obj.Currency().Code,
+ )
+ if err != nil {
+ return nil, err
+ }
+ htmlObj.ImpactPerc, err = obj.ImpactPerc(max)
+ if err != nil {
+ return nil, err
+ }
+
+ return &htmlObj, nil
+}
diff --git a/asset/template/objective.html.tmpl b/http/html/objective.html.tmpl
index aae46c6..aae46c6 100644
--- a/asset/template/objective.html.tmpl
+++ b/http/html/objective.html.tmpl
diff --git a/asset/template/project-back-button.html.tmpl b/http/html/project-back-button.html.tmpl
index f145ada..f145ada 100644
--- a/asset/template/project-back-button.html.tmpl
+++ b/http/html/project-back-button.html.tmpl
diff --git a/http/html/project-contributions.go b/http/html/project-contributions.go
new file mode 100644
index 0000000..0922d43
--- /dev/null
+++ b/http/html/project-contributions.go
@@ -0,0 +1,59 @@
+package html
+
+import (
+ "fmt"
+ "io"
+ "math"
+
+ "git.server.ky/cypher/librefund"
+ "git.server.ky/cypher/librefund/service"
+)
+
+type ProjectContributions struct {
+ *librefund.Project
+
+ RootURL string
+ Selected string
+ Title string
+
+ Contributions []*librefund.Contribution
+ TotalContributions int
+ TotalUpdates int
+}
+
+func NewProjectContributions(
+ srv *service.Service,
+ project string,
+) (*ProjectContributions, error) {
+ var ret ProjectContributions
+
+ ret.RootURL = "../.."
+ ret.Title = fmt.Sprintf("%s | Contributions", project)
+ ret.Selected = "ProjectContributions"
+
+ proj, err := srv.GetProject(project)
+ if err != nil {
+ return nil, err
+ }
+ ret.Project = proj
+
+ contrs, err := srv.GetProjectContributions(proj.Name, math.MaxInt32, 0)
+ ret.Contributions = contrs
+
+ total, err := srv.GetProjectContributionsTotal(ret.Project.Name)
+ if err != nil {
+ return nil, err
+ }
+ ret.TotalContributions = total
+
+ total, err = srv.GetProjectUpdatesTotal(ret.Project.Name)
+ if err != nil {
+ return nil, err
+ }
+ ret.TotalUpdates = total
+ return &ret, nil
+}
+
+func (r *Renderer) RenderProjectContributions(w io.Writer, arg *ProjectContributions) error {
+ return r.templates.ExecuteTemplate(w, "project-contributions.html.tmpl", arg)
+}
diff --git a/asset/template/project-contributions.html.tmpl b/http/html/project-contributions.html.tmpl
index 28ce775..28ce775 100644
--- a/asset/template/project-contributions.html.tmpl
+++ b/http/html/project-contributions.html.tmpl
diff --git a/asset/template/project-header.html.tmpl b/http/html/project-header.html.tmpl
index 04e4596..04e4596 100644
--- a/asset/template/project-header.html.tmpl
+++ b/http/html/project-header.html.tmpl
diff --git a/asset/template/project-menu.html.tmpl b/http/html/project-menu.html.tmpl
index 7d10437..7d10437 100644
--- a/asset/template/project-menu.html.tmpl
+++ b/http/html/project-menu.html.tmpl
diff --git a/http/html/project-updates.go b/http/html/project-updates.go
new file mode 100644
index 0000000..3e0acdc
--- /dev/null
+++ b/http/html/project-updates.go
@@ -0,0 +1,61 @@
+package html
+
+import (
+ "fmt"
+ "io"
+ "math"
+
+ "git.server.ky/cypher/librefund"
+ "git.server.ky/cypher/librefund/service"
+)
+
+type ProjectUpdates struct {
+ *librefund.Project
+
+ RootURL string
+ Selected string
+ Title string
+
+ TotalContributions int
+ TotalUpdates int
+ Updates []librefund.Update
+}
+
+func NewProjectUpdates(
+ srv *service.Service,
+ project string,
+) (*ProjectUpdates, error) {
+ var ret ProjectUpdates
+ ret.RootURL = "../.."
+ ret.Title = fmt.Sprintf("%s | Updates", project)
+ ret.Selected = "ProjectUpdates"
+
+ p, err := srv.GetProject(project)
+ if err != nil {
+ return nil, err
+ }
+ ret.Project = p
+
+ updates, err := srv.GetProjectUpdates(p.Name, math.MaxInt32, 0)
+ if err != nil {
+ return nil, err
+ }
+ ret.Updates = updates
+
+ total, err := srv.GetProjectContributionsTotal(ret.Project.Name)
+ if err != nil {
+ return nil, err
+ }
+ ret.TotalContributions = total
+
+ total, err = srv.GetProjectUpdatesTotal(ret.Project.Name)
+ if err != nil {
+ return nil, err
+ }
+ ret.TotalUpdates = total
+ return &ret, nil
+}
+
+func (r *Renderer) RenderProjectUpdates(w io.Writer, arg *ProjectUpdates) error {
+ return r.templates.ExecuteTemplate(w, "project-updates.html.tmpl", arg)
+}
diff --git a/asset/template/project-updates.html.tmpl b/http/html/project-updates.html.tmpl
index 1278fe6..1278fe6 100644
--- a/asset/template/project-updates.html.tmpl
+++ b/http/html/project-updates.html.tmpl
diff --git a/http/html/project.go b/http/html/project.go
new file mode 100644
index 0000000..0e42c5e
--- /dev/null
+++ b/http/html/project.go
@@ -0,0 +1,103 @@
+package html
+
+import (
+ _ "embed"
+ "io"
+ "sort"
+ "strings"
+
+ "git.server.ky/cypher/librefund"
+ "git.server.ky/cypher/librefund/service"
+)
+
+type Project struct {
+ *librefund.Project
+
+ RootURL string
+ Selected string
+ Title string
+
+ Contributions []*librefund.Contribution
+ Objectives []*Objective
+ TotalContributions int
+ TotalUpdates int
+ Updates []librefund.Update
+}
+
+type objectivesByName []*librefund.Objective
+
+func (p objectivesByName) Len() int {
+ return len(p)
+}
+
+func (p objectivesByName) Less(i, j int) bool {
+ return strings.Compare(
+ p[i].Name,
+ p[j].Name,
+ ) < 0
+}
+
+func (p objectivesByName) Swap(i, j int) {
+ p[i], p[j] = p[j], p[i]
+}
+
+func NewProject(
+ srv *service.Service,
+ project string,
+) (*Project, error) {
+ var ret Project
+
+ ret.RootURL = ".."
+ ret.Selected = "GetProject"
+ ret.Title = project
+
+ v, err := srv.GetProject(project)
+ if err != nil {
+ return nil, err
+ }
+ ret.Project = v
+
+ c, err := srv.GetProjectContributions(ret.Project.Name, 10, 0)
+ if err != nil {
+ return nil, err
+ }
+ ret.Contributions = c
+
+ total, err := srv.GetProjectContributionsTotal(ret.Project.Name)
+ if err != nil {
+ return nil, err
+ }
+ ret.TotalContributions = total
+
+ total, err = srv.GetProjectUpdatesTotal(ret.Project.Name)
+ if err != nil {
+ return nil, err
+ }
+ ret.TotalUpdates = total
+
+ updates, err := srv.GetProjectUpdates(ret.Project.Name, 10, 0)
+ if err != nil {
+ return nil, err
+ }
+ ret.Updates = updates
+
+ //funding.Project's objective deserve's a special position.
+ var objs []*librefund.Objective
+ for _, v := range ret.Project.Objectives {
+ objs = append(objs, v)
+ }
+ sort.Sort(objectivesByName(objs))
+ for _, v := range objs {
+ obj, err := NewObjective(&srv.Config, v, srv)
+ if err != nil {
+ return nil, err
+ }
+ ret.Objectives = append(ret.Objectives, obj)
+ }
+
+ return &ret, nil
+}
+
+func (r *Renderer) RenderProject(w io.Writer, arg *Project) error {
+ return r.templates.ExecuteTemplate(w, "project.html.tmpl", arg)
+}
diff --git a/asset/template/project.html.tmpl b/http/html/project.html.tmpl
index 877210e..877210e 100644
--- a/asset/template/project.html.tmpl
+++ b/http/html/project.html.tmpl
diff --git a/http/html/service-agreement.go b/http/html/service-agreement.go
new file mode 100644
index 0000000..14a6292
--- /dev/null
+++ b/http/html/service-agreement.go
@@ -0,0 +1,46 @@
+package html
+
+import (
+ "fmt"
+ "html/template"
+ "io"
+
+ "git.server.ky/cypher/librefund"
+ "git.server.ky/cypher/librefund/service"
+)
+
+type ServiceAgreement struct {
+ *librefund.Project
+
+ RootURL string
+ Title string
+
+ TermsAndConditions template.HTML
+}
+
+func NewServiceAgreement(
+ srv *service.Service,
+ project string,
+) (*ServiceAgreement, error) {
+ var page ServiceAgreement
+ var err error
+
+ page.RootURL = "../../"
+ page.Title = "Service Agreement"
+ page.Title = fmt.Sprintf("%s | Service Agreement", project)
+
+ page.Project, err = srv.GetProject(project)
+ if err != nil {
+ return nil, err
+ }
+ serviceAgreement, err := srv.GetTermsAndConditions()
+ if err != nil {
+ return nil, err
+ }
+ page.TermsAndConditions = template.HTML(serviceAgreement)
+ return &page, nil
+}
+
+func (r *Renderer) RenderServiceAgreement(w io.Writer, arg *ServiceAgreement) error {
+ return r.templates.ExecuteTemplate(w, "service-agreement.html.tmpl", arg)
+}
diff --git a/asset/template/service-agreement.html.tmpl b/http/html/service-agreement.html.tmpl
index a75997e..a75997e 100644
--- a/asset/template/service-agreement.html.tmpl
+++ b/http/html/service-agreement.html.tmpl
diff --git a/http/html_template.go b/http/html_template.go
deleted file mode 100644
index c4c22e4..0000000
--- a/http/html_template.go
+++ /dev/null
@@ -1,529 +0,0 @@
-package http
-
-import (
- "fmt"
- "html/template"
- "io"
- "math"
- "path"
- "sort"
- "strings"
-
- "git.server.ky/cypher/librefund"
- "git.server.ky/cypher/librefund/payment/bitcoin"
- "git.server.ky/cypher/librefund/service"
- "github.com/gorilla/schema"
- "github.com/pkg/errors"
- "github.com/russross/blackfriday/v2"
- "github.com/shopspring/decimal"
-)
-
-func AltCurrency(
- pricer service.QuotePricer,
- curCode *string,
-) func(*librefund.Money) *librefund.Money {
- return func(arg *librefund.Money) *librefund.Money {
- if curCode == nil {
- return nil
- }
- res, _ := pricer.QuotePrice(*curCode, arg)
- return res
- }
-}
-
-func ToTitle(arg string) string {
- title := strings.ReplaceAll(arg, "-", " ")
- title = strings.Title(title)
- return title
-}
-
-func initTemplates(cfg *librefund.Config, h *HTTPServer) (*template.Template, error) {
- funcMap := template.FuncMap{
- "add": add,
- "altCurrency": AltCurrency(h.srv, h.srv.Config.ShowAlternativeCurrency),
- "qrcodeURL": h.qrcodeURL,
- "url": h.url,
- "toTitle": ToTitle,
- }
-
- entries, err := librefund.AssetDir.ReadDir("asset/template")
- if err != nil {
- return nil, err
- }
-
- var tmpls *template.Template
- for _, fileInfo := range entries {
- fileName := fileInfo.Name()
- if !strings.HasSuffix(fileName, ".tmpl") {
- continue
- }
-
- f, err := librefund.AssetDir.Open(path.Join("asset/template", fileName))
- if err != nil {
- return nil, err
- }
- defer f.Close()
-
- buf, err := io.ReadAll(f)
- if err != nil {
- return nil, err
- }
-
- // Use the same naming convention as template.ParseFiles
- tmplName := path.Base(fileName)
- if tmpls == nil {
- tmpls = template.New(tmplName).Funcs(funcMap)
- } else {
- tmpls = tmpls.New(fileName)
- }
-
- _, err = tmpls.Parse(string(buf))
- if err != nil {
- return nil, err
- }
- }
- if tmpls == nil {
- return nil, errors.New("no templates found")
- }
-
- return tmpls, nil
-}
-
-type objectivesByName []*librefund.Objective
-
-func (p objectivesByName) Len() int {
- return len(p)
-}
-
-func (p objectivesByName) Less(i, j int) bool {
- return strings.Compare(
- p[i].Name,
- p[j].Name,
- ) < 0
-}
-
-func (p objectivesByName) Swap(i, j int) {
- p[i], p[j] = p[j], p[i]
-}
-
-type tplAboutFields struct {
- *librefund.Project
-
- RootURL string
- Selected string
- Title string
-
- TotalContributions int
- TotalUpdates int
-}
-
-func newTPLAboutFields(
- srv *service.Service,
- project string,
-) (*tplAboutFields, error) {
- var ret tplAboutFields
-
- ret.RootURL = "../.."
- ret.Selected = "About"
- ret.Title = fmt.Sprintf("%s | About", project)
-
- v, err := srv.GetProject(project)
- if err != nil {
- return nil, err
- }
- ret.Project = v
-
- total, err := srv.GetProjectContributionsTotal(ret.Project.Name)
- if err != nil {
- return nil, err
- }
- ret.TotalContributions = total
-
- total, err = srv.GetProjectUpdatesTotal(ret.Project.Name)
- if err != nil {
- return nil, err
- }
- ret.TotalUpdates = total
-
- return &ret, nil
-}
-
-type tplObjective struct {
- *librefund.Objective
- Description template.HTML
- ShortDescription template.HTML
- RefundPerc librefund.Percentage
- ImpactPerc librefund.Percentage
-}
-
-func newTPLObjective(
- cfg *librefund.Config,
- obj *librefund.Objective,
- quotePricer service.QuotePricer,
-) (*tplObjective, error) {
- tObj := tplObjective{
- Objective: obj,
- }
- tObj.Description = template.HTML(string(blackfriday.Run([]byte(obj.Description))))
- tObj.ShortDescription = template.HTML(string(blackfriday.Run([]byte(obj.ShortDescription))))
-
- max, err := librefund.MaxAllowedContribution(
- cfg,
- obj,
- obj.Currency().Code,
- )
- if err != nil {
- return nil, err
- }
- tObj.ImpactPerc, err = obj.ImpactPerc(max)
- if err != nil {
- return nil, err
- }
-
- return &tObj, nil
-}
-
-type tplGetProjectFields struct {
- *librefund.Project
-
- RootURL string
- Selected string
- Title string
-
- Contributions []*librefund.Contribution
- Objectives []*tplObjective
- TotalContributions int
- TotalUpdates int
- Updates []librefund.Update
-}
-
-func newTPLGetProjectFields(
- srv *service.Service,
- project string,
-) (*tplGetProjectFields, error) {
- var ret tplGetProjectFields
-
- ret.RootURL = ".."
- ret.Selected = "GetProject"
- ret.Title = project
-
- v, err := srv.GetProject(project)
- if err != nil {
- return nil, err
- }
- ret.Project = v
-
- c, err := srv.GetProjectContributions(ret.Project.Name, 10, 0)
- if err != nil {
- return nil, err
- }
- ret.Contributions = c
-
- total, err := srv.GetProjectContributionsTotal(ret.Project.Name)
- if err != nil {
- return nil, err
- }
- ret.TotalContributions = total
-
- total, err = srv.GetProjectUpdatesTotal(ret.Project.Name)
- if err != nil {
- return nil, err
- }
- ret.TotalUpdates = total
-
- updates, err := srv.GetProjectUpdates(ret.Project.Name, 10, 0)
- if err != nil {
- return nil, err
- }
- ret.Updates = updates
-
- //funding.Project's objective deserve's a special position.
- var objs []*librefund.Objective
- for _, v := range ret.Project.Objectives {
- objs = append(objs, v)
- }
- sort.Sort(objectivesByName(objs))
- for _, v := range objs {
- obj, err := newTPLObjective(&srv.Config, v, srv)
- if err != nil {
- return nil, err
- }
- ret.Objectives = append(ret.Objectives, obj)
- }
-
- return &ret, nil
-}
-
-type tplGetProjectContributionsFields struct {
- *librefund.Project
-
- RootURL string
- Selected string
- Title string
-
- Contributions []*librefund.Contribution
- TotalContributions int
- TotalUpdates int
-}
-
-func newTPLGetProjectContributionsFields(
- srv *service.Service,
- project string,
-) (*tplGetProjectContributionsFields, error) {
- var ret tplGetProjectContributionsFields
-
- ret.RootURL = "../.."
- ret.Title = fmt.Sprintf("%s | Contributions", project)
- ret.Selected = "ProjectContributions"
-
- proj, err := srv.GetProject(project)
- if err != nil {
- return nil, err
- }
- ret.Project = proj
-
- contrs, err := srv.GetProjectContributions(proj.Name, math.MaxInt32, 0)
- ret.Contributions = contrs
-
- total, err := srv.GetProjectContributionsTotal(ret.Project.Name)
- if err != nil {
- return nil, err
- }
- ret.TotalContributions = total
-
- total, err = srv.GetProjectUpdatesTotal(ret.Project.Name)
- if err != nil {
- return nil, err
- }
- ret.TotalUpdates = total
- return &ret, nil
-}
-
-type tplProjectUpdatesFields struct {
- *librefund.Project
-
- RootURL string
- Selected string
- Title string
-
- TotalContributions int
- TotalUpdates int
- Updates []librefund.Update
-}
-
-func newTPLProjectUpdatesFields(
- srv *service.Service,
- project string,
-) (tplProjectUpdatesFields, error) {
- var ret tplProjectUpdatesFields
- ret.RootURL = "../.."
- ret.Title = fmt.Sprintf("%s | Updates", project)
- ret.Selected = "ProjectUpdates"
-
- p, err := srv.GetProject(project)
- if err != nil {
- return tplProjectUpdatesFields{}, err
- }
- ret.Project = p
-
- updates, err := srv.GetProjectUpdates(p.Name, math.MaxInt32, 0)
- if err != nil {
- return tplProjectUpdatesFields{}, err
- }
- ret.Updates = updates
-
- total, err := srv.GetProjectContributionsTotal(ret.Project.Name)
- if err != nil {
- return tplProjectUpdatesFields{}, err
- }
- ret.TotalContributions = total
-
- total, err = srv.GetProjectUpdatesTotal(ret.Project.Name)
- if err != nil {
- return tplProjectUpdatesFields{}, err
- }
- ret.TotalUpdates = total
- return ret, nil
-}
-
-type tplTermsAndConditionsFields struct {
- *librefund.Project
-
- RootURL string
- Title string
-
- TermsAndConditions template.HTML
-}
-
-func newTPLTermsAndConditionsFields(
- srv *service.Service,
- project string,
-) (tplTermsAndConditionsFields, error) {
- var fields tplTermsAndConditionsFields
- var err error
-
- fields.RootURL = "../../"
- fields.Title = "Service Agreement"
- fields.Title = fmt.Sprintf("%s | Service Agreement", project)
-
- fields.Project, err = srv.GetProject(project)
- if err != nil {
- return tplTermsAndConditionsFields{}, err
- }
- serviceAgreement, err := srv.GetTermsAndConditions()
- if err != nil {
- return tplTermsAndConditionsFields{}, err
- }
- fields.TermsAndConditions = template.HTML(serviceAgreement)
- return fields, nil
-}
-
-type tplContributeFields struct {
- *librefund.Project
-
- RootURL string
- Title string
-
- Currency string
- MinContribution *librefund.Money
- MaxContribution *librefund.Money
- TransferMethod string
- SuggestedContribution *librefund.Money
- SuggestedContributionStep decimal.Decimal
- SuggestedContributions []*librefund.Money
-
- ShowTermsAndConditions bool
- Objective *tplObjective
- FieldErrors schema.MultiError
- Error error
-}
-
-// getSuggestedContribution returns the suggested contribution amount for an objective.
-func getSuggestedContributions(
- obj *librefund.Objective,
- conf *librefund.Config,
- min *librefund.Money,
- max *librefund.Money,
-) []*librefund.Money {
- cur := obj.Currency().Code
-
- var suggestions []*librefund.Money
- for _, v := range conf.SuggestedContributions[cur] {
- m, _ := librefund.NewMoneyFromDecimal(v, cur)
- if v, _ := m.Cmp(max); v > 0 {
- continue
- }
- if v, _ := m.Cmp(min); v < 0 {
- continue
- }
- suggestions = append(suggestions, m)
- }
-
- return suggestions
-}
-
-func newTPLContributeFields(
- srv *service.Service,
- project string,
- objective librefund.Version,
-) (*tplContributeFields, error) {
- var ret tplContributeFields
- ret.RootURL = "../../../"
-
- p, err := srv.GetProject(project)
- if err != nil {
- return nil, err
- }
- ret.Project = p
-
- obj, err := srv.GetObjectiveByVersion(objective)
- if err != nil {
- return nil, err
- }
- ret.Objective, err = newTPLObjective(&srv.Config, obj, srv)
- if err != nil {
- return nil, err
- }
- ret.Title = fmt.Sprintf("%s | %s", project, ret.Objective.Name)
-
- ret.Currency = obj.Currency().Grapheme
- ret.MinContribution, err = librefund.MinAllowedContribution(
- &srv.Config,
- obj.Currency().Code,
- )
- if err != nil {
- return nil, errors.Wrap(err, "minimum contribution")
- }
- ret.MaxContribution, err = librefund.MaxAllowedContribution(
- &srv.Config,
- obj,
- obj.Currency().Code,
- )
- if err != nil {
- return nil, errors.Wrap(err, "maximum contribution")
- }
- ret.SuggestedContribution = ret.MaxContribution
- ret.SuggestedContributionStep = decimal.New(1, int32(-obj.Currency().Fraction))
- ret.SuggestedContributions = getSuggestedContributions(
- obj,
- &srv.Config,
- ret.MinContribution,
- ret.MaxContribution,
- )
-
- if *srv.Config.ServiceAgreementEnable {
- ret.ShowTermsAndConditions = true
- }
- ret.TransferMethod, err = service.TransferMethodFor(&srv.Config, obj.Currency().Code)
- if err != nil {
- return nil, errors.Wrap(err, "transfer method")
- }
-
- return &ret, nil
-}
-
-func (t *tplContributeFields) AddError(err error) {
- switch v := err.(type) {
- case schema.MultiError:
- t.FieldErrors = v
- default:
- t.Error = v
- }
-}
-
-type tplContributeBitcoinFields struct {
- RootURL string
- Title string
-
- BitcoinAddress string
- BitcoinURI template.URL
- Project *librefund.Project
- Objective *librefund.Objective
- PaymentInfo *bitcoin.PaymentInfo
- PaymentReference string
-}
-
-func newTPLContributeBitcoinFields(
- srv *service.Service,
- payInfo *bitcoin.PaymentInfo,
- project string,
-) (*tplContributeBitcoinFields, error) {
- var ret tplContributeBitcoinFields
- var err error
-
- ret.RootURL = ".."
- ret.Title = fmt.Sprintf("%s | Payment Bitcoin", project)
- ret.PaymentInfo = payInfo
- ret.PaymentReference = payInfo.ID
-
- amount := librefund.Decimal(payInfo.Amount)
- ret.BitcoinURI = template.URL(bitcoin.NewURI(ret.PaymentInfo.ReceiveAddress, &amount, "", "").String())
-
- p, err := srv.GetProject(project)
- if err != nil {
- return nil, err
- }
- ret.Project = p
-
- return &ret, nil
-}
diff --git a/http/html_template_test.go b/http/html_template_test.go
deleted file mode 100644
index 1116b33..0000000
--- a/http/html_template_test.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package http
-
-import (
- "testing"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestInitTemplate(t *testing.T) {
- var h HTTPServer
- templates, err := initTemplates(&h)
- require.NoError(t, err)
- tmpl := templates.Lookup("project.html.tmpl")
- require.NotNil(t, tmpl)
-}
diff --git a/http/http.go b/http/http.go
index f1f6f73..986ff94 100644
--- a/http/http.go
+++ b/http/http.go
@@ -13,6 +13,7 @@ import (
"strings"
"git.server.ky/cypher/librefund"
+ "git.server.ky/cypher/librefund/http/html"
"git.server.ky/cypher/librefund/payment/bitcoin"
"git.server.ky/cypher/librefund/service"
"github.com/gorilla/mux"
@@ -33,21 +34,24 @@ type HTTPServer struct {
srv *service.Service
router *mux.Router
- // Templates
- templates *template.Template
+ htmlRenderer *html.Renderer
}
// NewHTTPServer serves the daemon to clients over HTTP.
func NewHTTPService(srv *service.Service) (*HTTPServer, error) {
ret := &HTTPServer{
+ srv: srv,
log: librefund.NewServiceLogger(os.Stderr),
}
- ret.srv = srv
err := ret.initRoutes()
if err != nil {
return nil, err
}
+ ret.htmlRenderer, err = html.NewRenderer(&srv.Config, srv, ret)
+ if err != nil {
+ return nil, err
+ }
return ret, nil
}
@@ -88,7 +92,7 @@ func (h *HTTPServer) SetLogger(log librefund.Logger) {
func (h *HTTPServer) qrcodeURL(payload template.URL) (string, error) {
buf := base64.URLEncoding.EncodeToString([]byte(payload))
- return h.url("WriteQRCode", "address", buf)
+ return h.GetURL("WriteQRCode", "address", buf)
}
func (h *HTTPServer) nonRelativeURL(path string) string {
@@ -96,7 +100,7 @@ func (h *HTTPServer) nonRelativeURL(path string) string {
}
// url returns the URL path for the given route.
-func (h *HTTPServer) url(name string, attrs ...string) (string, error) {
+func (h *HTTPServer) GetURL(name string, attrs ...string) (string, error) {
url, err := h.router.Get(name).URL(attrs...)
if err != nil {
return "", err
@@ -114,10 +118,6 @@ func (h *HTTPServer) initRoutes() error {
h.router = mux.NewRouter()
var err error
- h.templates, err = initTemplates(&h.srv.Config, h)
- if err != nil {
- return err
- }
staticFiles, err := fs.Sub(librefund.AssetDir, "asset")
if err != nil {
@@ -156,7 +156,7 @@ func (h *HTTPServer) GetAbout(
r *http.Request,
) {
v := mux.Vars(r)
- tplFields, err := newTPLAboutFields(
+ page, err := html.NewAbout(
h.srv,
v["project"],
)
@@ -165,7 +165,7 @@ func (h *HTTPServer) GetAbout(
return
}
- err = h.templates.Lookup("about.html.tmpl").Execute(w, tplFields)
+ err = h.htmlRenderer.RenderAbout(w, page)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return
@@ -177,7 +177,7 @@ func (h *HTTPServer) GetProject(
r *http.Request,
) {
v := mux.Vars(r)
- tplFields, err := newTPLGetProjectFields(
+ page, err := html.NewProject(
h.srv,
v["project"],
)
@@ -186,7 +186,7 @@ func (h *HTTPServer) GetProject(
return
}
- err = h.templates.Lookup("project.html.tmpl").Execute(w, tplFields)
+ err = h.htmlRenderer.RenderProject(w, page)
if err != nil {
h.log.Println(err)
return
@@ -198,7 +198,7 @@ func (h *HTTPServer) GetProjectContributions(
r *http.Request,
) {
v := mux.Vars(r)
- tplFields, err := newTPLGetProjectContributionsFields(
+ tplFields, err := html.NewProjectContributions(
h.srv,
v["project"],
)
@@ -206,7 +206,7 @@ func (h *HTTPServer) GetProjectContributions(
h.handleError(w, err)
return
}
- err = h.templates.Lookup("project-contributions.html.tmpl").Execute(w, tplFields)
+ err = h.htmlRenderer.RenderProjectContributions(w, tplFields)
if err != nil {
h.handleError(w, err)
return
@@ -229,7 +229,7 @@ func (h *HTTPServer) GetTransaction(
w.WriteHeader(http.StatusNotFound)
return
}
- redirectTo, err := h.url("GetProject", "project", project)
+ redirectTo, err := h.GetURL("GetProject", "project", project)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
@@ -246,13 +246,13 @@ func (h *HTTPServer) GetProjectUpdates(
v := mux.Vars(r)
project := v["project"]
- tplFields, err := newTPLProjectUpdatesFields(h.srv, project)
+ page, err := html.NewProjectUpdates(h.srv, project)
if err != nil {
h.handleError(w, err)
return
}
- err = h.templates.Lookup("project-updates.html.tmpl").Execute(w, tplFields)
+ err = h.htmlRenderer.RenderProjectUpdates(w, page)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return
@@ -266,12 +266,12 @@ func (h *HTTPServer) GetTermsAndConditions(
v := mux.Vars(r)
project := v["project"]
- tplFields, err := newTPLTermsAndConditionsFields(h.srv, project)
+ page, err := html.NewServiceAgreement(h.srv, project)
if err != nil {
h.handleError(w, err)
return
}
- err = h.templates.Lookup("service-agreement.html.tmpl").Execute(w, tplFields)
+ err = h.htmlRenderer.RenderServiceAgreement(w, page)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return
@@ -316,7 +316,7 @@ func (h *HTTPServer) Contribute(
return
}
- fields, err := newTPLContributeFields(
+ page, err := html.NewContribute(
h.srv,
v["project"],
objVersion,
@@ -325,7 +325,7 @@ func (h *HTTPServer) Contribute(
h.handleError(w, err)
return
}
- err = h.templates.Lookup("contribute.html.tmpl").Execute(w, fields)
+ err = h.htmlRenderer.RenderContribute(w, page)
if err != nil {
h.handleError(w, err)
return
@@ -372,7 +372,7 @@ func (h *HTTPServer) ContributePost(
var details service.ContributionReq
err = schema.NewDecoder().Decode(&details, r.PostForm)
- fulfillmentURL, err := h.url("GetTransaction", "project", project, "transactionID", "TXID")
+ fulfillmentURL, err := h.GetURL("GetTransaction", "project", project, "transactionID", "TXID")
if err != nil {
h.log.Println(err)
return
@@ -391,7 +391,7 @@ func (h *HTTPServer) ContributePost(
if err != nil {
h.log.Println(err)
tplErr := err
- fields, err := newTPLContributeFields(
+ page, err := html.NewContribute(
h.srv,
project,
objVersion,
@@ -400,9 +400,9 @@ func (h *HTTPServer) ContributePost(
h.handleError(w, err)
return
}
- fields.AddError(tplErr)
+ page.AddError(tplErr)
- err = h.templates.Lookup("contribute.html.tmpl").Execute(w, fields)
+ err = h.htmlRenderer.RenderContribute(w, page)
if err != nil {
h.handleError(w, err)
return
@@ -414,7 +414,7 @@ func (h *HTTPServer) ContributePost(
if redirectTo == "" {
// Bitcoin was the initial payment setup. Postpone proper
// integration until Taler is somewhat working.
- redirectTo, _ = h.url("ContributeBitcoin")
+ redirectTo, _ = h.GetURL("ContributeBitcoin")
redirectTo = h.nonRelativeURL(fmt.Sprintf(
"%s?%s=%s&%s=%s",
redirectTo,
@@ -438,12 +438,12 @@ func (h *HTTPServer) ContributeBitcoin(
return
}
- fields, err := newTPLContributeBitcoinFields(h.srv, payInfo, project)
+ page, err := html.NewContributeBitcoin(h.srv, payInfo, project)
if err != nil {
h.handleError(w, err)
return
}
- err = h.templates.Lookup("contribute_bitcoin.html.tmpl").Execute(w, fields)
+ err = h.htmlRenderer.RenderContributeBitcoin(w, page)
if err != nil {
h.handleError(w, err)
return