The backend for the AiteStory website
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.

346 lines
8.4 KiB

7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
  1. package model
  2. import (
  3. "database/sql"
  4. "errors"
  5. "fmt"
  6. "net/url"
  7. "time"
  8. "git.aiterp.net/AiteRP/aitestory/formparser"
  9. "git.aiterp.net/AiteRP/aitestory/server"
  10. "git.aiterp.net/gisle/wrouter/generate"
  11. "github.com/microcosm-cc/bluemonday"
  12. "github.com/russross/blackfriday"
  13. )
  14. // PageCategories are used by the view model and page to enforce
  15. // a limited selection of categories. I may move it to a configuration
  16. var PageCategories = []string{
  17. "OoC",
  18. "Story",
  19. "Background",
  20. "Document",
  21. "News",
  22. "Item",
  23. "Info",
  24. }
  25. // PageTypes describes how the source is rendered. For now it's only markdown,
  26. // but who knows what the future holds.
  27. var PageTypes = []string{
  28. "Markdown",
  29. }
  30. // PageMinDate is the earliest date possible. Stories from Matriarch Eriana's childhood
  31. // are thus not going to happen.
  32. var PageMinDate, _ = time.Parse(time.RFC3339, "1753-01-01T00:00:00Z")
  33. // Page is the model describing the individual articles posted
  34. // by users.
  35. type Page struct {
  36. ID string `json:"id"`
  37. Name string `json:"name"`
  38. Author string `json:"author"`
  39. Category string `json:"category"`
  40. FictionalDate time.Time `json:"fictionalDate"`
  41. PublishDate time.Time `json:"publishDate"`
  42. EditDate time.Time `json:"editDate"`
  43. Dated bool `json:"dated"`
  44. Published bool `json:"published"`
  45. Unlisted bool `json:"unlisted"`
  46. Specific bool `json:"specific"`
  47. Indexed bool `json:"indexed"`
  48. BackgroundURL string `json:"backgroundUrl"`
  49. Type string `json:"type"`
  50. Source string `json:"source"`
  51. Tags []Tag `json:"tags"`
  52. prevTags []Tag
  53. cachedOutput string
  54. }
  55. // Defaults fills in the default details for a page, suited for populating a form
  56. func (page *Page) Defaults() {
  57. page.Category = PageCategories[0]
  58. page.Dated = true
  59. page.Published = true
  60. page.Unlisted = false
  61. page.Specific = false
  62. page.Indexed = true
  63. page.BackgroundURL = ""
  64. page.Type = PageTypes[0]
  65. page.Source = ""
  66. }
  67. // Insert adds the page to the database
  68. func (page *Page) Insert() error {
  69. const insertPage = `
  70. INSERT INTO page (
  71. id, name, author, category, fictional_date,
  72. publish_date, edit_date, dated, published,
  73. unlisted, page.specific, indexed, type, source
  74. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
  75. `
  76. const insertTag = `INSERT INTO page_tag (page_id,tag_id,page_tag.primary) VALUES (?, ?, ?)`
  77. db := server.Main.DB
  78. if page.ID == "" {
  79. page.generateID()
  80. }
  81. // Do the thing
  82. _, err := db.Exec(insertPage,
  83. page.ID, page.Name, page.Author, page.Category, page.FictionalDate, page.PublishDate,
  84. page.EditDate, page.Dated, page.Published, page.Unlisted, page.Specific, page.Indexed,
  85. page.Type, page.Source,
  86. )
  87. if err != nil {
  88. return err
  89. }
  90. // Insert tags
  91. for i, tag := range page.Tags {
  92. _, err := db.Exec(insertTag, page.ID, tag.ID, i == 0)
  93. if err != nil {
  94. page.Delete()
  95. return err
  96. }
  97. }
  98. return nil
  99. }
  100. // Update saves the page to the database
  101. func (page *Page) Update() error {
  102. const updatePage = `
  103. UPDATE page SET
  104. name=?,category=?,fictional_date=?,publish_date=?,
  105. edit_date=?,dated=?,published=?,unlisted=?,page.specific=?,
  106. indexed=?,type=?,source=?
  107. WHERE id=?
  108. `
  109. const clearTags = `DELETE FROM page_tag WHERE page_id=?`
  110. const insertTag = `INSERT INTO page_tag (page_id,tag_id,page_tag.primary) VALUES (?, ?, ?)`
  111. db := server.Main.DB
  112. if page.ID == "" {
  113. return errors.New("no id")
  114. }
  115. // Do the thing
  116. _, err := db.Exec(updatePage,
  117. page.Name, page.Category, page.FictionalDate, page.PublishDate,
  118. page.EditDate, page.Dated, page.Published, page.Unlisted, page.Specific, page.Indexed,
  119. page.Type, page.Source, page.ID,
  120. )
  121. if err != nil {
  122. return err
  123. }
  124. // Stop now if the tages haven't changed
  125. if len(page.prevTags) == len(page.Tags) {
  126. change := false
  127. for i, tag := range page.prevTags {
  128. if tag.ID != page.prevTags[i].ID {
  129. change = true
  130. break
  131. }
  132. }
  133. if !change {
  134. return nil
  135. }
  136. }
  137. // Re-tag (can be optimized if need arise)
  138. _, err = db.Exec(clearTags, page.ID)
  139. if err != nil {
  140. return err
  141. }
  142. for i, tag := range page.Tags {
  143. _, err := db.Exec(insertTag, page.ID, tag.ID, i == 0)
  144. if err != nil {
  145. return err
  146. }
  147. }
  148. return nil
  149. }
  150. // Delete removes the page from the database
  151. func (page *Page) Delete() error {
  152. db := server.Main.DB
  153. // Do the thing
  154. results, err := db.Exec("DELETE FROM `page` WHERE id=? LIMIT 1", page.ID)
  155. if err != nil {
  156. return err
  157. }
  158. // Count the stuffs that were done things to
  159. affected, err := results.RowsAffected()
  160. if err != nil {
  161. return err
  162. }
  163. if affected == 0 {
  164. return errors.New("page not found")
  165. }
  166. return nil
  167. }
  168. // Content parses the content of the page
  169. func (page *Page) Content() (string, error) {
  170. if page.cachedOutput != "" {
  171. return page.cachedOutput, nil
  172. }
  173. if page.Type == "Markdown" {
  174. // TODO: Convert [[Ehanis Tioran]] to [Ehanis Tioran](https://wiki.aiterp.net/index.php?title=Ehanis%20Tioran)
  175. unsafe := blackfriday.MarkdownCommon([]byte(page.Source))
  176. page.cachedOutput = string(bluemonday.UGCPolicy().SanitizeBytes(unsafe))
  177. return page.cachedOutput, nil
  178. }
  179. return "", fmt.Errorf("Page type '%s' is not supported", page.Type)
  180. }
  181. // ParseForm validates the values in a form and sets the page's values whenever possible regardless
  182. // so that it can be pushed to the viewmodel to allow the user to correct their mistakes without fear
  183. // of losing their hard work
  184. func (page *Page) ParseForm(form url.Values) []error {
  185. errors := make([]error, 0, 4)
  186. page.cachedOutput = ""
  187. err := formparser.Select(form.Get("category"), &page.Category, PageCategories, page.Category != "")
  188. if err != nil {
  189. errors = append(errors, fmt.Errorf("Category: %s", err))
  190. }
  191. err = formparser.Date(form.Get("fictionalDate"), &page.FictionalDate, !page.FictionalDate.IsZero())
  192. if err != nil {
  193. errors = append(errors, fmt.Errorf("Fictonal Date: %s", err))
  194. }
  195. page.Dated = form.Get("dated") != ""
  196. page.Published = form.Get("published") != ""
  197. page.Unlisted = form.Get("unlisted") != ""
  198. page.Specific = form.Get("specific") != ""
  199. page.Indexed = form.Get("indexed") != ""
  200. err = formparser.String(form.Get("backgroundUrl"), &page.BackgroundURL, 0, 255)
  201. if err != nil {
  202. errors = append(errors, fmt.Errorf("Background URL: %s", err))
  203. }
  204. err = formparser.Select(form.Get("type"), &page.Type, PageTypes, page.Type != "")
  205. if err != nil {
  206. errors = append(errors, fmt.Errorf("Category: %s", err))
  207. }
  208. err = formparser.String(form.Get("source"), &page.Source, 0, 102400)
  209. if err != nil {
  210. errors = append(errors, fmt.Errorf("Content is too long, max: 100 KB (~17,000 words)"))
  211. }
  212. if len(errors) > 0 {
  213. errors = nil
  214. }
  215. return errors
  216. }
  217. // Standardize page ID generation
  218. func (page *Page) generateID() {
  219. page.ID = generate.FriendlyID(16)
  220. }
  221. // FindPage finds a page by ID. The Header model handles
  222. // listning pages
  223. func FindPage(id string) (*Page, error) {
  224. const selectPage = `
  225. SELECT id,name,author,category,fictional_date,publish_date,edit_date,dated,published,
  226. unlisted,page.specific,indexed,type,source,background_url
  227. FROM page
  228. WHERE id=?
  229. `
  230. const selectPageTags = `
  231. SELECT tag.id,tag.type,tag.name
  232. FROM page_tag
  233. RIGHT JOIN tag ON (tag.id = page_tag.tag_id)
  234. WHERE page_tag.page_id = ?
  235. `
  236. db := server.Main.DB
  237. rows, err := db.Query(selectPage, id)
  238. if err != nil {
  239. return nil, err
  240. }
  241. defer rows.Close()
  242. if !rows.Next() {
  243. return nil, errors.New("not found")
  244. }
  245. page := new(Page)
  246. err = parsePage(page, rows)
  247. if err != nil {
  248. return nil, err
  249. }
  250. rows, err = db.Query(selectPageTags, page.ID)
  251. if err != nil {
  252. return nil, err
  253. }
  254. page.Tags = make([]Tag, 0, 64)
  255. for rows.Next() {
  256. tag := Tag{}
  257. rows.Scan(&tag.ID, &tag.Type, &tag.Name)
  258. page.Tags = append(page.Tags, tag)
  259. }
  260. return page, nil
  261. }
  262. func parsePage(page *Page, rows *sql.Rows) error {
  263. var fictionalDate, publishDate, editDate string
  264. var bgURL *string
  265. err := rows.Scan(
  266. &page.ID, &page.Name, &page.Author, &page.Category, &fictionalDate,
  267. &publishDate, &editDate, &page.Dated, &page.Published, &page.Unlisted,
  268. &page.Specific, &page.Indexed, &page.Type, &page.Source, &bgURL,
  269. )
  270. if err != nil {
  271. return err
  272. }
  273. if bgURL != nil {
  274. page.BackgroundURL = *bgURL
  275. }
  276. page.FictionalDate, err = time.Parse("2006-01-02 15:04:05", fictionalDate)
  277. if err != nil {
  278. return err
  279. }
  280. page.PublishDate, err = time.Parse("2006-01-02 15:04:05", publishDate)
  281. if err != nil {
  282. return err
  283. }
  284. page.EditDate, err = time.Parse("2006-01-02 15:04:05", editDate)
  285. if err != nil {
  286. return err
  287. }
  288. return nil
  289. }