diff --git a/api/index.go b/api/index.go
index 28b54e9..9308ae3 100644
--- a/api/index.go
+++ b/api/index.go
@@ -4,14 +4,24 @@ import (
"fmt"
"log"
"net/http"
+ "os"
"scribly/cmd/site"
+ "strings"
"github.com/kelseyhightower/envconfig"
)
var router http.Handler
+func url() string {
+ url := os.Getenv("VERCEL_PROJECT_PRODUCTION_URL")
+ url, _ = strings.CutPrefix(url, "www.")
+ return "https://" + url
+}
+
func init() {
+ os.Setenv("SITE_URL", url())
+
cfg := site.Config{}
err := envconfig.Process("", &cfg)
if err != nil {
diff --git a/cmd/config.go b/cmd/config.go
index 5952c0e..c7f0984 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -14,6 +14,7 @@ type Config struct {
DatabaseURL string `envconfig:"database_url" default:"postgres://scribly:pass@localhost/scribly?sslmode=disable"`
ResendBaseURL string `envconfig:"resend_base_url" default:"https://api.resend.com"`
ResendAPIKey string `envconfig:"resend_api_key" default:"test_resend_api_key"`
+ SiteURL string `envconfig:"site_url"`
Debug bool
}
@@ -34,7 +35,7 @@ func (c *Config) MakeScribly() (*internal.Scribly, error) {
resend := internal.NewResendClient(c.ResendBaseURL, c.ResendAPIKey)
messageGateway := internal.GoroutineMessageGateway{}
- scribly, err := internal.NewScribly(db, resend, &messageGateway)
+ scribly, err := internal.NewScribly(db, resend, &messageGateway, c.SiteURL)
if err != nil {
return nil, err
}
diff --git a/cmd/site/main.go b/cmd/site/main.go
index 27918af..b50bf0e 100644
--- a/cmd/site/main.go
+++ b/cmd/site/main.go
@@ -55,7 +55,7 @@ func MakeRouter(cfg Config) (http.Handler, error) {
errorPage := func(w http.ResponseWriter, r *http.Request, e error) error {
fmt.Printf("error on request %s: %s\n", r.URL.String(), e.Error())
- return embedded.WebTemplates.ExecuteTemplate(w, "exception.tmpl", ViewData{e, r, "Uh Oh!", ""})
+ return embedded.WebTemplates.ExecuteTemplate(w, "exception.tmpl", NewViewData(r, cfg, WithData(e), WithTitle("Uh Oh!")))
}
router.HandleFunc("/exception", func(w http.ResponseWriter, r *http.Request) {
@@ -69,7 +69,7 @@ func MakeRouter(cfg Config) (http.Handler, error) {
return
}
- if err := embedded.WebTemplates.ExecuteTemplate(w, "index.tmpl", ViewData{nil, r, "", ""}); err != nil {
+ if err := embedded.WebTemplates.ExecuteTemplate(w, "index.tmpl", NewViewData(r, cfg)); err != nil {
errorPage(w, r, err)
}
}).Methods("GET")
@@ -80,7 +80,7 @@ func MakeRouter(cfg Config) (http.Handler, error) {
return
}
- if err := embedded.WebTemplates.ExecuteTemplate(w, "login.tmpl", ViewData{nil, r, "Scribly - Log in", ""}); err != nil {
+ if err := embedded.WebTemplates.ExecuteTemplate(w, "login.tmpl", NewViewData(r, cfg, WithTitle("Scribly - Log in"))); err != nil {
errorPage(w, r, err)
}
}).Methods("GET")
@@ -118,7 +118,7 @@ func MakeRouter(cfg Config) (http.Handler, error) {
}).Methods("POST")
router.HandleFunc("/signup", func(w http.ResponseWriter, r *http.Request) {
- if err := embedded.WebTemplates.ExecuteTemplate(w, "signup.tmpl", ViewData{nil, r, "Scribly - Sign up", ""}); err != nil {
+ if err := embedded.WebTemplates.ExecuteTemplate(w, "signup.tmpl", NewViewData(r, cfg, WithTitle("Scribly - Sign up"))); err != nil {
errorPage(w, r, err)
}
}).Methods("GET")
@@ -173,7 +173,7 @@ func MakeRouter(cfg Config) (http.Handler, error) {
return
}
- if err := embedded.WebTemplates.ExecuteTemplate(w, "me.tmpl", ViewData{me, r, "", ""}); err != nil {
+ if err := embedded.WebTemplates.ExecuteTemplate(w, "me.tmpl", NewViewData(r, cfg, WithData(me))); err != nil {
errorPage(w, r, err)
return
}
@@ -200,20 +200,19 @@ func MakeRouter(cfg Config) (http.Handler, error) {
switch story.Story.State {
case internal.StoryStateDraft:
userSuggestions, _ := scribly.UserSuggestions(r.Context(), *user)
- viewData := ViewData{
- Data: map[string]interface{}{
+ viewData := NewViewData(r, cfg,
+ WithData(map[string]interface{}{
"UserStory": story,
"UserSuggestions": userSuggestions,
- },
- Request: r,
- title: fmt.Sprintf("%s - Add Cowriters", story.Story.Title),
- }
+ }),
+ WithTitle(fmt.Sprintf("%s - Add Cowriters", story.Story.Title)),
+ )
if err := embedded.WebTemplates.ExecuteTemplate(w, "addpeopletostory.tmpl", viewData); err != nil {
errorPage(w, r, err)
return
}
default:
- if err := embedded.WebTemplates.ExecuteTemplate(w, "story.tmpl", ViewData{story, r, fmt.Sprintf("Scribly - %s", story.Story.Title), ""}); err != nil {
+ if err := embedded.WebTemplates.ExecuteTemplate(w, "story.tmpl", NewViewData(r, cfg, WithData(story), WithTitle(fmt.Sprintf("Scribly - %s", story.Story.Title)))); err != nil {
errorPage(w, r, err)
return
}
@@ -286,7 +285,7 @@ func MakeRouter(cfg Config) (http.Handler, error) {
return
}
- if err := embedded.WebTemplates.ExecuteTemplate(w, "newstory.tmpl", ViewData{nil, r, "Scribly - New Story", "page page-tall"}); err != nil {
+ if err := embedded.WebTemplates.ExecuteTemplate(w, "newstory.tmpl", NewViewData(r, cfg, WithTitle("Scribly - New Story"), WithPageClass("page page-tall"))); err != nil {
errorPage(w, r, err)
return
}
@@ -381,14 +380,13 @@ func MakeRouter(cfg Config) (http.Handler, error) {
return
}
- data := ViewData{
- Request: r,
- Data: map[string]interface{}{
+ data := NewViewData(r, cfg,
+ WithData(map[string]interface{}{
"User": user,
"StoryID": storyID,
- },
- title: "Nudge delivered",
- }
+ }),
+ WithTitle("Nudge delivered"),
+ )
if err := embedded.WebTemplates.ExecuteTemplate(w, "nudged.tmpl", data); err != nil {
errorPage(w, r, err)
return
@@ -408,7 +406,7 @@ func MakeRouter(cfg Config) (http.Handler, error) {
return
}
- if err := embedded.WebTemplates.ExecuteTemplate(w, "emailverificationrequested.tmpl", ViewData{user, r, "Email Verification Sent", ""}); err != nil {
+ if err := embedded.WebTemplates.ExecuteTemplate(w, "emailverificationrequested.tmpl", NewViewData(r, cfg, WithData(user), WithTitle("Email Verification Sent"))); err != nil {
errorPage(w, r, err)
return
}
@@ -428,7 +426,7 @@ func MakeRouter(cfg Config) (http.Handler, error) {
return
}
- if err := embedded.WebTemplates.ExecuteTemplate(w, "emailverificationsuccess.tmpl", ViewData{user, r, "Email Verified!", ""}); err != nil {
+ if err := embedded.WebTemplates.ExecuteTemplate(w, "emailverificationsuccess.tmpl", NewViewData(r, cfg, WithData(user), WithTitle("Email Verified!"))); err != nil {
errorPage(w, r, err)
return
}
diff --git a/cmd/site/template.go b/cmd/site/template.go
index 64c7da2..d81f64b 100644
--- a/cmd/site/template.go
+++ b/cmd/site/template.go
@@ -15,6 +15,35 @@ type ViewData struct {
Request *http.Request
title string
pageClass string
+ cfg Config
+}
+
+type ViewDataOption func(*ViewData)
+
+func NewViewData(r *http.Request, cfg Config, opts ...ViewDataOption) ViewData {
+ vd := ViewData{Request: r, cfg: cfg}
+ for _, opt := range opts {
+ opt(&vd)
+ }
+ return vd
+}
+
+func WithData(data interface{}) ViewDataOption {
+ return func(vd *ViewData) {
+ vd.Data = data
+ }
+}
+
+func WithTitle(title string) ViewDataOption {
+ return func(vd *ViewData) {
+ vd.title = title
+ }
+}
+
+func WithPageClass(pageClass string) ViewDataOption {
+ return func(vd *ViewData) {
+ vd.pageClass = pageClass
+ }
}
// v *ViewData doesn't seem to work. i guess go templates don't have the same
@@ -35,6 +64,10 @@ func (v ViewData) Title() string {
return v.title
}
+func (v ViewData) SiteURL() string {
+ return v.cfg.SiteURL
+}
+
func (v ViewData) PageClass() string {
if v.pageClass == "" {
return "page"
@@ -80,6 +113,7 @@ func (v ViewData) Propogate(data interface{}) ViewData {
return ViewData{
Data: data,
Request: v.Request,
+ cfg: v.cfg,
}
}
diff --git a/docker-compose.yml b/docker-compose.yml
index c43d48f..d0b885f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,7 +11,7 @@ services:
- "./static:/project/static"
environment:
DATABASE_URL: postgres://scribly:pass@db/scribly?sslmode=disable
- WEBSITE_URL: http://127.0.0.1:8000
+ SITE_URL: http://127.0.0.1:8000
RESEND_BASE_URL: http://mockresend:1080
command: >
bash -c "
diff --git a/embed/templates/web/layout.tmpl b/embed/templates/web/layout.tmpl
index c74a489..9ca1c4f 100644
--- a/embed/templates/web/layout.tmpl
+++ b/embed/templates/web/layout.tmpl
@@ -14,7 +14,7 @@
-
+
diff --git a/internal/emails.go b/internal/emails.go
index e57e987..89ba50a 100644
--- a/internal/emails.go
+++ b/internal/emails.go
@@ -4,25 +4,39 @@ import (
"bytes"
"fmt"
"html/template"
- "os"
"scribly/embed"
"strings"
"github.com/vanng822/go-premailer/premailer"
)
-var websiteURL = os.Getenv("WEBSITE_URL")
-
type viewData struct {
- Data interface{}
+ Data interface{}
+ websiteURL string
+}
+
+type viewDataOption func(*viewData)
+
+func newViewData(websiteURL string, opts ...viewDataOption) viewData {
+ vd := viewData{websiteURL: websiteURL}
+ for _, opt := range opts {
+ opt(&vd)
+ }
+ return vd
+}
+
+func withData(data interface{}) viewDataOption {
+ return func(vd *viewData) {
+ vd.Data = data
+ }
}
func (v viewData) StoryLink(story Story) template.HTML {
- return template.HTML(fmt.Sprintf(`%s`, websiteURL, story.ID, story.Title))
+ return template.HTML(fmt.Sprintf(`%s`, v.websiteURL, story.ID, story.Title))
}
func (v viewData) WebsiteURL() string {
- return websiteURL
+ return v.websiteURL
}
func (v viewData) Replace(original string, pattern string, replacement string) string {
@@ -45,13 +59,13 @@ func (v viewData) CSS() template.HTML {
return template.HTML("")
}
-func BuildNudgeEmail(nudger User, nudgee User, story Story) (*Email, error) {
+func BuildNudgeEmail(websiteURL string, nudger User, nudgee User, story Story) (*Email, error) {
subject := fmt.Sprintf("%s nudged you to take your turn on %s", nudger.Username, story.Title)
data := map[string]interface{}{
"Story": story,
"Nudger": nudger,
}
- body, err := renderTemplateWithCSS("nudge.tmpl", data)
+ body, err := renderTemplateWithCSS("nudge.tmpl", websiteURL, data)
if err != nil {
return nil, err
}
@@ -63,7 +77,7 @@ func BuildNudgeEmail(nudger User, nudgee User, story Story) (*Email, error) {
}, nil
}
-func BuildAddedToStoryEmails(story Story) ([]Email, error) {
+func BuildAddedToStoryEmails(websiteURL string, story Story) ([]Email, error) {
var recipients []User
for _, cowriter := range story.Cowriters {
if cowriter.User.ID != story.CreatedByID && cowriter.User.EmailVerificationStatus == EmailVerificationStateVerified {
@@ -77,7 +91,7 @@ func BuildAddedToStoryEmails(story Story) ([]Email, error) {
"Story": story,
"Recipient": recipient,
}
- body, err := renderTemplateWithCSS("addedtostory.tmpl", data)
+ body, err := renderTemplateWithCSS("addedtostory.tmpl", websiteURL, data)
if err != nil {
return nil, err
}
@@ -91,7 +105,7 @@ func BuildAddedToStoryEmails(story Story) ([]Email, error) {
return emails, nil
}
-func BuildTurnNotificationEmails(story Story, turnNumber int) ([]Email, error) {
+func BuildTurnNotificationEmails(websiteURL string, story Story, turnNumber int) ([]Email, error) {
turn := story.Turns[turnNumber-1]
var recipients []User
for _, cowriter := range story.Cowriters {
@@ -108,7 +122,7 @@ func BuildTurnNotificationEmails(story Story, turnNumber int) ([]Email, error) {
"Turn": &turn,
"Recipient": recipient,
}
- body, err := renderTemplateWithCSS("storyturnnotification.tmpl", data)
+ body, err := renderTemplateWithCSS("storyturnnotification.tmpl", websiteURL, data)
if err != nil {
return nil, err
}
@@ -134,12 +148,12 @@ func BuildTurnNotificationEmails(story Story, turnNumber int) ([]Email, error) {
return emails, nil
}
-func BuildEmailVerificationEmail(user User, token string) (*Email, error) {
+func BuildEmailVerificationEmail(websiteURL string, user User, token string) (*Email, error) {
data := map[string]interface{}{
"Recipient": user,
"VerificationToken": token,
}
- body, err := renderTemplateWithCSS("verification.tmpl", data)
+ body, err := renderTemplateWithCSS("verification.tmpl", websiteURL, data)
if err != nil {
return nil, err
}
@@ -151,10 +165,10 @@ func BuildEmailVerificationEmail(user User, token string) (*Email, error) {
}, nil
}
-func renderTemplateWithCSS(templateName string, data interface{}) (string, error) {
+func renderTemplateWithCSS(templateName string, websiteURL string, data interface{}) (string, error) {
var buffer bytes.Buffer
- viewData := viewData{Data: data}
- if err := embed.EmailTemplates.ExecuteTemplate(&buffer, templateName, viewData); err != nil {
+ vd := newViewData(websiteURL, withData(data))
+ if err := embed.EmailTemplates.ExecuteTemplate(&buffer, templateName, vd); err != nil {
return "", err
}
prem, err := premailer.NewPremailerFromBytes(buffer.Bytes(), premailer.NewOptions())
diff --git a/internal/scribly.go b/internal/scribly.go
index de8abb7..ca48ef2 100644
--- a/internal/scribly.go
+++ b/internal/scribly.go
@@ -14,6 +14,7 @@ type Scribly struct {
db *bun.DB
emailer EmailGateway
messageGateway MessageGateway
+ websiteURL string
}
func (s *Scribly) LogIn(ctx context.Context, input LoginInput) (*User, error) {
@@ -271,7 +272,7 @@ func (s *Scribly) Nudge(ctx context.Context, nudger User, nudgeeID int, storyID
return err
}
- email, err := BuildNudgeEmail(nudger, nudgee, story)
+ email, err := BuildNudgeEmail(s.websiteURL, nudger, nudgee, story)
if err != nil {
return err
}
@@ -292,7 +293,7 @@ func (s *Scribly) SendAddedToStoryEmails(ctx context.Context, storyID int) error
return err
}
- emails, err := BuildAddedToStoryEmails(story)
+ emails, err := BuildAddedToStoryEmails(s.websiteURL, story)
if err != nil {
return err
}
@@ -318,7 +319,7 @@ func (s *Scribly) SendTurnEmailNotifications(ctx context.Context, storyID int, t
return err
}
- emails, err := BuildTurnNotificationEmails(story, turnNumber)
+ emails, err := BuildTurnNotificationEmails(s.websiteURL, story, turnNumber)
if err != nil {
return err
}
@@ -347,7 +348,7 @@ func (s *Scribly) SendVerificationEmail(ctx context.Context, userID int) error {
return err
}
- email, err := BuildEmailVerificationEmail(user, verificationToken)
+ email, err := BuildEmailVerificationEmail(s.websiteURL, user, verificationToken)
if err != nil {
return err
}
@@ -377,10 +378,11 @@ func (s *Scribly) VerifyEmail(ctx context.Context, user User, token string) erro
return nil
}
-func NewScribly(db *bun.DB, emailer EmailGateway, messageGateway MessageGateway) (*Scribly, error) {
+func NewScribly(db *bun.DB, emailer EmailGateway, messageGateway MessageGateway, websiteURL string) (*Scribly, error) {
return &Scribly{
db: db,
emailer: emailer,
messageGateway: messageGateway,
+ websiteURL: websiteURL,
}, nil
}