diff --git a/app/controllers/helpers.go b/app/controllers/helpers.go new file mode 100644 index 0000000..3186d3a --- /dev/null +++ b/app/controllers/helpers.go @@ -0,0 +1,19 @@ +package controllers + +import ( + "github.com/robfig/revel" + "html/template" + "time" +) + +func init() { + rev.TRACE.Println("Registering helpers") + rev.Funcs["friendlyTime"] = func(t time.Time) template.HTML { + ty, tm, td := t.Date() + ny, nm, nd := time.Now().Date() + if (ty == ny) && (tm == nm) && (td == nd) { + return template.HTML(t.Format("03:04:05 PM")) + } + return template.HTML(t.Format("Mon Jan 2, 2006")) + } +} diff --git a/app/controllers/mailbox.go b/app/controllers/mailbox.go index 671e400..02ba4be 100644 --- a/app/controllers/mailbox.go +++ b/app/controllers/mailbox.go @@ -10,22 +10,34 @@ type Mailbox struct { } func (c Mailbox) Index(name string) rev.Result { + c.Validation.Required(name).Message("Account name is required") + + if c.Validation.HasErrors() { + c.Validation.Keep() + c.FlashParams() + return c.Redirect(Application.Index) + } + return c.Render(name) } func (c Mailbox) List(name string) rev.Result { + c.Validation.Required(name).Message("Account name is required") + + if c.Validation.HasErrors() { + c.Validation.Keep() + c.FlashParams() + return c.Redirect(Application.Index) + } + ds := inbucket.NewDataStore() mb, err := ds.MailboxFor(name) if err != nil { - rev.ERROR.Printf(err.Error()) - c.Flash.Error(err.Error()) - return c.Redirect(Application.Index) + return c.RenderError(err) } messages, err := mb.GetMessages() if err != nil { - rev.ERROR.Printf(err.Error()) - c.Flash.Error(err.Error()) - return c.Redirect(Application.Index) + return c.RenderError(err) } rev.INFO.Printf("Got %v messsages", len(messages)) @@ -34,24 +46,27 @@ func (c Mailbox) List(name string) rev.Result { } func (c Mailbox) Show(name string, id string) rev.Result { + c.Validation.Required(name).Message("Account name is required") + c.Validation.Required(id).Message("Message ID is required") + + if c.Validation.HasErrors() { + c.Validation.Keep() + c.FlashParams() + return c.Redirect(Application.Index) + } + ds := inbucket.NewDataStore() mb, err := ds.MailboxFor(name) if err != nil { - rev.ERROR.Printf(err.Error()) - c.Flash.Error(err.Error()) - return c.Redirect(Application.Index) + return c.RenderError(err) } message, err := mb.GetMessage(id) if err != nil { - rev.ERROR.Printf(err.Error()) - c.Flash.Error(err.Error()) - return c.Redirect(Application.Index) + return c.RenderError(err) } _, body, err := message.ReadBody() if err != nil { - rev.ERROR.Printf(err.Error()) - c.Flash.Error(err.Error()) - return c.Redirect(Application.Index) + return c.RenderError(err) } c.Response.Out.Header().Set("Expires", "-1") @@ -59,47 +74,53 @@ func (c Mailbox) Show(name string, id string) rev.Result { } func (c Mailbox) Delete(name string, id string) rev.Result { + c.Validation.Required(name).Message("Account name is required") + c.Validation.Required(id).Message("Message ID is required") + + if c.Validation.HasErrors() { + c.Validation.Keep() + c.FlashParams() + return c.Redirect(Application.Index) + } + ds := inbucket.NewDataStore() mb, err := ds.MailboxFor(name) if err != nil { - rev.ERROR.Printf(err.Error()) - c.Flash.Error(err.Error()) - return c.Redirect(Application.Index) + return c.RenderError(err) } message, err := mb.GetMessage(id) if err != nil { - rev.ERROR.Printf(err.Error()) - c.Flash.Error(err.Error()) - return c.Redirect(Application.Index) + return c.RenderError(err) } err = message.Delete() if err != nil { - rev.ERROR.Printf(err.Error()) - c.Flash.Error(err.Error()) - return c.Redirect(Application.Index) + return c.RenderError(err) } return c.RenderText("OK") } func (c Mailbox) Source(name string, id string) rev.Result { + c.Validation.Required(name).Message("Account name is required") + c.Validation.Required(id).Message("Message ID is required") + + if c.Validation.HasErrors() { + c.Validation.Keep() + c.FlashParams() + return c.Redirect(Application.Index) + } + ds := inbucket.NewDataStore() mb, err := ds.MailboxFor(name) if err != nil { - rev.ERROR.Printf(err.Error()) - c.Flash.Error(err.Error()) - return c.Redirect(Application.Index) + return c.RenderError(err) } message, err := mb.GetMessage(id) if err != nil { - rev.ERROR.Printf(err.Error()) - c.Flash.Error(err.Error()) - return c.Redirect(Application.Index) + return c.RenderError(err) } raw, err := message.ReadRaw() if err != nil { - rev.ERROR.Printf(err.Error()) - c.Flash.Error(err.Error()) - return c.Redirect(Application.Index) + return c.RenderError(err) } c.Response.Out.Header().Set("Expires", "-1") diff --git a/app/views/Application/Index.html b/app/views/Application/Index.html index 2f603c1..466ec39 100644 --- a/app/views/Application/Index.html +++ b/app/views/Application/Index.html @@ -3,6 +3,8 @@ {{template "menu.html" .}}
+ {{template "errors.html" .}} +

Inbucket is an email testing service; it will accept email for any email address and make it available to view without a password.

To view email for a particular address, enter the username portion diff --git a/app/views/Mailbox/Index.html b/app/views/Mailbox/Index.html index dd48e39..800cf25 100644 --- a/app/views/Mailbox/Index.html +++ b/app/views/Mailbox/Index.html @@ -3,54 +3,54 @@ {{template "header.html" .}} {{$name := .name}}

@@ -59,12 +59,14 @@

mail for {{.name}}

- Refresh List + Refresh List
+ {{template "errors.html" .}} +

Select a message at left, or enter a different username into the box on upper right.

diff --git a/app/views/Mailbox/List.html b/app/views/Mailbox/List.html index f991cec..49a656e 100644 --- a/app/views/Mailbox/List.html +++ b/app/views/Mailbox/List.html @@ -1,9 +1,9 @@ {{$name := .name}} {{range .messages}}
- +
{{.Subject}}
{{.From}}
-
{{.Date}}
+
{{friendlyTime .Date}}
{{else}}
diff --git a/app/views/errors.html b/app/views/errors.html new file mode 100644 index 0000000..eae587d --- /dev/null +++ b/app/views/errors.html @@ -0,0 +1,10 @@ +{{if .errors}} +
+

Please fix the following errors and resubmit:

+

    + {{range .errors}} +
  • {{.Message}}
  • + {{end}} +
+
+{{end}} diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css index 4d18b70..0264dc1 100644 --- a/public/stylesheets/main.css +++ b/public/stylesheets/main.css @@ -2,305 +2,312 @@ Design by Free CSS Templates http://www.freecsstemplates.org Released for free under a Creative Commons Attribution 2.5 License -*/ + */ /* Elements */ body { - margin: 20px 0; - font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; - font-size: 12px; - color: #808080; + margin: 20px 0; + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + font-size: 12px; + color: #808080; } h1 { } h2, h3, h4, h5, h6 { - color: #2582A4; + color: #2582A4; } p, ol, ul { - text-align: justify; + text-align: justify; } strong { - font-weight: bold; - color: #808080; + font-weight: bold; + color: #808080; } a { - color: #87A019; + color: #87A019; } a:hover { - text-decoration: none; - color: #2582A4; + text-decoration: none; + color: #2582A4; } .box { - margin-bottom: 0; - padding-bottom: 0; - background: url(/public/images/img07.gif) repeat-x left bottom; + margin-bottom: 0; + padding-bottom: 0; + background: url(/public/images/img07.gif) repeat-x left bottom; } .bottom { - display: block; - margin-bottom: 0; - padding-bottom: 10px; - background: url(/public/images/img08.gif) no-repeat left bottom; + display: block; + margin-bottom: 0; + padding-bottom: 10px; + background: url(/public/images/img08.gif) no-repeat left bottom; } .image { - float: left; - margin: 3px 0 0 0; - padding: 0; + float: left; + margin: 3px 0 0 0; + padding: 0; } .indent { - margin-left: 125px; + margin-left: 125px; } /* Header */ #header { - width: 928px; - height: 37px; - margin: 0 auto; - background: #BECF74 url(/public/images/img01.gif) repeat-x; - border-bottom: 3px solid #FFFFFF; + width: 928px; + height: 37px; + margin: 0 auto; + background: #BECF74 url(/public/images/img01.gif) repeat-x; + border-bottom: 3px solid #FFFFFF; } /* Menu */ #menu { - float: left; - width: 600px; - height: 37px; - margin: 0; - padding: 0 0 0 10px; - list-style: none; - background: url(/public/images/img02.gif) no-repeat; + float: left; + width: 600px; + height: 37px; + margin: 0; + padding: 0 0 0 10px; + list-style: none; + background: url(/public/images/img02.gif) no-repeat; } #menu li { - display: inline; + display: inline; } #menu a { - display: block; - float: left; - height: 27px; - padding: 10px 15px 0px 15px; - text-transform: lowercase; - text-decoration: none; - font-size: 12px; - font-weight: bold; - color: #FFFFFF; + display: block; + float: left; + height: 27px; + padding: 10px 15px 0px 15px; + text-transform: lowercase; + text-decoration: none; + font-size: 12px; + font-weight: bold; + color: #FFFFFF; } #menu a:hover { - background: #A4B74C url(/public/images/img04.gif) repeat-x; + background: #A4B74C url(/public/images/img04.gif) repeat-x; } /* Search */ #search { - float: right; - height: 28px; - margin: 0; - padding: 9px 25px 0 0; - background: url(/public/images/img03.gif) no-repeat right top; + float: right; + height: 28px; + margin: 0; + padding: 9px 25px 0 0; + background: url(/public/images/img03.gif) no-repeat right top; } #search fieldset { - display: inline; - margin: 0; - padding: 0; - border: none; + display: inline; + margin: 0; + padding: 0; + border: none; } #input1 { - width: 164px; - padding: 2px 5px; - background: #FFFFFF; - border: none; + width: 164px; + padding: 2px 5px; + background: #FFFFFF; + border: none; } #input2 { - height: 20px; - background: #87C4DB; - border: none; - text-transform: lowercase; - font-size: 10px; - font-weight: bold; - color: #FFFFFF; + height: 20px; + background: #87C4DB; + border: none; + text-transform: lowercase; + font-size: 10px; + font-weight: bold; + color: #FFFFFF; } /* Content */ #content { - width: 928px; - margin: 0 auto; + width: 928px; + margin: 0 auto; } #colOne { - float: left; - width: 238px; - background: url(/public/images/img05.gif) repeat-x; + float: left; + width: 238px; + background: url(/public/images/img05.gif) repeat-x; } #colOne h3 { - padding: 0 20px; + padding: 0 20px; } #colOne ul { - margin-left: 0; - padding-left: 20px; - padding-right: 20px; - list-style: none; + margin-left: 0; + padding-left: 20px; + padding-right: 20px; + list-style: none; } #colOne li { - padding: 5px 0; - border-top: 1px solid #EFEFEF; + padding: 5px 0; + border-top: 1px solid #EFEFEF; } #colOne li.first { - border: none; + border: none; } #colTwo { - float: right; - width: 617px; - padding: 30px 40px 0 30px; - background: url(/public/images/img09.gif) repeat-x; + float: right; + width: 617px; + padding: 30px 40px 0 30px; + background: url(/public/images/img09.gif) repeat-x; } #colTwo h2 { - margin-top: 0; - font-size: 26px; + margin-top: 0; + font-size: 26px; } #colTwo h3 { - margin-bottom: 0; - font-size: 18px; + margin-bottom: 0; + font-size: 18px; } #colTwo h4 { - margin-top: 0; - font-size: 11px; - font-weight: normal; + margin-top: 0; + font-size: 11px; + font-weight: normal; } #colTwo .box { - margin: 0 -30px 30px -20px; - padding: 0 30px 0 20px; + margin: 0 -30px 30px -20px; + padding: 0 30px 0 20px; } #colTwo .bottom { - margin: 0 0 20px -20px; - padding: 0 0 20px 20px; + margin: 0 0 20px -20px; + padding: 0 0 20px 20px; } /* Logo */ #logo { - height: 150px; - background: url(/public/images/img06.gif) no-repeat; + height: 150px; + background: url(/public/images/img06.gif) no-repeat; } #logo h1, #logo h2, #logo a { - margin: 0; - padding: 0; - text-decoration: none; - text-transform: lowercase; - text-align: center; - color: #FFFFFF; + margin: 0; + padding: 0; + text-decoration: none; + text-transform: lowercase; + text-align: center; + color: #FFFFFF; } #logo h1 { - padding-top: 25px; - font-size: 34px; + padding-top: 25px; + font-size: 34px; } #logo h2 { - margin-top: -5px; - font-size: 12px; + margin-top: -5px; + font-size: 12px; } /* Footer */ #footer { - clear: both; - width: 928px; - margin: 0 auto; - padding-top: 40px; - background: url(/public/images/img11.gif) repeat-x; + clear: both; + width: 928px; + margin: 0 auto; + padding-top: 40px; + background: url(/public/images/img11.gif) repeat-x; } #footer p { - margin: 0; - text-align: center; + margin: 0; + text-align: center; } .listEntry { - color: #909090; - padding: 5px; + color: #909090; + padding: 5px; } .listEntry > .subject { - color: #2582A4; - font-weight: bold; + color: #2582A4; + font-weight: bold; } .listEntrySelected { - background: #becf74; - color: #666; + background: #becf74; + color: #666; } .listEntryHover { - background: #8ac6dc; - color: #666; + background: #8ac6dc; + color: #666; } #emailContent { - padding-bottom: 20px; - min-height: 300px; + padding-bottom: 20px; + min-height: 300px; } #emailHeader { - border-collapse: collapse; + border-collapse: collapse; } #emailHeader th, #emailHeader td { - text-align: left; - padding: 0 3px 3px 0; + text-align: left; + padding: 0 3px 3px 0; } #emailSubject { - border-bottom: 1px #606060 solid; - margin: 0; - width: 617px; + border-bottom: 1px #606060 solid; + margin: 0; + width: 617px; } #emailBody { - color: #000000; + color: #000000; } #emailActions { - padding: 5px 0; - margin: 0 0 10px 0; + padding: 5px 0; + margin: 0 0 10px 0; } #emailActions a { - background: #8ac6dc; - color: #fff; - text-decoration: none; - font-weight: bold; - padding: 5px; + background: #8ac6dc; + color: #fff; + text-decoration: none; + font-weight: bold; + padding: 5px; } #emailActions a:hover { - background: #becf74; + background: #becf74; } + +.errors { + background-color: #ffa0a0; + color: #333; + padding: 5px 10px; +} +