Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions api/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
}
Expand Down
40 changes: 19 additions & 21 deletions cmd/site/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down
34 changes: 34 additions & 0 deletions cmd/site/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -80,6 +113,7 @@ func (v ViewData) Propogate(data interface{}) ViewData {
return ViewData{
Data: data,
Request: v.Request,
cfg: v.cfg,
}
}

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
2 changes: 1 addition & 1 deletion embed/templates/web/layout.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<meta property="og:description" content="{{$description}}" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Scribly" />
<meta property="og:image" content="/static/og-image.png" />
<meta property="og:image" content="{{.SiteURL}}/static/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
Expand Down
48 changes: 31 additions & 17 deletions internal/emails.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(`<a href="%s/stories/%d">%s</a>`, websiteURL, story.ID, story.Title))
return template.HTML(fmt.Sprintf(`<a href="%s/stories/%d">%s</a>`, 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 {
Expand All @@ -45,13 +59,13 @@ func (v viewData) CSS() template.HTML {
return template.HTML("<style>" + embed.CSS + "</style>")
}

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
}
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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())
Expand Down
Loading
Loading