A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 
 

293 righe
6.8 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.2"
  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. genKeys := flag.Bool("gen-keys", false, "Generate encryption and authentication keys")
  111. flag.Parse()
  112. debugging = *debugPtr
  113. app := &app{}
  114. if *createConfig {
  115. log.Info("Creating configuration...")
  116. c := config.New()
  117. log.Info("Saving configuration...")
  118. err := config.Save(c)
  119. if err != nil {
  120. log.Error("Unable to save configuration: %v", err)
  121. os.Exit(1)
  122. }
  123. os.Exit(0)
  124. } else if *doConfig {
  125. d, err := config.Configure()
  126. if err != nil {
  127. log.Error("Unable to configure: %v", err)
  128. os.Exit(1)
  129. }
  130. if d.User != nil {
  131. app.cfg = d.Config
  132. connectToDatabase(app)
  133. defer shutdown(app)
  134. u := &User{
  135. Username: d.User.Username,
  136. HashedPass: d.User.HashedPass,
  137. Created: time.Now().Truncate(time.Second).UTC(),
  138. }
  139. // Create blog
  140. log.Info("Creating user %s...\n", u.Username)
  141. err = app.db.CreateUser(u, app.cfg.App.SiteName)
  142. if err != nil {
  143. log.Error("Unable to create user: %s", err)
  144. os.Exit(1)
  145. }
  146. log.Info("Done!")
  147. }
  148. os.Exit(0)
  149. } else if *genKeys {
  150. errStatus := 0
  151. err := generateKey(emailKeyPath)
  152. if err != nil {
  153. errStatus = 1
  154. }
  155. err = generateKey(cookieAuthKeyPath)
  156. if err != nil {
  157. errStatus = 1
  158. }
  159. err = generateKey(cookieKeyPath)
  160. if err != nil {
  161. errStatus = 1
  162. }
  163. os.Exit(errStatus)
  164. }
  165. log.Info("Initializing...")
  166. log.Info("Loading configuration...")
  167. cfg, err := config.Load()
  168. if err != nil {
  169. log.Error("Unable to load configuration: %v", err)
  170. os.Exit(1)
  171. }
  172. app.cfg = cfg
  173. hostName = cfg.App.Host
  174. isSingleUser = cfg.App.SingleUser
  175. app.cfg.Server.Dev = *debugPtr
  176. initTemplates()
  177. // Load keys
  178. log.Info("Loading encryption keys...")
  179. err = initKeys(app)
  180. if err != nil {
  181. log.Error("\n%s\n", err)
  182. }
  183. // Initialize modules
  184. app.sessionStore = initSession(app)
  185. app.formDecoder = schema.NewDecoder()
  186. app.formDecoder.RegisterConverter(converter.NullJSONString{}, converter.ConvertJSONNullString)
  187. app.formDecoder.RegisterConverter(converter.NullJSONBool{}, converter.ConvertJSONNullBool)
  188. app.formDecoder.RegisterConverter(sql.NullString{}, converter.ConvertSQLNullString)
  189. app.formDecoder.RegisterConverter(sql.NullBool{}, converter.ConvertSQLNullBool)
  190. app.formDecoder.RegisterConverter(sql.NullInt64{}, converter.ConvertSQLNullInt64)
  191. app.formDecoder.RegisterConverter(sql.NullFloat64{}, converter.ConvertSQLNullFloat64)
  192. // Check database configuration
  193. if app.cfg.Database.User == "" || app.cfg.Database.Password == "" {
  194. log.Error("Database user or password not set.")
  195. os.Exit(1)
  196. }
  197. if app.cfg.Database.Host == "" {
  198. app.cfg.Database.Host = "localhost"
  199. }
  200. if app.cfg.Database.Database == "" {
  201. app.cfg.Database.Database = "writeas"
  202. }
  203. connectToDatabase(app)
  204. defer shutdown(app)
  205. r := mux.NewRouter()
  206. handler := NewHandler(app)
  207. handler.SetErrorPages(&ErrorPages{
  208. NotFound: pages["404-general.tmpl"],
  209. Gone: pages["410.tmpl"],
  210. InternalServerError: pages["500.tmpl"],
  211. Blank: pages["blank.tmpl"],
  212. })
  213. // Handle app routes
  214. initRoutes(handler, r, app.cfg, app.db)
  215. // Handle static files
  216. fs := http.FileServer(http.Dir(staticDir))
  217. shttp.Handle("/", fs)
  218. r.PathPrefix("/").Handler(fs)
  219. // Handle shutdown
  220. c := make(chan os.Signal, 2)
  221. signal.Notify(c, os.Interrupt, syscall.SIGTERM)
  222. go func() {
  223. <-c
  224. log.Info("Shutting down...")
  225. shutdown(app)
  226. log.Info("Done.")
  227. os.Exit(0)
  228. }()
  229. // Start web application server
  230. http.Handle("/", r)
  231. log.Info("Serving on http://localhost:%d\n", app.cfg.Server.Port)
  232. log.Info("---")
  233. err = http.ListenAndServe(fmt.Sprintf(":%d", app.cfg.Server.Port), nil)
  234. if err != nil {
  235. log.Error("Unable to start: %v", err)
  236. os.Exit(1)
  237. }
  238. }
  239. func connectToDatabase(app *app) {
  240. log.Info("Connecting to database...")
  241. 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))
  242. if err != nil {
  243. log.Error("%s", err)
  244. os.Exit(1)
  245. }
  246. app.db = &datastore{db}
  247. app.db.SetMaxOpenConns(50)
  248. }
  249. func shutdown(app *app) {
  250. log.Info("Closing database connection...")
  251. app.db.Close()
  252. }