A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 
 

274 satır
6.5 KiB

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