diff options
author | Cypher <cypher@server.ky> | 2023-09-30 16:56:04 -0500 |
---|---|---|
committer | Cypher <cypher@server.ky> | 2023-10-02 09:36:52 -0500 |
commit | 8f31e6463df59a87ab660e1daa234c24547bca12 (patch) | |
tree | 1f42953d0b99ad0045a34874a4775fbf417ca13e | |
parent | e62ca945d169a9aa5ced9658f89266697c5fe014 (diff) | |
download | librefund-master.tar.xz |
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.go | 55 | ||||
-rw-r--r-- | http/html/about.html.tmpl (renamed from asset/template/about.html.tmpl) | 0 | ||||
-rw-r--r-- | http/html/contribute.go | 129 | ||||
-rw-r--r-- | http/html/contribute.html.tmpl (renamed from asset/template/contribute.html.tmpl) | 0 | ||||
-rw-r--r-- | http/html/contribute_bitcoin.go | 52 | ||||
-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.go | 50 | ||||
-rw-r--r-- | http/html/html.go | 67 | ||||
-rw-r--r-- | http/html/money.html.tmpl (renamed from asset/template/money.html.tmpl) | 2 | ||||
-rw-r--r-- | http/html/objective.go | 54 | ||||
-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.go | 59 | ||||
-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.go | 61 | ||||
-rw-r--r-- | http/html/project-updates.html.tmpl (renamed from asset/template/project-updates.html.tmpl) | 0 | ||||
-rw-r--r-- | http/html/project.go | 103 | ||||
-rw-r--r-- | http/html/project.html.tmpl (renamed from asset/template/project.html.tmpl) | 0 | ||||
-rw-r--r-- | http/html/service-agreement.go | 46 | ||||
-rw-r--r-- | http/html/service-agreement.html.tmpl (renamed from asset/template/service-agreement.html.tmpl) | 0 | ||||
-rw-r--r-- | http/html_template.go | 529 | ||||
-rw-r--r-- | http/html_template_test.go | 15 | ||||
-rw-r--r-- | http/http.go | 58 |
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 |