A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

226 lines
5.4 KiB

  1. package writefreely
  2. import (
  3. "database/sql"
  4. "flag"
  5. "fmt"
  6. _ "github.com/go-sql-driver/mysql"
  7. "net/http"
  8. "os"
  9. "os/signal"
  10. "regexp"
  11. "syscall"
  12. "github.com/gorilla/mux"
  13. "github.com/gorilla/schema"
  14. "github.com/gorilla/sessions"
  15. "github.com/writeas/web-core/converter"
  16. "github.com/writeas/web-core/log"
  17. "github.com/writeas/writefreely/config"
  18. "github.com/writeas/writefreely/page"
  19. )
  20. const (
  21. staticDir = "static/"
  22. assumedTitleLen = 80
  23. postsPerPage = 10
  24. serverSoftware = "Write Freely"
  25. softwareURL = "https://writefreely.org"
  26. softwareVer = "0.1"
  27. )
  28. var (
  29. debugging bool
  30. // DEPRECATED VARS
  31. // TODO: pass app.cfg into GetCollection* calls so we can get these values
  32. // from Collection methods and we no longer need these.
  33. hostName string
  34. isSingleUser bool
  35. )
  36. type app struct {
  37. router *mux.Router
  38. db *datastore
  39. cfg *config.Config
  40. keys *keychain
  41. sessionStore *sessions.CookieStore
  42. formDecoder *schema.Decoder
  43. }
  44. // handleViewHome shows page at root path. Will be the Pad if logged in and the
  45. // catch-all landing page otherwise.
  46. func handleViewHome(app *app, w http.ResponseWriter, r *http.Request) error {
  47. if app.cfg.App.SingleUser {
  48. // Render blog index
  49. return handleViewCollection(app, w, r)
  50. }
  51. // Multi-user instance
  52. u := getUserSession(app, r)
  53. if u != nil {
  54. // User is logged in, so show the Pad
  55. return handleViewPad(app, w, r)
  56. }
  57. // Show landing page
  58. return renderPage(w, "landing.tmpl", pageForReq(app, r))
  59. }
  60. func pageForReq(app *app, r *http.Request) page.StaticPage {
  61. p := page.StaticPage{
  62. AppCfg: app.cfg.App,
  63. Path: r.URL.Path,
  64. Version: "v" + softwareVer,
  65. }
  66. // Add user information, if given
  67. var u *User
  68. accessToken := r.FormValue("t")
  69. if accessToken != "" {
  70. userID := app.db.GetUserID(accessToken)
  71. if userID != -1 {
  72. var err error
  73. u, err = app.db.GetUserByID(userID)
  74. if err == nil {
  75. p.Username = u.Username
  76. }
  77. }
  78. } else {
  79. u = getUserSession(app, r)
  80. if u != nil {
  81. p.Username = u.Username
  82. }
  83. }
  84. return p
  85. }
  86. var shttp = http.NewServeMux()
  87. var fileRegex = regexp.MustCompile("/([^/]*\\.[^/]*)$")
  88. func Serve() {
  89. debugPtr := flag.Bool("debug", false, "Enables debug logging.")
  90. createConfig := flag.Bool("create-config", false, "Creates a basic configuration and exits")
  91. doConfig := flag.Bool("config", false, "Run the configuration process")
  92. flag.Parse()
  93. debugging = *debugPtr
  94. if *createConfig {
  95. log.Info("Creating configuration...")
  96. c := config.New()
  97. log.Info("Saving configuration...")
  98. err := config.Save(c)
  99. if err != nil {
  100. log.Error("Unable to save configuration: %v", err)
  101. os.Exit(1)
  102. }
  103. os.Exit(0)
  104. } else if *doConfig {
  105. err := config.Configure()
  106. if err != nil {
  107. log.Error("Unable to configure: %v", err)
  108. os.Exit(1)
  109. }
  110. os.Exit(0)
  111. }
  112. log.Info("Initializing...")
  113. log.Info("Loading configuration...")
  114. cfg, err := config.Load()
  115. if err != nil {
  116. log.Error("Unable to load configuration: %v", err)
  117. os.Exit(1)
  118. }
  119. app := &app{
  120. cfg: cfg,
  121. }
  122. hostName = cfg.App.Host
  123. isSingleUser = cfg.App.SingleUser
  124. app.cfg.Server.Dev = *debugPtr
  125. initTemplates()
  126. // Load keys
  127. log.Info("Loading encryption keys...")
  128. err = initKeys(app)
  129. if err != nil {
  130. log.Error("\n%s\n", err)
  131. }
  132. // Initialize modules
  133. app.sessionStore = initSession(app)
  134. app.formDecoder = schema.NewDecoder()
  135. app.formDecoder.RegisterConverter(converter.NullJSONString{}, converter.ConvertJSONNullString)
  136. app.formDecoder.RegisterConverter(converter.NullJSONBool{}, converter.ConvertJSONNullBool)
  137. app.formDecoder.RegisterConverter(sql.NullString{}, converter.ConvertSQLNullString)
  138. app.formDecoder.RegisterConverter(sql.NullBool{}, converter.ConvertSQLNullBool)
  139. app.formDecoder.RegisterConverter(sql.NullInt64{}, converter.ConvertSQLNullInt64)
  140. app.formDecoder.RegisterConverter(sql.NullFloat64{}, converter.ConvertSQLNullFloat64)
  141. // Check database configuration
  142. if app.cfg.Database.User == "" || app.cfg.Database.Password == "" {
  143. log.Error("Database user or password not set.")
  144. os.Exit(1)
  145. }
  146. if app.cfg.Database.Host == "" {
  147. app.cfg.Database.Host = "localhost"
  148. }
  149. if app.cfg.Database.Database == "" {
  150. app.cfg.Database.Database = "writeas"
  151. }
  152. log.Info("Connecting to database...")
  153. db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true", app.cfg.Database.User, app.cfg.Database.Password, app.cfg.Database.Host, app.cfg.Database.Port, app.cfg.Database.Database))
  154. if err != nil {
  155. log.Error("\n%s\n", err)
  156. os.Exit(1)
  157. }
  158. app.db = &datastore{db}
  159. defer shutdown(app)
  160. app.db.SetMaxOpenConns(50)
  161. r := mux.NewRouter()
  162. handler := NewHandler(app)
  163. handler.SetErrorPages(&ErrorPages{
  164. NotFound: pages["404-general.tmpl"],
  165. Gone: pages["410.tmpl"],
  166. InternalServerError: pages["500.tmpl"],
  167. Blank: pages["blank.tmpl"],
  168. })
  169. // Handle app routes
  170. initRoutes(handler, r, app.cfg, app.db)
  171. // Handle static files
  172. fs := http.FileServer(http.Dir(staticDir))
  173. shttp.Handle("/", fs)
  174. r.PathPrefix("/").Handler(fs)
  175. // Handle shutdown
  176. c := make(chan os.Signal, 2)
  177. signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  178. go func() {
  179. <-c
  180. log.Info("Shutting down...")
  181. shutdown(app)
  182. log.Info("Done.")
  183. os.Exit(0)
  184. }()
  185. // Start web application server
  186. http.Handle("/", r)
  187. log.Info("Serving on http://localhost:%d\n", app.cfg.Server.Port)
  188. log.Info("---")
  189. http.ListenAndServe(fmt.Sprintf(":%d", app.cfg.Server.Port), nil)
  190. }
  191. func shutdown(app *app) {
  192. log.Info("Closing database connection...")
  193. app.db.Close()
  194. }