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}}
+ {{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;
+}
+