├── .gitignore ├── app ├── views │ ├── footer.html │ ├── Application │ │ ├── Index.html │ │ └── Login.html │ ├── errors │ │ ├── 500.html │ │ └── 404.html │ └── header.html ├── models │ ├── db │ │ └── mongo.go │ └── user │ │ └── user.go └── controllers │ └── app.go ├── README.md ├── tests └── apptest.go ├── messages └── sample.en ├── conf ├── routes └── app.conf └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | test-results/ 2 | tmp/ 3 | -------------------------------------------------------------------------------- /app/views/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/views/Application/Index.html: -------------------------------------------------------------------------------- 1 | {{set . "title" "Home"}} 2 | {{template "header.html" .}} 3 | 4 |

Welcome to BYVNotes

5 | 6 | {{template "footer.html" .}} 7 | 8 | -------------------------------------------------------------------------------- /app/views/errors/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Application error 5 | 6 | 7 | {{if eq .RunMode "dev"}} 8 | {{template "errors/500-dev.html" .}} 9 | {{else}} 10 |

Oops, an error occured

11 |

12 | This exception has been logged. 13 |

14 | {{end}} 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/views/errors/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Not found 5 | 6 | 7 | {{if eq .RunMode "dev"}} 8 | {{template "errors/404-dev.html" .}} 9 | {{else}} 10 | {{with .Error}} 11 |

12 | {{.Title}} 13 |

14 |

15 | {{.Description}} 16 |

17 | {{end}} 18 | {{end}} 19 | 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BYVNotes 2 | 3 | BYVNotes is very simple and elegant editor for you to write articles with Markdown. 4 | 5 | # How to run 6 | 7 | Firstly, have `git` and `bzr` installed. 8 | 9 | go get labix.org/v2/mgo 10 | go get github.com/robfig/revel 11 | go build -o bin/revel github.com/robfig/revel/cmd 12 | revel run byvnotes 13 | 14 | # License 15 | 16 | MIT License 17 | -------------------------------------------------------------------------------- /app/models/db/mongo.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "labix.org/v2/mgo" 5 | //"labix.org/v2/mgo/bson" 6 | ) 7 | 8 | var mongo *mgo.Session = nil 9 | 10 | func DB() (*mgo.Database, error) { 11 | if mongo == nil { 12 | var err error 13 | mongo, err = mgo.Dial("localhost") 14 | if err != nil { 15 | return nil, err 16 | } 17 | mongo.SetMode(mgo.Monotonic, true) 18 | } 19 | return mongo.DB("byvnotes"), nil 20 | } 21 | -------------------------------------------------------------------------------- /tests/apptest.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import "github.com/robfig/revel" 4 | 5 | type ApplicationTest struct { 6 | revel.TestSuite 7 | } 8 | 9 | func (t ApplicationTest) Before() { 10 | println("Set up") 11 | } 12 | 13 | func (t ApplicationTest) TestThatIndexPageWorks() { 14 | t.Get("/") 15 | t.AssertOk() 16 | t.AssertContentType("text/html") 17 | } 18 | 19 | func (t ApplicationTest) After() { 20 | println("Tear down") 21 | } 22 | -------------------------------------------------------------------------------- /messages/sample.en: -------------------------------------------------------------------------------- 1 | # Sample messages file for the English language (en) 2 | # Message file extensions should be ISO 639-1 codes (http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) 3 | # Sections within each message file can optionally override the defaults using ISO 3166-1 alpha-2 codes (http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) 4 | # See also: 5 | # - http://www.rfc-editor.org/rfc/bcp/bcp47.txt 6 | # - http://www.w3.org/International/questions/qa-accept-lang-locales 7 | 8 | -------------------------------------------------------------------------------- /app/views/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{.title}} 5 | 6 | 7 | 8 | 9 | {{range .moreStyles}} 10 | 11 | {{end}} 12 | {{range .moreScripts}} 13 | 14 | {{end}} 15 | 16 | 17 | -------------------------------------------------------------------------------- /conf/routes: -------------------------------------------------------------------------------- 1 | # Routes 2 | # This file defines all application routes (Higher priority routes first) 3 | # ~~~~ 4 | 5 | GET / Application.Index 6 | GET /login Application.Login 7 | POST /login Application.DoLogin 8 | GET /logout Application.Logout 9 | GET /settings Application.Settings 10 | POST /settings Application.EditSettings 11 | 12 | # Ignore favicon requests 13 | GET /favicon.ico 404 14 | 15 | # Map static resources from the /app/public folder to the /public path 16 | GET /public/{<.+>filepath} Static.Serve("public") 17 | 18 | # Catch all 19 | * /{controller}/{action} {controller}.{action} 20 | -------------------------------------------------------------------------------- /app/views/Application/Login.html: -------------------------------------------------------------------------------- 1 | {{set . "title" "Settings"}} 2 | {{template "header.html" .}} 3 | 4 |

Login

5 | 6 |
7 | {{with $field := field "username" .}} 8 |

9 | Username: 10 | * 11 | {{$field.Error}} 12 |

13 | {{end}} 14 | {{with $field := field "password" .}} 15 |

16 | Password: * 17 | {{$field.Error}} 18 |

19 | {{end}} 20 |

21 | Cancel 22 |

23 |
24 | 25 | {{template "footer.html" .}} 26 | 27 | -------------------------------------------------------------------------------- /conf/app.conf: -------------------------------------------------------------------------------- 1 | app.name=byvnotes 2 | app.secret=bPlNFGdSC2wd8f2QnFhk5A84JJjKWZdKH9H2FHFuvUs9Jz8UvBHv3Vc5awx39ivu 3 | http.addr= 4 | http.port=9000 5 | cookie.prefix=REVEL 6 | format.date=01/02/2006 7 | format.datetime=01/02/2006 15:04 8 | 9 | # The default language of this application. 10 | i18n.default_language=en 11 | 12 | module.static=github.com/robfig/revel/modules/static 13 | 14 | [dev] 15 | mode.dev=true 16 | results.pretty=true 17 | results.staging=true 18 | watch=true 19 | 20 | module.testrunner = github.com/robfig/revel/modules/testrunner 21 | 22 | log.trace.output = off 23 | log.info.output = stderr 24 | log.warn.output = stderr 25 | log.error.output = stderr 26 | 27 | [prod] 28 | mode.dev=false 29 | results.pretty=false 30 | results.staging=false 31 | watch=false 32 | 33 | module.testrunner = 34 | 35 | log.trace.output = off 36 | log.info.output = off 37 | log.warn.output = %(app.name)s.log 38 | log.error.output = %(app.name)s.log 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Carbo KUO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/controllers/app.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/robfig/revel" 5 | "byvnotes/app/models/user" 6 | ) 7 | 8 | type Application struct { 9 | *revel.Controller 10 | } 11 | 12 | func (ctl Application) checkUser() revel.Result { 13 | if _, ok := ctl.Session["user"]; !ok { 14 | if ctl.Action != "Application.Login" && ctl.Action != "Application.DoLogin" { 15 | ctl.Flash.Error("Please log in first") 16 | return ctl.Redirect(Application.Login) 17 | } 18 | } 19 | return nil 20 | } 21 | 22 | func init() { 23 | revel.InterceptMethod(Application.checkUser, revel.BEFORE) 24 | } 25 | 26 | func (ctl Application) Index() revel.Result { 27 | return ctl.Render() 28 | } 29 | 30 | func (ctl Application) Login() revel.Result { 31 | return ctl.Render() 32 | } 33 | 34 | func (ctl Application) DoLogin(username string, password string) revel.Result { 35 | count, err := user.Count() 36 | if err != nil { 37 | return ctl.RenderError(err) 38 | } 39 | var userInst *user.User = nil 40 | if count > 0 { 41 | userInst, err = user.Get(username) 42 | ctl.Validation.Required(userInst != nil).Message("User does not exist").Key("username") 43 | if userInst != nil { 44 | ctl.Validation.Required(userInst.CheckPassword(password)).Message("Password is wrong").Key("password") 45 | } 46 | } else { 47 | // If this is the first user, create it 48 | userInst = &user.User{} 49 | userInst.Username = username 50 | userInst.Password = password 51 | userInst.Validate(ctl.Validation) 52 | } 53 | if ctl.Validation.HasErrors() { 54 | ctl.Validation.Keep() 55 | ctl.FlashParams() 56 | return ctl.Redirect(Application.Login) 57 | } 58 | if count == 0 { 59 | // Save first created user 60 | err := userInst.Save() 61 | if err != nil { 62 | return ctl.RenderError(err) 63 | } 64 | } 65 | ctl.Session["user"] = userInst.Username 66 | ctl.Flash.Success("Welcome, " + userInst.Username) 67 | return ctl.Redirect(Application.Index) 68 | } 69 | 70 | func (ctl Application) Logout() revel.Result { 71 | for key := range ctl.Session { 72 | delete(ctl.Session, key) 73 | } 74 | return ctl.Redirect(Application.Index) 75 | } 76 | 77 | func (ctl Application) Settings() revel.Result { 78 | return ctl.Render() 79 | } 80 | 81 | func (ctl Application) EditSettings() revel.Result { 82 | // to be implemented 83 | ctl.Flash.Success("Settings saved") 84 | return ctl.Redirect(Application.Settings) 85 | } 86 | -------------------------------------------------------------------------------- /app/models/user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "fmt" 5 | "github.com/robfig/revel" 6 | "labix.org/v2/mgo" 7 | "labix.org/v2/mgo/bson" 8 | "regexp" 9 | "crypto/sha256" 10 | "io" 11 | "encoding/base64" 12 | "byvnotes/app/models/db" 13 | ) 14 | 15 | type User struct { 16 | Username string 17 | Password string 18 | } 19 | 20 | func collection() (*mgo.Collection, error) { 21 | mongo, err := db.DB() 22 | if err != nil { 23 | return nil, err 24 | } 25 | collection := mongo.C("users") 26 | index := mgo.Index{ 27 | Key: []string{"username"}, 28 | Unique: true, 29 | DropDups: true, 30 | Background: true, 31 | Sparse: true, 32 | } 33 | err = collection.EnsureIndex(index) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return collection, nil 38 | } 39 | 40 | func Get(username string) (*User, error) { 41 | collection, err := collection() 42 | if err != nil { 43 | return nil, err 44 | } 45 | user := User{} 46 | err = collection.Find(bson.M{"username": username}).One(&user) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return &user, nil 51 | } 52 | 53 | func Count() (int, error) { 54 | collection, err := collection() 55 | if err != nil { 56 | return 0, err 57 | } 58 | return collection.Count() 59 | } 60 | 61 | func (u *User) String() string { 62 | return fmt.Sprintf("User(%s)", u.Username) 63 | } 64 | 65 | var userRegex = regexp.MustCompile("^\\w*$") 66 | 67 | func (user *User) Validate(v *revel.Validation) { 68 | v.Check(user.Username, 69 | revel.Required{}, 70 | revel.MaxSize{15}, 71 | revel.MinSize{3}, 72 | revel.Match{userRegex}, 73 | ).Key("username") 74 | v.Check(user.Password, 75 | revel.Required{}, 76 | revel.MaxSize{15}, 77 | revel.MinSize{3}, 78 | ).Key("password") 79 | } 80 | 81 | func (user *User) CheckPassword(password string) bool { 82 | password = getPasswordHash(password) 83 | return user.Password == password 84 | } 85 | 86 | func (user *User) Save() error { 87 | collection, err := collection() 88 | if err != nil { 89 | return err 90 | } 91 | // Generate hash checksum of password 92 | user.Password = getPasswordHash(user.Password) 93 | err = collection.Insert(user) 94 | if err != nil { 95 | return err 96 | } 97 | return nil 98 | } 99 | 100 | func getPasswordHash(password string) string { 101 | hash := sha256.New() 102 | io.WriteString(hash, password) 103 | return base64.StdEncoding.EncodeToString(hash.Sum(nil)) 104 | } 105 | --------------------------------------------------------------------------------