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.
 
 
 
 
 

2425 lines
72 KiB

  1. /*
  2. * Copyright © 2018 A Bunch Tell LLC.
  3. *
  4. * This file is part of WriteFreely.
  5. *
  6. * WriteFreely is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License, included
  8. * in the LICENSE file in this source code package.
  9. */
  10. package writefreely
  11. import (
  12. "database/sql"
  13. "fmt"
  14. "net/http"
  15. "strings"
  16. "time"
  17. "github.com/guregu/null"
  18. "github.com/guregu/null/zero"
  19. uuid "github.com/nu7hatch/gouuid"
  20. "github.com/writeas/impart"
  21. "github.com/writeas/nerds/store"
  22. "github.com/writeas/web-core/activitypub"
  23. "github.com/writeas/web-core/auth"
  24. "github.com/writeas/web-core/data"
  25. "github.com/writeas/web-core/id"
  26. "github.com/writeas/web-core/log"
  27. "github.com/writeas/web-core/query"
  28. "github.com/writeas/writefreely/author"
  29. )
  30. const (
  31. mySQLErrDuplicateKey = 1062
  32. driverMySQL = "mysql"
  33. driverSQLite = "sqlite3"
  34. )
  35. var (
  36. SQLiteEnabled bool
  37. )
  38. type writestore interface {
  39. CreateUser(*User, string) error
  40. UpdateUserEmail(keys *keychain, userID int64, email string) error
  41. UpdateEncryptedUserEmail(int64, []byte) error
  42. GetUserByID(int64) (*User, error)
  43. GetUserForAuth(string) (*User, error)
  44. GetUserForAuthByID(int64) (*User, error)
  45. GetUserNameFromToken(string) (string, error)
  46. GetUserDataFromToken(string) (int64, string, error)
  47. GetAPIUser(header string) (*User, error)
  48. GetUserID(accessToken string) int64
  49. GetUserIDPrivilege(accessToken string) (userID int64, sudo bool)
  50. DeleteToken(accessToken []byte) error
  51. FetchLastAccessToken(userID int64) string
  52. GetAccessToken(userID int64) (string, error)
  53. GetTemporaryAccessToken(userID int64, validSecs int) (string, error)
  54. GetTemporaryOneTimeAccessToken(userID int64, validSecs int, oneTime bool) (string, error)
  55. DeleteAccount(userID int64) (l *string, err error)
  56. ChangeSettings(app *App, u *User, s *userSettings) error
  57. ChangePassphrase(userID int64, sudo bool, curPass string, hashedPass []byte) error
  58. GetCollections(u *User) (*[]Collection, error)
  59. GetPublishableCollections(u *User) (*[]Collection, error)
  60. GetMeStats(u *User) userMeStats
  61. GetTotalCollections() (int64, error)
  62. GetTotalPosts() (int64, error)
  63. GetTopPosts(u *User, alias string) (*[]PublicPost, error)
  64. GetAnonymousPosts(u *User) (*[]PublicPost, error)
  65. GetUserPosts(u *User) (*[]PublicPost, error)
  66. CreateOwnedPost(post *SubmittedPost, accessToken, collAlias string) (*PublicPost, error)
  67. CreatePost(userID, collID int64, post *SubmittedPost) (*Post, error)
  68. UpdateOwnedPost(post *AuthenticatedPost, userID int64) error
  69. GetEditablePost(id, editToken string) (*PublicPost, error)
  70. PostIDExists(id string) bool
  71. GetPost(id string, collectionID int64) (*PublicPost, error)
  72. GetOwnedPost(id string, ownerID int64) (*PublicPost, error)
  73. GetPostProperty(id string, collectionID int64, property string) (interface{}, error)
  74. CreateCollectionFromToken(string, string, string) (*Collection, error)
  75. CreateCollection(string, string, int64) (*Collection, error)
  76. GetCollectionBy(condition string, value interface{}) (*Collection, error)
  77. GetCollection(alias string) (*Collection, error)
  78. GetCollectionForPad(alias string) (*Collection, error)
  79. GetCollectionByID(id int64) (*Collection, error)
  80. UpdateCollection(c *SubmittedCollection, alias string) error
  81. DeleteCollection(alias string, userID int64) error
  82. UpdatePostPinState(pinned bool, postID string, collID, ownerID, pos int64) error
  83. GetLastPinnedPostPos(collID int64) int64
  84. GetPinnedPosts(coll *CollectionObj) (*[]PublicPost, error)
  85. RemoveCollectionRedirect(t *sql.Tx, alias string) error
  86. GetCollectionRedirect(alias string) (new string)
  87. IsCollectionAttributeOn(id int64, attr string) bool
  88. CollectionHasAttribute(id int64, attr string) bool
  89. CanCollect(cpr *ClaimPostRequest, userID int64) bool
  90. AttemptClaim(p *ClaimPostRequest, query string, params []interface{}, slugIdx int) (sql.Result, error)
  91. DispersePosts(userID int64, postIDs []string) (*[]ClaimPostResult, error)
  92. ClaimPosts(userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error)
  93. GetPostsCount(c *CollectionObj, includeFuture bool)
  94. GetPosts(c *Collection, page int, includeFuture, forceRecentFirst bool) (*[]PublicPost, error)
  95. GetPostsTagged(c *Collection, tag string, page int, includeFuture bool) (*[]PublicPost, error)
  96. GetAPFollowers(c *Collection) (*[]RemoteUser, error)
  97. GetAPActorKeys(collectionID int64) ([]byte, []byte)
  98. CreateUserInvite(id string, userID int64, maxUses int, expires *time.Time) error
  99. GetUserInvites(userID int64) (*[]Invite, error)
  100. GetUserInvite(id string) (*Invite, error)
  101. GetUsersInvitedCount(id string) int64
  102. CreateInvitedUser(inviteID string, userID int64) error
  103. GetDynamicContent(id string) (*instanceContent, error)
  104. UpdateDynamicContent(id, title, content, contentType string) error
  105. GetAllUsers(page uint) (*[]User, error)
  106. GetAllUsersCount() int64
  107. GetUserLastPostTime(id int64) (*time.Time, error)
  108. GetCollectionLastPostTime(id int64) (*time.Time, error)
  109. DatabaseInitialized() bool
  110. }
  111. type datastore struct {
  112. *sql.DB
  113. driverName string
  114. }
  115. func (db *datastore) now() string {
  116. if db.driverName == driverSQLite {
  117. return "strftime('%Y-%m-%d %H:%M:%S','now')"
  118. }
  119. return "NOW()"
  120. }
  121. func (db *datastore) clip(field string, l int) string {
  122. if db.driverName == driverSQLite {
  123. return fmt.Sprintf("SUBSTR(%s, 0, %d)", field, l)
  124. }
  125. return fmt.Sprintf("LEFT(%s, %d)", field, l)
  126. }
  127. func (db *datastore) upsert(indexedCols ...string) string {
  128. if db.driverName == driverSQLite {
  129. // NOTE: SQLite UPSERT syntax only works in v3.24.0 (2018-06-04) or later
  130. // Leaving this for whenever we can upgrade and include it in our binary
  131. cc := strings.Join(indexedCols, ", ")
  132. return "ON CONFLICT(" + cc + ") DO UPDATE SET"
  133. }
  134. return "ON DUPLICATE KEY UPDATE"
  135. }
  136. func (db *datastore) dateSub(l int, unit string) string {
  137. if db.driverName == driverSQLite {
  138. return fmt.Sprintf("DATETIME('now', '-%d %s')", l, unit)
  139. }
  140. return fmt.Sprintf("DATE_SUB(NOW(), INTERVAL %d %s)", l, unit)
  141. }
  142. func (db *datastore) CreateUser(u *User, collectionTitle string) error {
  143. if db.PostIDExists(u.Username) {
  144. return impart.HTTPError{http.StatusConflict, "Invalid collection name."}
  145. }
  146. // New users get a `users` and `collections` row.
  147. t, err := db.Begin()
  148. if err != nil {
  149. return err
  150. }
  151. // 1. Add to `users` table
  152. // NOTE: Assumes User's Password is already hashed!
  153. res, err := t.Exec("INSERT INTO users (username, password, email) VALUES (?, ?, ?)", u.Username, u.HashedPass, u.Email)
  154. if err != nil {
  155. t.Rollback()
  156. if db.isDuplicateKeyErr(err) {
  157. return impart.HTTPError{http.StatusConflict, "Username is already taken."}
  158. }
  159. log.Error("Rolling back users INSERT: %v\n", err)
  160. return err
  161. }
  162. u.ID, err = res.LastInsertId()
  163. if err != nil {
  164. t.Rollback()
  165. log.Error("Rolling back after LastInsertId: %v\n", err)
  166. return err
  167. }
  168. // 2. Create user's Collection
  169. if collectionTitle == "" {
  170. collectionTitle = u.Username
  171. }
  172. res, err = t.Exec("INSERT INTO collections (alias, title, description, privacy, owner_id, view_count) VALUES (?, ?, ?, ?, ?, ?)", u.Username, collectionTitle, "", CollUnlisted, u.ID, 0)
  173. if err != nil {
  174. t.Rollback()
  175. if db.isDuplicateKeyErr(err) {
  176. return impart.HTTPError{http.StatusConflict, "Username is already taken."}
  177. }
  178. log.Error("Rolling back collections INSERT: %v\n", err)
  179. return err
  180. }
  181. db.RemoveCollectionRedirect(t, u.Username)
  182. err = t.Commit()
  183. if err != nil {
  184. t.Rollback()
  185. log.Error("Rolling back after Commit(): %v\n", err)
  186. return err
  187. }
  188. return nil
  189. }
  190. // FIXME: We're returning errors inconsistently in this file. Do we use Errorf
  191. // for returned value, or impart?
  192. func (db *datastore) UpdateUserEmail(keys *keychain, userID int64, email string) error {
  193. encEmail, err := data.Encrypt(keys.emailKey, email)
  194. if err != nil {
  195. return fmt.Errorf("Couldn't encrypt email %s: %s\n", email, err)
  196. }
  197. return db.UpdateEncryptedUserEmail(userID, encEmail)
  198. }
  199. func (db *datastore) UpdateEncryptedUserEmail(userID int64, encEmail []byte) error {
  200. _, err := db.Exec("UPDATE users SET email = ? WHERE id = ?", encEmail, userID)
  201. if err != nil {
  202. return fmt.Errorf("Unable to update user email: %s", err)
  203. }
  204. return nil
  205. }
  206. func (db *datastore) CreateCollectionFromToken(alias, title, accessToken string) (*Collection, error) {
  207. userID := db.GetUserID(accessToken)
  208. if userID == -1 {
  209. return nil, ErrBadAccessToken
  210. }
  211. return db.CreateCollection(alias, title, userID)
  212. }
  213. func (db *datastore) GetUserCollectionCount(userID int64) (uint64, error) {
  214. var collCount uint64
  215. err := db.QueryRow("SELECT COUNT(*) FROM collections WHERE owner_id = ?", userID).Scan(&collCount)
  216. switch {
  217. case err == sql.ErrNoRows:
  218. return 0, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user from database."}
  219. case err != nil:
  220. log.Error("Couldn't get collections count for user %d: %v", userID, err)
  221. return 0, err
  222. }
  223. return collCount, nil
  224. }
  225. func (db *datastore) CreateCollection(alias, title string, userID int64) (*Collection, error) {
  226. if db.PostIDExists(alias) {
  227. return nil, impart.HTTPError{http.StatusConflict, "Invalid collection name."}
  228. }
  229. // All good, so create new collection
  230. res, err := db.Exec("INSERT INTO collections (alias, title, description, privacy, owner_id, view_count) VALUES (?, ?, ?, ?, ?, ?)", alias, title, "", CollUnlisted, userID, 0)
  231. if err != nil {
  232. if db.isDuplicateKeyErr(err) {
  233. return nil, impart.HTTPError{http.StatusConflict, "Collection already exists."}
  234. }
  235. log.Error("Couldn't add to collections: %v\n", err)
  236. return nil, err
  237. }
  238. c := &Collection{
  239. Alias: alias,
  240. Title: title,
  241. OwnerID: userID,
  242. PublicOwner: false,
  243. }
  244. c.ID, err = res.LastInsertId()
  245. if err != nil {
  246. log.Error("Couldn't get collection LastInsertId: %v\n", err)
  247. }
  248. return c, nil
  249. }
  250. func (db *datastore) GetUserByID(id int64) (*User, error) {
  251. u := &User{ID: id}
  252. err := db.QueryRow("SELECT username, password, email, created FROM users WHERE id = ?", id).Scan(&u.Username, &u.HashedPass, &u.Email, &u.Created)
  253. switch {
  254. case err == sql.ErrNoRows:
  255. return nil, ErrUserNotFound
  256. case err != nil:
  257. log.Error("Couldn't SELECT user password: %v", err)
  258. return nil, err
  259. }
  260. return u, nil
  261. }
  262. // DoesUserNeedAuth returns true if the user hasn't provided any methods for
  263. // authenticating with the account, such a passphrase or email address.
  264. // Any errors are reported to admin and silently quashed, returning false as the
  265. // result.
  266. func (db *datastore) DoesUserNeedAuth(id int64) bool {
  267. var pass, email []byte
  268. // Find out if user has an email set first
  269. err := db.QueryRow("SELECT password, email FROM users WHERE id = ?", id).Scan(&pass, &email)
  270. switch {
  271. case err == sql.ErrNoRows:
  272. // ERROR. Don't give false positives on needing auth methods
  273. return false
  274. case err != nil:
  275. // ERROR. Don't give false positives on needing auth methods
  276. log.Error("Couldn't SELECT user %d from users: %v", id, err)
  277. return false
  278. }
  279. // User doesn't need auth if there's an email
  280. return len(email) == 0 && len(pass) == 0
  281. }
  282. func (db *datastore) IsUserPassSet(id int64) (bool, error) {
  283. var pass []byte
  284. err := db.QueryRow("SELECT password FROM users WHERE id = ?", id).Scan(&pass)
  285. switch {
  286. case err == sql.ErrNoRows:
  287. return false, nil
  288. case err != nil:
  289. log.Error("Couldn't SELECT user %d from users: %v", id, err)
  290. return false, err
  291. }
  292. return len(pass) > 0, nil
  293. }
  294. func (db *datastore) GetUserForAuth(username string) (*User, error) {
  295. u := &User{Username: username}
  296. err := db.QueryRow("SELECT id, password, email, created FROM users WHERE username = ?", username).Scan(&u.ID, &u.HashedPass, &u.Email, &u.Created)
  297. switch {
  298. case err == sql.ErrNoRows:
  299. // Check if they've entered the wrong, unnormalized username
  300. username = getSlug(username, "")
  301. if username != u.Username {
  302. err = db.QueryRow("SELECT id FROM users WHERE username = ? LIMIT 1", username).Scan(&u.ID)
  303. if err == nil {
  304. return db.GetUserForAuth(username)
  305. }
  306. }
  307. return nil, ErrUserNotFound
  308. case err != nil:
  309. log.Error("Couldn't SELECT user password: %v", err)
  310. return nil, err
  311. }
  312. return u, nil
  313. }
  314. func (db *datastore) GetUserForAuthByID(userID int64) (*User, error) {
  315. u := &User{ID: userID}
  316. err := db.QueryRow("SELECT id, password, email, created FROM users WHERE id = ?", u.ID).Scan(&u.ID, &u.HashedPass, &u.Email, &u.Created)
  317. switch {
  318. case err == sql.ErrNoRows:
  319. return nil, ErrUserNotFound
  320. case err != nil:
  321. log.Error("Couldn't SELECT userForAuthByID: %v", err)
  322. return nil, err
  323. }
  324. return u, nil
  325. }
  326. func (db *datastore) GetUserNameFromToken(accessToken string) (string, error) {
  327. t := auth.GetToken(accessToken)
  328. if len(t) == 0 {
  329. return "", ErrNoAccessToken
  330. }
  331. var oneTime bool
  332. var username string
  333. err := db.QueryRow("SELECT username, one_time FROM accesstokens LEFT JOIN users ON user_id = id WHERE token = ? AND (expires IS NULL OR expires > NOW())", t).Scan(&username, &oneTime)
  334. switch {
  335. case err == sql.ErrNoRows:
  336. return "", ErrBadAccessToken
  337. case err != nil:
  338. return "", ErrInternalGeneral
  339. }
  340. // Delete token if it was one-time
  341. if oneTime {
  342. db.DeleteToken(t[:])
  343. }
  344. return username, nil
  345. }
  346. func (db *datastore) GetUserDataFromToken(accessToken string) (int64, string, error) {
  347. t := auth.GetToken(accessToken)
  348. if len(t) == 0 {
  349. return 0, "", ErrNoAccessToken
  350. }
  351. var userID int64
  352. var oneTime bool
  353. var username string
  354. err := db.QueryRow("SELECT user_id, username, one_time FROM accesstokens LEFT JOIN users ON user_id = id WHERE token = ? AND (expires IS NULL OR expires > NOW())", t).Scan(&userID, &username, &oneTime)
  355. switch {
  356. case err == sql.ErrNoRows:
  357. return 0, "", ErrBadAccessToken
  358. case err != nil:
  359. return 0, "", ErrInternalGeneral
  360. }
  361. // Delete token if it was one-time
  362. if oneTime {
  363. db.DeleteToken(t[:])
  364. }
  365. return userID, username, nil
  366. }
  367. func (db *datastore) GetAPIUser(header string) (*User, error) {
  368. uID := db.GetUserID(header)
  369. if uID == -1 {
  370. return nil, fmt.Errorf(ErrUserNotFound.Error())
  371. }
  372. return db.GetUserByID(uID)
  373. }
  374. // GetUserID takes a hexadecimal accessToken, parses it into its binary
  375. // representation, and gets any user ID associated with the token. If no user
  376. // is associated, -1 is returned.
  377. func (db *datastore) GetUserID(accessToken string) int64 {
  378. i, _ := db.GetUserIDPrivilege(accessToken)
  379. return i
  380. }
  381. func (db *datastore) GetUserIDPrivilege(accessToken string) (userID int64, sudo bool) {
  382. t := auth.GetToken(accessToken)
  383. if len(t) == 0 {
  384. return -1, false
  385. }
  386. var oneTime bool
  387. err := db.QueryRow("SELECT user_id, sudo, one_time FROM accesstokens WHERE token = ? AND (expires IS NULL OR expires > NOW())", t).Scan(&userID, &sudo, &oneTime)
  388. switch {
  389. case err == sql.ErrNoRows:
  390. return -1, false
  391. case err != nil:
  392. return -1, false
  393. }
  394. // Delete token if it was one-time
  395. if oneTime {
  396. db.DeleteToken(t[:])
  397. }
  398. return
  399. }
  400. func (db *datastore) DeleteToken(accessToken []byte) error {
  401. res, err := db.Exec("DELETE FROM accesstokens WHERE token = ?", accessToken)
  402. if err != nil {
  403. return err
  404. }
  405. rowsAffected, _ := res.RowsAffected()
  406. if rowsAffected == 0 {
  407. return impart.HTTPError{http.StatusNotFound, "Token is invalid or doesn't exist"}
  408. }
  409. return nil
  410. }
  411. // FetchLastAccessToken creates a new non-expiring, valid access token for the given
  412. // userID.
  413. func (db *datastore) FetchLastAccessToken(userID int64) string {
  414. var t []byte
  415. err := db.QueryRow("SELECT token FROM accesstokens WHERE user_id = ? AND (expires IS NULL OR expires > NOW()) ORDER BY created DESC LIMIT 1", userID).Scan(&t)
  416. switch {
  417. case err == sql.ErrNoRows:
  418. return ""
  419. case err != nil:
  420. log.Error("Failed selecting from accesstoken: %v", err)
  421. return ""
  422. }
  423. u, err := uuid.Parse(t)
  424. if err != nil {
  425. return ""
  426. }
  427. return u.String()
  428. }
  429. // GetAccessToken creates a new non-expiring, valid access token for the given
  430. // userID.
  431. func (db *datastore) GetAccessToken(userID int64) (string, error) {
  432. return db.GetTemporaryOneTimeAccessToken(userID, 0, false)
  433. }
  434. // GetTemporaryAccessToken creates a new valid access token for the given
  435. // userID that remains valid for the given time in seconds. If validSecs is 0,
  436. // the access token doesn't automatically expire.
  437. func (db *datastore) GetTemporaryAccessToken(userID int64, validSecs int) (string, error) {
  438. return db.GetTemporaryOneTimeAccessToken(userID, validSecs, false)
  439. }
  440. // GetTemporaryOneTimeAccessToken creates a new valid access token for the given
  441. // userID that remains valid for the given time in seconds and can only be used
  442. // once if oneTime is true. If validSecs is 0, the access token doesn't
  443. // automatically expire.
  444. func (db *datastore) GetTemporaryOneTimeAccessToken(userID int64, validSecs int, oneTime bool) (string, error) {
  445. u, err := uuid.NewV4()
  446. if err != nil {
  447. log.Error("Unable to generate token: %v", err)
  448. return "", err
  449. }
  450. // Insert UUID to `accesstokens`
  451. binTok := u[:]
  452. expirationVal := "NULL"
  453. if validSecs > 0 {
  454. expirationVal = fmt.Sprintf("DATE_ADD(NOW(), INTERVAL %d SECOND)", validSecs)
  455. }
  456. _, err = db.Exec("INSERT INTO accesstokens (token, user_id, one_time, expires) VALUES (?, ?, ?, "+expirationVal+")", string(binTok), userID, oneTime)
  457. if err != nil {
  458. log.Error("Couldn't INSERT accesstoken: %v", err)
  459. return "", err
  460. }
  461. return u.String(), nil
  462. }
  463. func (db *datastore) CreateOwnedPost(post *SubmittedPost, accessToken, collAlias string) (*PublicPost, error) {
  464. var userID, collID int64 = -1, -1
  465. var coll *Collection
  466. var err error
  467. if accessToken != "" {
  468. userID = db.GetUserID(accessToken)
  469. if userID == -1 {
  470. return nil, ErrBadAccessToken
  471. }
  472. if collAlias != "" {
  473. coll, err = db.GetCollection(collAlias)
  474. if err != nil {
  475. return nil, err
  476. }
  477. if coll.OwnerID != userID {
  478. return nil, ErrForbiddenCollection
  479. }
  480. collID = coll.ID
  481. }
  482. }
  483. rp := &PublicPost{}
  484. rp.Post, err = db.CreatePost(userID, collID, post)
  485. if err != nil {
  486. return rp, err
  487. }
  488. if coll != nil {
  489. coll.ForPublic()
  490. rp.Collection = &CollectionObj{Collection: *coll}
  491. }
  492. return rp, nil
  493. }
  494. func (db *datastore) CreatePost(userID, collID int64, post *SubmittedPost) (*Post, error) {
  495. idLen := postIDLen
  496. friendlyID := store.GenerateFriendlyRandomString(idLen)
  497. // Handle appearance / font face
  498. appearance := post.Font
  499. if !post.isFontValid() {
  500. appearance = "norm"
  501. }
  502. var err error
  503. ownerID := sql.NullInt64{
  504. Valid: false,
  505. }
  506. ownerCollID := sql.NullInt64{
  507. Valid: false,
  508. }
  509. slug := sql.NullString{"", false}
  510. // If an alias was supplied, we'll add this to the collection as well.
  511. if userID > 0 {
  512. ownerID.Int64 = userID
  513. ownerID.Valid = true
  514. if collID > 0 {
  515. ownerCollID.Int64 = collID
  516. ownerCollID.Valid = true
  517. var slugVal string
  518. if post.Title != nil && *post.Title != "" {
  519. slugVal = getSlug(*post.Title, post.Language.String)
  520. if slugVal == "" {
  521. slugVal = getSlug(*post.Content, post.Language.String)
  522. }
  523. } else {
  524. slugVal = getSlug(*post.Content, post.Language.String)
  525. }
  526. if slugVal == "" {
  527. slugVal = friendlyID
  528. }
  529. slug = sql.NullString{slugVal, true}
  530. }
  531. }
  532. created := time.Now()
  533. if db.driverName == driverSQLite {
  534. // SQLite stores datetimes in UTC, so convert time.Now() to it here
  535. created = created.UTC()
  536. }
  537. if post.Created != nil {
  538. created, err = time.Parse("2006-01-02T15:04:05Z", *post.Created)
  539. if err != nil {
  540. log.Error("Unable to parse Created time '%s': %v", *post.Created, err)
  541. created = time.Now()
  542. if db.driverName == driverSQLite {
  543. // SQLite stores datetimes in UTC, so convert time.Now() to it here
  544. created = created.UTC()
  545. }
  546. }
  547. }
  548. stmt, err := db.Prepare("INSERT INTO posts (id, slug, title, content, text_appearance, language, rtl, privacy, owner_id, collection_id, created, updated, view_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + db.now() + ", ?)")
  549. if err != nil {
  550. return nil, err
  551. }
  552. defer stmt.Close()
  553. _, err = stmt.Exec(friendlyID, slug, post.Title, post.Content, appearance, post.Language, post.IsRTL, 0, ownerID, ownerCollID, created, 0)
  554. if err != nil {
  555. if db.isDuplicateKeyErr(err) {
  556. // Duplicate entry error; try a new slug
  557. // TODO: make this a little more robust
  558. slug = sql.NullString{id.GenSafeUniqueSlug(slug.String), true}
  559. _, err = stmt.Exec(friendlyID, slug, post.Title, post.Content, appearance, post.Language, post.IsRTL, 0, ownerID, ownerCollID, created, 0)
  560. if err != nil {
  561. return nil, handleFailedPostInsert(fmt.Errorf("Retried slug generation, still failed: %v", err))
  562. }
  563. } else {
  564. return nil, handleFailedPostInsert(err)
  565. }
  566. }
  567. // TODO: return Created field in proper format
  568. return &Post{
  569. ID: friendlyID,
  570. Slug: null.NewString(slug.String, slug.Valid),
  571. Font: appearance,
  572. Language: zero.NewString(post.Language.String, post.Language.Valid),
  573. RTL: zero.NewBool(post.IsRTL.Bool, post.IsRTL.Valid),
  574. OwnerID: null.NewInt(userID, true),
  575. CollectionID: null.NewInt(userID, true),
  576. Created: created.Truncate(time.Second).UTC(),
  577. Updated: time.Now().Truncate(time.Second).UTC(),
  578. Title: zero.NewString(*(post.Title), true),
  579. Content: *(post.Content),
  580. }, nil
  581. }
  582. // UpdateOwnedPost updates an existing post with only the given fields in the
  583. // supplied AuthenticatedPost.
  584. func (db *datastore) UpdateOwnedPost(post *AuthenticatedPost, userID int64) error {
  585. params := []interface{}{}
  586. var queryUpdates, sep, authCondition string
  587. if post.Slug != nil && *post.Slug != "" {
  588. queryUpdates += sep + "slug = ?"
  589. sep = ", "
  590. params = append(params, getSlug(*post.Slug, ""))
  591. }
  592. if post.Content != nil {
  593. queryUpdates += sep + "content = ?"
  594. sep = ", "
  595. params = append(params, post.Content)
  596. }
  597. if post.Title != nil {
  598. queryUpdates += sep + "title = ?"
  599. sep = ", "
  600. params = append(params, post.Title)
  601. }
  602. if post.Language.Valid {
  603. queryUpdates += sep + "language = ?"
  604. sep = ", "
  605. params = append(params, post.Language.String)
  606. }
  607. if post.IsRTL.Valid {
  608. queryUpdates += sep + "rtl = ?"
  609. sep = ", "
  610. params = append(params, post.IsRTL.Bool)
  611. }
  612. if post.Font != "" {
  613. queryUpdates += sep + "text_appearance = ?"
  614. sep = ", "
  615. params = append(params, post.Font)
  616. }
  617. if post.Created != nil {
  618. createTime, err := time.Parse(postMetaDateFormat, *post.Created)
  619. if err != nil {
  620. log.Error("Unable to parse Created date: %v", err)
  621. return fmt.Errorf("That's the incorrect format for Created date.")
  622. }
  623. queryUpdates += sep + "created = ?"
  624. sep = ", "
  625. params = append(params, createTime)
  626. }
  627. // WHERE parameters...
  628. // id = ?
  629. params = append(params, post.ID)
  630. // AND owner_id = ?
  631. authCondition = "(owner_id = ?)"
  632. params = append(params, userID)
  633. if queryUpdates == "" {
  634. return ErrPostNoUpdatableVals
  635. }
  636. queryUpdates += sep + "updated = " + db.now()
  637. res, err := db.Exec("UPDATE posts SET "+queryUpdates+" WHERE id = ? AND "+authCondition, params...)
  638. if err != nil {
  639. log.Error("Unable to update owned post: %v", err)
  640. return err
  641. }
  642. rowsAffected, _ := res.RowsAffected()
  643. if rowsAffected == 0 {
  644. // Show the correct error message if nothing was updated
  645. var dummy int
  646. err := db.QueryRow("SELECT 1 FROM posts WHERE id = ? AND "+authCondition, post.ID, params[len(params)-1]).Scan(&dummy)
  647. switch {
  648. case err == sql.ErrNoRows:
  649. return ErrUnauthorizedEditPost
  650. case err != nil:
  651. log.Error("Failed selecting from posts: %v", err)
  652. }
  653. return nil
  654. }
  655. return nil
  656. }
  657. func (db *datastore) GetCollectionBy(condition string, value interface{}) (*Collection, error) {
  658. c := &Collection{}
  659. // FIXME: change Collection to reflect database values. Add helper functions to get actual values
  660. var styleSheet, script, format zero.String
  661. row := db.QueryRow("SELECT id, alias, title, description, style_sheet, script, format, owner_id, privacy, view_count FROM collections WHERE "+condition, value)
  662. err := row.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &styleSheet, &script, &format, &c.OwnerID, &c.Visibility, &c.Views)
  663. switch {
  664. case err == sql.ErrNoRows:
  665. return nil, impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."}
  666. case err != nil:
  667. log.Error("Failed selecting from collections: %v", err)
  668. return nil, err
  669. }
  670. c.StyleSheet = styleSheet.String
  671. c.Script = script.String
  672. c.Format = format.String
  673. c.Public = c.IsPublic()
  674. c.db = db
  675. return c, nil
  676. }
  677. func (db *datastore) GetCollection(alias string) (*Collection, error) {
  678. return db.GetCollectionBy("alias = ?", alias)
  679. }
  680. func (db *datastore) GetCollectionForPad(alias string) (*Collection, error) {
  681. c := &Collection{Alias: alias}
  682. row := db.QueryRow("SELECT id, alias, title, description, privacy FROM collections WHERE alias = ?", alias)
  683. err := row.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &c.Visibility)
  684. switch {
  685. case err == sql.ErrNoRows:
  686. return c, impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."}
  687. case err != nil:
  688. log.Error("Failed selecting from collections: %v", err)
  689. return c, ErrInternalGeneral
  690. }
  691. c.Public = c.IsPublic()
  692. return c, nil
  693. }
  694. func (db *datastore) GetCollectionByID(id int64) (*Collection, error) {
  695. return db.GetCollectionBy("id = ?", id)
  696. }
  697. func (db *datastore) GetCollectionFromDomain(host string) (*Collection, error) {
  698. return db.GetCollectionBy("host = ?", host)
  699. }
  700. func (db *datastore) UpdateCollection(c *SubmittedCollection, alias string) error {
  701. q := query.NewUpdate().
  702. SetStringPtr(c.Title, "title").
  703. SetStringPtr(c.Description, "description").
  704. SetNullString(c.StyleSheet, "style_sheet").
  705. SetNullString(c.Script, "script")
  706. if c.Format != nil {
  707. cf := &CollectionFormat{Format: c.Format.String}
  708. if cf.Valid() {
  709. q.SetNullString(c.Format, "format")
  710. }
  711. }
  712. var updatePass bool
  713. if c.Visibility != nil && (collVisibility(*c.Visibility)&CollProtected == 0 || c.Pass != "") {
  714. q.SetIntPtr(c.Visibility, "privacy")
  715. if c.Pass != "" {
  716. updatePass = true
  717. }
  718. }
  719. // WHERE values
  720. q.Where("alias = ? AND owner_id = ?", alias, c.OwnerID)
  721. if q.Updates == "" {
  722. return ErrPostNoUpdatableVals
  723. }
  724. // Find any current domain
  725. var collID int64
  726. var rowsAffected int64
  727. var changed bool
  728. var res sql.Result
  729. err := db.QueryRow("SELECT id FROM collections WHERE alias = ?", alias).Scan(&collID)
  730. if err != nil {
  731. log.Error("Failed selecting from collections: %v. Some things won't work.", err)
  732. }
  733. // Update MathJax value
  734. if c.MathJax {
  735. if db.driverName == driverSQLite {
  736. _, err = db.Exec("INSERT OR REPLACE INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?)", collID, "render_mathjax", "1")
  737. } else {
  738. _, err = db.Exec("INSERT INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?) "+db.upsert("collection_id", "attribute")+" value = ?", collID, "render_mathjax", "1", "1")
  739. }
  740. if err != nil {
  741. log.Error("Unable to insert render_mathjax value: %v", err)
  742. return err
  743. }
  744. } else {
  745. _, err = db.Exec("DELETE FROM collectionattributes WHERE collection_id = ? AND attribute = ?", collID, "render_mathjax")
  746. if err != nil {
  747. log.Error("Unable to delete render_mathjax value: %v", err)
  748. return err
  749. }
  750. }
  751. // Update rest of the collection data
  752. res, err = db.Exec("UPDATE collections SET "+q.Updates+" WHERE "+q.Conditions, q.Params...)
  753. if err != nil {
  754. log.Error("Unable to update collection: %v", err)
  755. return err
  756. }
  757. rowsAffected, _ = res.RowsAffected()
  758. if !changed || rowsAffected == 0 {
  759. // Show the correct error message if nothing was updated
  760. var dummy int
  761. err := db.QueryRow("SELECT 1 FROM collections WHERE alias = ? AND owner_id = ?", alias, c.OwnerID).Scan(&dummy)
  762. switch {
  763. case err == sql.ErrNoRows:
  764. return ErrUnauthorizedEditPost
  765. case err != nil:
  766. log.Error("Failed selecting from collections: %v", err)
  767. }
  768. if !updatePass {
  769. return nil
  770. }
  771. }
  772. if updatePass {
  773. hashedPass, err := auth.HashPass([]byte(c.Pass))
  774. if err != nil {
  775. log.Error("Unable to create hash: %s", err)
  776. return impart.HTTPError{http.StatusInternalServerError, "Could not create password hash."}
  777. }
  778. if db.driverName == driverSQLite {
  779. _, err = db.Exec("INSERT OR REPLACE INTO collectionpasswords (collection_id, password) VALUES ((SELECT id FROM collections WHERE alias = ?), ?)", alias, hashedPass)
  780. } else {
  781. _, err = db.Exec("INSERT INTO collectionpasswords (collection_id, password) VALUES ((SELECT id FROM collections WHERE alias = ?), ?) "+db.upsert("collection_id")+" password = ?", alias, hashedPass, hashedPass)
  782. }
  783. if err != nil {
  784. return err
  785. }
  786. }
  787. return nil
  788. }
  789. const postCols = "id, slug, text_appearance, language, rtl, privacy, owner_id, collection_id, pinned_position, created, updated, view_count, title, content"
  790. // getEditablePost returns a PublicPost with the given ID only if the given
  791. // edit token is valid for the post.
  792. func (db *datastore) GetEditablePost(id, editToken string) (*PublicPost, error) {
  793. // FIXME: code duplicated from getPost()
  794. // TODO: add slight logic difference to getPost / one func
  795. var ownerName sql.NullString
  796. p := &Post{}
  797. row := db.QueryRow("SELECT "+postCols+", (SELECT username FROM users WHERE users.id = posts.owner_id) AS username FROM posts WHERE id = ? LIMIT 1", id)
  798. err := row.Scan(&p.ID, &p.Slug, &p.Font, &p.Language, &p.RTL, &p.Privacy, &p.OwnerID, &p.CollectionID, &p.PinnedPosition, &p.Created, &p.Updated, &p.ViewCount, &p.Title, &p.Content, &ownerName)
  799. switch {
  800. case err == sql.ErrNoRows:
  801. return nil, ErrPostNotFound
  802. case err != nil:
  803. log.Error("Failed selecting from collections: %v", err)
  804. return nil, err
  805. }
  806. if p.Content == "" {
  807. return nil, ErrPostUnpublished
  808. }
  809. res := p.processPost()
  810. if ownerName.Valid {
  811. res.Owner = &PublicUser{Username: ownerName.String}
  812. }
  813. return &res, nil
  814. }
  815. func (db *datastore) PostIDExists(id string) bool {
  816. var dummy bool
  817. err := db.QueryRow("SELECT 1 FROM posts WHERE id = ?", id).Scan(&dummy)
  818. return err == nil && dummy
  819. }
  820. // GetPost gets a public-facing post object from the database. If collectionID
  821. // is > 0, the post will be retrieved by slug and collection ID, rather than
  822. // post ID.
  823. // TODO: break this into two functions:
  824. // - GetPost(id string)
  825. // - GetCollectionPost(slug string, collectionID int64)
  826. func (db *datastore) GetPost(id string, collectionID int64) (*PublicPost, error) {
  827. var ownerName sql.NullString
  828. p := &Post{}
  829. var row *sql.Row
  830. var where string
  831. params := []interface{}{id}
  832. if collectionID > 0 {
  833. where = "slug = ? AND collection_id = ?"
  834. params = append(params, collectionID)
  835. } else {
  836. where = "id = ?"
  837. }
  838. row = db.QueryRow("SELECT "+postCols+", (SELECT username FROM users WHERE users.id = posts.owner_id) AS username FROM posts WHERE "+where+" LIMIT 1", params...)
  839. err := row.Scan(&p.ID, &p.Slug, &p.Font, &p.Language, &p.RTL, &p.Privacy, &p.OwnerID, &p.CollectionID, &p.PinnedPosition, &p.Created, &p.Updated, &p.ViewCount, &p.Title, &p.Content, &ownerName)
  840. switch {
  841. case err == sql.ErrNoRows:
  842. if collectionID > 0 {
  843. return nil, ErrCollectionPageNotFound
  844. }
  845. return nil, ErrPostNotFound
  846. case err != nil:
  847. log.Error("Failed selecting from collections: %v", err)
  848. return nil, err
  849. }
  850. if p.Content == "" {
  851. return nil, ErrPostUnpublished
  852. }
  853. res := p.processPost()
  854. if ownerName.Valid {
  855. res.Owner = &PublicUser{Username: ownerName.String}
  856. }
  857. return &res, nil
  858. }
  859. // TODO: don't duplicate getPost() functionality
  860. func (db *datastore) GetOwnedPost(id string, ownerID int64) (*PublicPost, error) {
  861. p := &Post{}
  862. var row *sql.Row
  863. where := "id = ? AND owner_id = ?"
  864. params := []interface{}{id, ownerID}
  865. row = db.QueryRow("SELECT "+postCols+" FROM posts WHERE "+where+" LIMIT 1", params...)
  866. err := row.Scan(&p.ID, &p.Slug, &p.Font, &p.Language, &p.RTL, &p.Privacy, &p.OwnerID, &p.CollectionID, &p.PinnedPosition, &p.Created, &p.Updated, &p.ViewCount, &p.Title, &p.Content)
  867. switch {
  868. case err == sql.ErrNoRows:
  869. return nil, ErrPostNotFound
  870. case err != nil:
  871. log.Error("Failed selecting from collections: %v", err)
  872. return nil, err
  873. }
  874. if p.Content == "" {
  875. return nil, ErrPostUnpublished
  876. }
  877. res := p.processPost()
  878. return &res, nil
  879. }
  880. func (db *datastore) GetPostProperty(id string, collectionID int64, property string) (interface{}, error) {
  881. propSelects := map[string]string{
  882. "views": "view_count AS views",
  883. }
  884. selectQuery, ok := propSelects[property]
  885. if !ok {
  886. return nil, impart.HTTPError{http.StatusBadRequest, fmt.Sprintf("Invalid property: %s.", property)}
  887. }
  888. var res interface{}
  889. var row *sql.Row
  890. if collectionID != 0 {
  891. row = db.QueryRow("SELECT "+selectQuery+" FROM posts WHERE slug = ? AND collection_id = ? LIMIT 1", id, collectionID)
  892. } else {
  893. row = db.QueryRow("SELECT "+selectQuery+" FROM posts WHERE id = ? LIMIT 1", id)
  894. }
  895. err := row.Scan(&res)
  896. switch {
  897. case err == sql.ErrNoRows:
  898. return nil, impart.HTTPError{http.StatusNotFound, "Post not found."}
  899. case err != nil:
  900. log.Error("Failed selecting post: %v", err)
  901. return nil, err
  902. }
  903. return res, nil
  904. }
  905. // GetPostsCount modifies the CollectionObj to include the correct number of
  906. // standard (non-pinned) posts. It will return future posts if `includeFuture`
  907. // is true.
  908. func (db *datastore) GetPostsCount(c *CollectionObj, includeFuture bool) {
  909. var count int64
  910. timeCondition := ""
  911. if !includeFuture {
  912. timeCondition = "AND created <= " + db.now()
  913. }
  914. err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE collection_id = ? AND pinned_position IS NULL "+timeCondition, c.ID).Scan(&count)
  915. switch {
  916. case err == sql.ErrNoRows:
  917. c.TotalPosts = 0
  918. case err != nil:
  919. log.Error("Failed selecting from collections: %v", err)
  920. c.TotalPosts = 0
  921. }
  922. c.TotalPosts = int(count)
  923. }
  924. // GetPosts retrieves all standard (non-pinned) posts for the given Collection.
  925. // It will return future posts if `includeFuture` is true.
  926. // TODO: change includeFuture to isOwner, since that's how it's used
  927. func (db *datastore) GetPosts(c *Collection, page int, includeFuture, forceRecentFirst bool) (*[]PublicPost, error) {
  928. collID := c.ID
  929. cf := c.NewFormat()
  930. order := "DESC"
  931. if cf.Ascending() && !forceRecentFirst {
  932. order = "ASC"
  933. }
  934. pagePosts := cf.PostsPerPage()
  935. start := page*pagePosts - pagePosts
  936. if page == 0 {
  937. start = 0
  938. pagePosts = 1000
  939. }
  940. limitStr := ""
  941. if page > 0 {
  942. limitStr = fmt.Sprintf(" LIMIT %d, %d", start, pagePosts)
  943. }
  944. timeCondition := ""
  945. if !includeFuture {
  946. timeCondition = "AND created <= " + db.now()
  947. }
  948. rows, err := db.Query("SELECT "+postCols+" FROM posts WHERE collection_id = ? AND pinned_position IS NULL "+timeCondition+" ORDER BY created "+order+limitStr, collID)
  949. if err != nil {
  950. log.Error("Failed selecting from posts: %v", err)
  951. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve collection posts."}
  952. }
  953. defer rows.Close()
  954. // TODO: extract this common row scanning logic for queries using `postCols`
  955. posts := []PublicPost{}
  956. for rows.Next() {
  957. p := &Post{}
  958. err = rows.Scan(&p.ID, &p.Slug, &p.Font, &p.Language, &p.RTL, &p.Privacy, &p.OwnerID, &p.CollectionID, &p.PinnedPosition, &p.Created, &p.Updated, &p.ViewCount, &p.Title, &p.Content)
  959. if err != nil {
  960. log.Error("Failed scanning row: %v", err)
  961. break
  962. }
  963. p.extractData()
  964. p.formatContent(c, includeFuture)
  965. posts = append(posts, p.processPost())
  966. }
  967. err = rows.Err()
  968. if err != nil {
  969. log.Error("Error after Next() on rows: %v", err)
  970. }
  971. return &posts, nil
  972. }
  973. // GetPostsTagged retrieves all posts on the given Collection that contain the
  974. // given tag.
  975. // It will return future posts if `includeFuture` is true.
  976. // TODO: change includeFuture to isOwner, since that's how it's used
  977. func (db *datastore) GetPostsTagged(c *Collection, tag string, page int, includeFuture bool) (*[]PublicPost, error) {
  978. collID := c.ID
  979. cf := c.NewFormat()
  980. order := "DESC"
  981. if cf.Ascending() {
  982. order = "ASC"
  983. }
  984. pagePosts := cf.PostsPerPage()
  985. start := page*pagePosts - pagePosts
  986. if page == 0 {
  987. start = 0
  988. pagePosts = 1000
  989. }
  990. limitStr := ""
  991. if page > 0 {
  992. limitStr = fmt.Sprintf(" LIMIT %d, %d", start, pagePosts)
  993. }
  994. timeCondition := ""
  995. if !includeFuture {
  996. timeCondition = "AND created <= " + db.now()
  997. }
  998. var rows *sql.Rows
  999. var err error
  1000. if db.driverName == driverSQLite {
  1001. rows, err = db.Query("SELECT "+postCols+" FROM posts WHERE collection_id = ? AND LOWER(content) regexp ? "+timeCondition+" ORDER BY created "+order+limitStr, collID, `.*#`+strings.ToLower(tag)+`\b.*`)
  1002. } else {
  1003. rows, err = db.Query("SELECT "+postCols+" FROM posts WHERE collection_id = ? AND LOWER(content) RLIKE ? "+timeCondition+" ORDER BY created "+order+limitStr, collID, "#"+strings.ToLower(tag)+"[[:>:]]")
  1004. }
  1005. if err != nil {
  1006. log.Error("Failed selecting from posts: %v", err)
  1007. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve collection posts."}
  1008. }
  1009. defer rows.Close()
  1010. // TODO: extract this common row scanning logic for queries using `postCols`
  1011. posts := []PublicPost{}
  1012. for rows.Next() {
  1013. p := &Post{}
  1014. err = rows.Scan(&p.ID, &p.Slug, &p.Font, &p.Language, &p.RTL, &p.Privacy, &p.OwnerID, &p.CollectionID, &p.PinnedPosition, &p.Created, &p.Updated, &p.ViewCount, &p.Title, &p.Content)
  1015. if err != nil {
  1016. log.Error("Failed scanning row: %v", err)
  1017. break
  1018. }
  1019. p.extractData()
  1020. p.formatContent(c, includeFuture)
  1021. posts = append(posts, p.processPost())
  1022. }
  1023. err = rows.Err()
  1024. if err != nil {
  1025. log.Error("Error after Next() on rows: %v", err)
  1026. }
  1027. return &posts, nil
  1028. }
  1029. func (db *datastore) GetAPFollowers(c *Collection) (*[]RemoteUser, error) {
  1030. rows, err := db.Query("SELECT actor_id, inbox, shared_inbox FROM remotefollows f INNER JOIN remoteusers u ON f.remote_user_id = u.id WHERE collection_id = ?", c.ID)
  1031. if err != nil {
  1032. log.Error("Failed selecting from followers: %v", err)
  1033. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve followers."}
  1034. }
  1035. defer rows.Close()
  1036. followers := []RemoteUser{}
  1037. for rows.Next() {
  1038. f := RemoteUser{}
  1039. err = rows.Scan(&f.ActorID, &f.Inbox, &f.SharedInbox)
  1040. followers = append(followers, f)
  1041. }
  1042. return &followers, nil
  1043. }
  1044. // CanCollect returns whether or not the given user can add the given post to a
  1045. // collection. This is true when a post is already owned by the user.
  1046. // NOTE: this is currently only used to potentially add owned posts to a
  1047. // collection. This has the SIDE EFFECT of also generating a slug for the post.
  1048. // FIXME: make this side effect more explicit (or extract it)
  1049. func (db *datastore) CanCollect(cpr *ClaimPostRequest, userID int64) bool {
  1050. var title, content string
  1051. var lang sql.NullString
  1052. err := db.QueryRow("SELECT title, content, language FROM posts WHERE id = ? AND owner_id = ?", cpr.ID, userID).Scan(&title, &content, &lang)
  1053. switch {
  1054. case err == sql.ErrNoRows:
  1055. return false
  1056. case err != nil:
  1057. log.Error("Failed on post CanCollect(%s, %d): %v", cpr.ID, userID, err)
  1058. return false
  1059. }
  1060. // Since we have the post content and the post is collectable, generate the
  1061. // post's slug now.
  1062. cpr.Slug = getSlugFromPost(title, content, lang.String)
  1063. return true
  1064. }
  1065. func (db *datastore) AttemptClaim(p *ClaimPostRequest, query string, params []interface{}, slugIdx int) (sql.Result, error) {
  1066. qRes, err := db.Exec(query, params...)
  1067. if err != nil {
  1068. if db.isDuplicateKeyErr(err) && slugIdx > -1 {
  1069. s := id.GenSafeUniqueSlug(p.Slug)
  1070. if s == p.Slug {
  1071. // Sanity check to prevent infinite recursion
  1072. return qRes, fmt.Errorf("GenSafeUniqueSlug generated nothing unique: %s", s)
  1073. }
  1074. p.Slug = s
  1075. params[slugIdx] = p.Slug
  1076. return db.AttemptClaim(p, query, params, slugIdx)
  1077. }
  1078. return qRes, fmt.Errorf("attemptClaim: %s", err)
  1079. }
  1080. return qRes, nil
  1081. }
  1082. func (db *datastore) DispersePosts(userID int64, postIDs []string) (*[]ClaimPostResult, error) {
  1083. postClaimReqs := map[string]bool{}
  1084. res := []ClaimPostResult{}
  1085. for i := range postIDs {
  1086. postID := postIDs[i]
  1087. r := ClaimPostResult{Code: 0, ErrorMessage: ""}
  1088. // Perform post validation
  1089. if postID == "" {
  1090. r.ErrorMessage = "Missing post ID. "
  1091. }
  1092. if _, ok := postClaimReqs[postID]; ok {
  1093. r.Code = 429
  1094. r.ErrorMessage = "You've already tried anonymizing this post."
  1095. r.ID = postID
  1096. res = append(res, r)
  1097. continue
  1098. }
  1099. postClaimReqs[postID] = true
  1100. var err error
  1101. // Get full post information to return
  1102. var fullPost *PublicPost
  1103. fullPost, err = db.GetPost(postID, 0)
  1104. if err != nil {
  1105. if err, ok := err.(impart.HTTPError); ok {
  1106. r.Code = err.Status
  1107. r.ErrorMessage = err.Message
  1108. r.ID = postID
  1109. res = append(res, r)
  1110. continue
  1111. } else {
  1112. log.Error("Error getting post in dispersePosts: %v", err)
  1113. }
  1114. }
  1115. if fullPost.OwnerID.Int64 != userID {
  1116. r.Code = http.StatusConflict
  1117. r.ErrorMessage = "Post is already owned by someone else."
  1118. r.ID = postID
  1119. res = append(res, r)
  1120. continue
  1121. }
  1122. var qRes sql.Result
  1123. var query string
  1124. var params []interface{}
  1125. // Do AND owner_id = ? for sanity.
  1126. // This should've been caught and returned with a good error message
  1127. // just above.
  1128. query = "UPDATE posts SET collection_id = NULL WHERE id = ? AND owner_id = ?"
  1129. params = []interface{}{postID, userID}
  1130. qRes, err = db.Exec(query, params...)
  1131. if err != nil {
  1132. r.Code = http.StatusInternalServerError
  1133. r.ErrorMessage = "A glitch happened on our end."
  1134. r.ID = postID
  1135. res = append(res, r)
  1136. log.Error("dispersePosts (post %s): %v", postID, err)
  1137. continue
  1138. }
  1139. // Post was successfully dispersed
  1140. r.Code = http.StatusOK
  1141. r.Post = fullPost
  1142. rowsAffected, _ := qRes.RowsAffected()
  1143. if rowsAffected == 0 {
  1144. // This was already claimed, but return 200
  1145. r.Code = http.StatusOK
  1146. }
  1147. res = append(res, r)
  1148. }
  1149. return &res, nil
  1150. }
  1151. func (db *datastore) ClaimPosts(userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error) {
  1152. postClaimReqs := map[string]bool{}
  1153. res := []ClaimPostResult{}
  1154. postCollAlias := collAlias
  1155. for i := range *posts {
  1156. p := (*posts)[i]
  1157. if &p == nil {
  1158. continue
  1159. }
  1160. r := ClaimPostResult{Code: 0, ErrorMessage: ""}
  1161. // Perform post validation
  1162. if p.ID == "" {
  1163. r.ErrorMessage = "Missing post ID `id`. "
  1164. }
  1165. if _, ok := postClaimReqs[p.ID]; ok {
  1166. r.Code = 429
  1167. r.ErrorMessage = "You've already tried claiming this post."
  1168. r.ID = p.ID
  1169. res = append(res, r)
  1170. continue
  1171. }
  1172. postClaimReqs[p.ID] = true
  1173. canCollect := db.CanCollect(&p, userID)
  1174. if !canCollect && p.Token == "" {
  1175. // TODO: ensure post isn't owned by anyone else when a valid modify
  1176. // token is given.
  1177. r.ErrorMessage += "Missing post Edit Token `token`."
  1178. }
  1179. if r.ErrorMessage != "" {
  1180. // Post validate failed
  1181. r.Code = http.StatusBadRequest
  1182. r.ID = p.ID
  1183. res = append(res, r)
  1184. continue
  1185. }
  1186. var err error
  1187. var qRes sql.Result
  1188. var query string
  1189. var params []interface{}
  1190. var slugIdx int = -1
  1191. var coll *Collection
  1192. if collAlias == "" {
  1193. // Posts are being claimed at /posts/claim, not
  1194. // /collections/{alias}/collect, so use given individual collection
  1195. // to associate post with.
  1196. postCollAlias = p.CollectionAlias
  1197. }
  1198. if postCollAlias != "" {
  1199. // Associate this post with a collection
  1200. if p.CreateCollection {
  1201. // This is a new collection
  1202. // TODO: consider removing this. This seriously complicates this
  1203. // method and adds another (unnecessary?) logic path.
  1204. coll, err = db.CreateCollection(postCollAlias, "", userID)
  1205. if err != nil {
  1206. if err, ok := err.(impart.HTTPError); ok {
  1207. r.Code = err.Status
  1208. r.ErrorMessage = err.Message
  1209. } else {
  1210. r.Code = http.StatusInternalServerError
  1211. r.ErrorMessage = "Unknown error occurred creating collection"
  1212. }
  1213. r.ID = p.ID
  1214. res = append(res, r)
  1215. continue
  1216. }
  1217. } else {
  1218. // Attempt to add to existing collection
  1219. coll, err = db.GetCollection(postCollAlias)
  1220. if err != nil {
  1221. if err, ok := err.(impart.HTTPError); ok {
  1222. if err.Status == http.StatusNotFound {
  1223. // Show obfuscated "forbidden" response, as if attempting to add to an
  1224. // unowned blog.
  1225. r.Code = ErrForbiddenCollection.Status
  1226. r.ErrorMessage = ErrForbiddenCollection.Message
  1227. } else {
  1228. r.Code = err.Status
  1229. r.ErrorMessage = err.Message
  1230. }
  1231. } else {
  1232. r.Code = http.StatusInternalServerError
  1233. r.ErrorMessage = "Unknown error occurred claiming post with collection"
  1234. }
  1235. r.ID = p.ID
  1236. res = append(res, r)
  1237. continue
  1238. }
  1239. if coll.OwnerID != userID {
  1240. r.Code = ErrForbiddenCollection.Status
  1241. r.ErrorMessage = ErrForbiddenCollection.Message
  1242. r.ID = p.ID
  1243. res = append(res, r)
  1244. continue
  1245. }
  1246. }
  1247. if p.Slug == "" {
  1248. p.Slug = p.ID
  1249. }
  1250. if canCollect {
  1251. // User already owns this post, so just add it to the given
  1252. // collection.
  1253. query = "UPDATE posts SET collection_id = ?, slug = ? WHERE id = ? AND owner_id = ?"
  1254. params = []interface{}{coll.ID, p.Slug, p.ID, userID}
  1255. slugIdx = 1
  1256. } else {
  1257. query = "UPDATE posts SET owner_id = ?, collection_id = ?, slug = ? WHERE id = ? AND modify_token = ? AND owner_id IS NULL"
  1258. params = []interface{}{userID, coll.ID, p.Slug, p.ID, p.Token}
  1259. slugIdx = 2
  1260. }
  1261. } else {
  1262. query = "UPDATE posts SET owner_id = ? WHERE id = ? AND modify_token = ? AND owner_id IS NULL"
  1263. params = []interface{}{userID, p.ID, p.Token}
  1264. }
  1265. qRes, err = db.AttemptClaim(&p, query, params, slugIdx)
  1266. if err != nil {
  1267. r.Code = http.StatusInternalServerError
  1268. r.ErrorMessage = "An unknown error occurred."
  1269. r.ID = p.ID
  1270. res = append(res, r)
  1271. log.Error("claimPosts (post %s): %v", p.ID, err)
  1272. continue
  1273. }
  1274. // Get full post information to return
  1275. var fullPost *PublicPost
  1276. if p.Token != "" {
  1277. fullPost, err = db.GetEditablePost(p.ID, p.Token)
  1278. } else {
  1279. fullPost, err = db.GetPost(p.ID, 0)
  1280. }
  1281. if err != nil {
  1282. if err, ok := err.(impart.HTTPError); ok {
  1283. r.Code = err.Status
  1284. r.ErrorMessage = err.Message
  1285. r.ID = p.ID
  1286. res = append(res, r)
  1287. continue
  1288. }
  1289. }
  1290. if fullPost.OwnerID.Int64 != userID {
  1291. r.Code = http.StatusConflict
  1292. r.ErrorMessage = "Post is already owned by someone else."
  1293. r.ID = p.ID
  1294. res = append(res, r)
  1295. continue
  1296. }
  1297. // Post was successfully claimed
  1298. r.Code = http.StatusOK
  1299. r.Post = fullPost
  1300. if coll != nil {
  1301. r.Post.Collection = &CollectionObj{Collection: *coll}
  1302. }
  1303. rowsAffected, _ := qRes.RowsAffected()
  1304. if rowsAffected == 0 {
  1305. // This was already claimed, but return 200
  1306. r.Code = http.StatusOK
  1307. }
  1308. res = append(res, r)
  1309. }
  1310. return &res, nil
  1311. }
  1312. func (db *datastore) UpdatePostPinState(pinned bool, postID string, collID, ownerID, pos int64) error {
  1313. if pos <= 0 || pos > 20 {
  1314. pos = db.GetLastPinnedPostPos(collID) + 1
  1315. if pos == -1 {
  1316. pos = 1
  1317. }
  1318. }
  1319. var err error
  1320. if pinned {
  1321. _, err = db.Exec("UPDATE posts SET pinned_position = ? WHERE id = ?", pos, postID)
  1322. } else {
  1323. _, err = db.Exec("UPDATE posts SET pinned_position = NULL WHERE id = ?", postID)
  1324. }
  1325. if err != nil {
  1326. log.Error("Unable to update pinned post: %v", err)
  1327. return err
  1328. }
  1329. return nil
  1330. }
  1331. func (db *datastore) GetLastPinnedPostPos(collID int64) int64 {
  1332. var lastPos sql.NullInt64
  1333. err := db.QueryRow("SELECT MAX(pinned_position) FROM posts WHERE collection_id = ? AND pinned_position IS NOT NULL", collID).Scan(&lastPos)
  1334. switch {
  1335. case err == sql.ErrNoRows:
  1336. return -1
  1337. case err != nil:
  1338. log.Error("Failed selecting from posts: %v", err)
  1339. return -1
  1340. }
  1341. if !lastPos.Valid {
  1342. return -1
  1343. }
  1344. return lastPos.Int64
  1345. }
  1346. func (db *datastore) GetPinnedPosts(coll *CollectionObj) (*[]PublicPost, error) {
  1347. // FIXME: sqlite-backed instances don't include ellipsis on truncated titles
  1348. rows, err := db.Query("SELECT id, slug, title, "+db.clip("content", 80)+", pinned_position FROM posts WHERE collection_id = ? AND pinned_position IS NOT NULL ORDER BY pinned_position ASC", coll.ID)
  1349. if err != nil {
  1350. log.Error("Failed selecting pinned posts: %v", err)
  1351. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve pinned posts."}
  1352. }
  1353. defer rows.Close()
  1354. posts := []PublicPost{}
  1355. for rows.Next() {
  1356. p := &Post{}
  1357. err = rows.Scan(&p.ID, &p.Slug, &p.Title, &p.Content, &p.PinnedPosition)
  1358. if err != nil {
  1359. log.Error("Failed scanning row: %v", err)
  1360. break
  1361. }
  1362. p.extractData()
  1363. pp := p.processPost()
  1364. pp.Collection = coll
  1365. posts = append(posts, pp)
  1366. }
  1367. return &posts, nil
  1368. }
  1369. func (db *datastore) GetCollections(u *User) (*[]Collection, error) {
  1370. rows, err := db.Query("SELECT id, alias, title, description, privacy, view_count FROM collections WHERE owner_id = ? ORDER BY id ASC", u.ID)
  1371. if err != nil {
  1372. log.Error("Failed selecting from collections: %v", err)
  1373. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user collections."}
  1374. }
  1375. defer rows.Close()
  1376. colls := []Collection{}
  1377. for rows.Next() {
  1378. c := Collection{}
  1379. err = rows.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &c.Visibility, &c.Views)
  1380. if err != nil {
  1381. log.Error("Failed scanning row: %v", err)
  1382. break
  1383. }
  1384. c.URL = c.CanonicalURL()
  1385. c.Public = c.IsPublic()
  1386. colls = append(colls, c)
  1387. }
  1388. err = rows.Err()
  1389. if err != nil {
  1390. log.Error("Error after Next() on rows: %v", err)
  1391. }
  1392. return &colls, nil
  1393. }
  1394. func (db *datastore) GetPublishableCollections(u *User) (*[]Collection, error) {
  1395. c, err := db.GetCollections(u)
  1396. if err != nil {
  1397. return nil, err
  1398. }
  1399. if len(*c) == 0 {
  1400. return nil, impart.HTTPError{http.StatusInternalServerError, "You don't seem to have any blogs; they might've moved to another account. Try logging out and logging into your other account."}
  1401. }
  1402. return c, nil
  1403. }
  1404. func (db *datastore) GetMeStats(u *User) userMeStats {
  1405. s := userMeStats{}
  1406. // User counts
  1407. colls, _ := db.GetUserCollectionCount(u.ID)
  1408. s.TotalCollections = colls
  1409. var articles, collPosts uint64
  1410. err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE owner_id = ? AND collection_id IS NULL", u.ID).Scan(&articles)
  1411. if err != nil && err != sql.ErrNoRows {
  1412. log.Error("Couldn't get articles count for user %d: %v", u.ID, err)
  1413. }
  1414. s.TotalArticles = articles
  1415. err = db.QueryRow("SELECT COUNT(*) FROM posts WHERE owner_id = ? AND collection_id IS NOT NULL", u.ID).Scan(&collPosts)
  1416. if err != nil && err != sql.ErrNoRows {
  1417. log.Error("Couldn't get coll posts count for user %d: %v", u.ID, err)
  1418. }
  1419. s.CollectionPosts = collPosts
  1420. return s
  1421. }
  1422. func (db *datastore) GetTotalCollections() (collCount int64, err error) {
  1423. err = db.QueryRow(`SELECT COUNT(*) FROM collections`).Scan(&collCount)
  1424. if err != nil {
  1425. log.Error("Unable to fetch collections count: %v", err)
  1426. }
  1427. return
  1428. }
  1429. func (db *datastore) GetTotalPosts() (postCount int64, err error) {
  1430. err = db.QueryRow(`SELECT COUNT(*) FROM posts`).Scan(&postCount)
  1431. if err != nil {
  1432. log.Error("Unable to fetch posts count: %v", err)
  1433. }
  1434. return
  1435. }
  1436. func (db *datastore) GetTopPosts(u *User, alias string) (*[]PublicPost, error) {
  1437. params := []interface{}{u.ID}
  1438. where := ""
  1439. if alias != "" {
  1440. where = " AND alias = ?"
  1441. params = append(params, alias)
  1442. }
  1443. rows, err := db.Query("SELECT p.id, p.slug, p.view_count, p.title, c.alias, c.title, c.description, c.view_count FROM posts p LEFT JOIN collections c ON p.collection_id = c.id WHERE p.owner_id = ?"+where+" ORDER BY p.view_count DESC, created DESC LIMIT 25", params...)
  1444. if err != nil {
  1445. log.Error("Failed selecting from posts: %v", err)
  1446. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user top posts."}
  1447. }
  1448. defer rows.Close()
  1449. posts := []PublicPost{}
  1450. var gotErr bool
  1451. for rows.Next() {
  1452. p := Post{}
  1453. c := Collection{}
  1454. var alias, title, description sql.NullString
  1455. var views sql.NullInt64
  1456. err = rows.Scan(&p.ID, &p.Slug, &p.ViewCount, &p.Title, &alias, &title, &description, &views)
  1457. if err != nil {
  1458. log.Error("Failed scanning User.getPosts() row: %v", err)
  1459. gotErr = true
  1460. break
  1461. }
  1462. p.extractData()
  1463. pubPost := p.processPost()
  1464. if alias.Valid && alias.String != "" {
  1465. c.Alias = alias.String
  1466. c.Title = title.String
  1467. c.Description = description.String
  1468. c.Views = views.Int64
  1469. pubPost.Collection = &CollectionObj{Collection: c}
  1470. }
  1471. posts = append(posts, pubPost)
  1472. }
  1473. err = rows.Err()
  1474. if err != nil {
  1475. log.Error("Error after Next() on rows: %v", err)
  1476. }
  1477. if gotErr && len(posts) == 0 {
  1478. // There were a lot of errors
  1479. return nil, impart.HTTPError{http.StatusInternalServerError, "Unable to get data."}
  1480. }
  1481. return &posts, nil
  1482. }
  1483. func (db *datastore) GetAnonymousPosts(u *User) (*[]PublicPost, error) {
  1484. rows, err := db.Query("SELECT id, view_count, title, created, updated, content FROM posts WHERE owner_id = ? AND collection_id IS NULL ORDER BY created DESC", u.ID)
  1485. if err != nil {
  1486. log.Error("Failed selecting from posts: %v", err)
  1487. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user anonymous posts."}
  1488. }
  1489. defer rows.Close()
  1490. posts := []PublicPost{}
  1491. for rows.Next() {
  1492. p := Post{}
  1493. err = rows.Scan(&p.ID, &p.ViewCount, &p.Title, &p.Created, &p.Updated, &p.Content)
  1494. if err != nil {
  1495. log.Error("Failed scanning row: %v", err)
  1496. break
  1497. }
  1498. p.extractData()
  1499. posts = append(posts, p.processPost())
  1500. }
  1501. err = rows.Err()
  1502. if err != nil {
  1503. log.Error("Error after Next() on rows: %v", err)
  1504. }
  1505. return &posts, nil
  1506. }
  1507. func (db *datastore) GetUserPosts(u *User) (*[]PublicPost, error) {
  1508. rows, err := db.Query("SELECT p.id, p.slug, p.view_count, p.title, p.created, p.updated, p.content, p.text_appearance, p.language, p.rtl, c.alias, c.title, c.description, c.view_count FROM posts p LEFT JOIN collections c ON collection_id = c.id WHERE p.owner_id = ? ORDER BY created ASC", u.ID)
  1509. if err != nil {
  1510. log.Error("Failed selecting from posts: %v", err)
  1511. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user posts."}
  1512. }
  1513. defer rows.Close()
  1514. posts := []PublicPost{}
  1515. var gotErr bool
  1516. for rows.Next() {
  1517. p := Post{}
  1518. c := Collection{}
  1519. var alias, title, description sql.NullString
  1520. var views sql.NullInt64
  1521. err = rows.Scan(&p.ID, &p.Slug, &p.ViewCount, &p.Title, &p.Created, &p.Updated, &p.Content, &p.Font, &p.Language, &p.RTL, &alias, &title, &description, &views)
  1522. if err != nil {
  1523. log.Error("Failed scanning User.getPosts() row: %v", err)
  1524. gotErr = true
  1525. break
  1526. }
  1527. p.extractData()
  1528. pubPost := p.processPost()
  1529. if alias.Valid && alias.String != "" {
  1530. c.Alias = alias.String
  1531. c.Title = title.String
  1532. c.Description = description.String
  1533. c.Views = views.Int64
  1534. pubPost.Collection = &CollectionObj{Collection: c}
  1535. }
  1536. posts = append(posts, pubPost)
  1537. }
  1538. err = rows.Err()
  1539. if err != nil {
  1540. log.Error("Error after Next() on rows: %v", err)
  1541. }
  1542. if gotErr && len(posts) == 0 {
  1543. // There were a lot of errors
  1544. return nil, impart.HTTPError{http.StatusInternalServerError, "Unable to get data."}
  1545. }
  1546. return &posts, nil
  1547. }
  1548. func (db *datastore) GetUserPostsCount(userID int64) int64 {
  1549. var count int64
  1550. err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE owner_id = ?", userID).Scan(&count)
  1551. switch {
  1552. case err == sql.ErrNoRows:
  1553. return 0
  1554. case err != nil:
  1555. log.Error("Failed selecting posts count for user %d: %v", userID, err)
  1556. return 0
  1557. }
  1558. return count
  1559. }
  1560. // ChangeSettings takes a User and applies the changes in the given
  1561. // userSettings, MODIFYING THE USER with successful changes.
  1562. func (db *datastore) ChangeSettings(app *App, u *User, s *userSettings) error {
  1563. var errPass error
  1564. q := query.NewUpdate()
  1565. // Update email if given
  1566. if s.Email != "" {
  1567. encEmail, err := data.Encrypt(app.keys.emailKey, s.Email)
  1568. if err != nil {
  1569. log.Error("Couldn't encrypt email %s: %s\n", s.Email, err)
  1570. return impart.HTTPError{http.StatusInternalServerError, "Unable to encrypt email address."}
  1571. }
  1572. q.SetBytes(encEmail, "email")
  1573. // Update the email if something goes awry updating the password
  1574. defer func() {
  1575. if errPass != nil {
  1576. db.UpdateEncryptedUserEmail(u.ID, encEmail)
  1577. }
  1578. }()
  1579. u.Email = zero.StringFrom(s.Email)
  1580. }
  1581. // Update username if given
  1582. var newUsername string
  1583. if s.Username != "" {
  1584. var ie *impart.HTTPError
  1585. newUsername, ie = getValidUsername(app, s.Username, u.Username)
  1586. if ie != nil {
  1587. // Username is invalid
  1588. return *ie
  1589. }
  1590. if !author.IsValidUsername(app.cfg, newUsername) {
  1591. // Ensure the username is syntactically correct.
  1592. return impart.HTTPError{http.StatusPreconditionFailed, "Username isn't valid."}
  1593. }
  1594. t, err := db.Begin()
  1595. if err != nil {
  1596. log.Error("Couldn't start username change transaction: %v", err)
  1597. return err
  1598. }
  1599. _, err = t.Exec("UPDATE users SET username = ? WHERE id = ?", newUsername, u.ID)
  1600. if err != nil {
  1601. t.Rollback()
  1602. if db.isDuplicateKeyErr(err) {
  1603. return impart.HTTPError{http.StatusConflict, "Username is already taken."}
  1604. }
  1605. log.Error("Unable to update users table: %v", err)
  1606. return ErrInternalGeneral
  1607. }
  1608. _, err = t.Exec("UPDATE collections SET alias = ? WHERE alias = ? AND owner_id = ?", newUsername, u.Username, u.ID)
  1609. if err != nil {
  1610. t.Rollback()
  1611. if db.isDuplicateKeyErr(err) {
  1612. return impart.HTTPError{http.StatusConflict, "Username is already taken."}
  1613. }
  1614. log.Error("Unable to update collection: %v", err)
  1615. return ErrInternalGeneral
  1616. }
  1617. // Keep track of name changes for redirection
  1618. db.RemoveCollectionRedirect(t, newUsername)
  1619. _, err = t.Exec("UPDATE collectionredirects SET new_alias = ? WHERE new_alias = ?", newUsername, u.Username)
  1620. if err != nil {
  1621. log.Error("Unable to update collectionredirects: %v", err)
  1622. }
  1623. _, err = t.Exec("INSERT INTO collectionredirects (prev_alias, new_alias) VALUES (?, ?)", u.Username, newUsername)
  1624. if err != nil {
  1625. log.Error("Unable to add new collectionredirect: %v", err)
  1626. }
  1627. err = t.Commit()
  1628. if err != nil {
  1629. t.Rollback()
  1630. log.Error("Rolling back after Commit(): %v\n", err)
  1631. return err
  1632. }
  1633. u.Username = newUsername
  1634. }
  1635. // Update passphrase if given
  1636. if s.NewPass != "" {
  1637. // Check if user has already set a password
  1638. var err error
  1639. u.HasPass, err = db.IsUserPassSet(u.ID)
  1640. if err != nil {
  1641. errPass = impart.HTTPError{http.StatusInternalServerError, "Unable to retrieve user data."}
  1642. return errPass
  1643. }
  1644. if u.HasPass {
  1645. // Check if currently-set password is correct
  1646. hashedPass := u.HashedPass
  1647. if len(hashedPass) == 0 {
  1648. authUser, err := db.GetUserForAuthByID(u.ID)
  1649. if err != nil {
  1650. errPass = err
  1651. return errPass
  1652. }
  1653. hashedPass = authUser.HashedPass
  1654. }
  1655. if !auth.Authenticated(hashedPass, []byte(s.OldPass)) {
  1656. errPass = impart.HTTPError{http.StatusUnauthorized, "Incorrect password."}
  1657. return errPass
  1658. }
  1659. }
  1660. hashedPass, err := auth.HashPass([]byte(s.NewPass))
  1661. if err != nil {
  1662. errPass = impart.HTTPError{http.StatusInternalServerError, "Could not create password hash."}
  1663. return errPass
  1664. }
  1665. q.SetBytes(hashedPass, "password")
  1666. }
  1667. // WHERE values
  1668. q.Append(u.ID)
  1669. if q.Updates == "" {
  1670. if s.Username == "" {
  1671. return ErrPostNoUpdatableVals
  1672. }
  1673. // Nothing to update except username. That was successful, so return now.
  1674. return nil
  1675. }
  1676. res, err := db.Exec("UPDATE users SET "+q.Updates+" WHERE id = ?", q.Params...)
  1677. if err != nil {
  1678. log.Error("Unable to update collection: %v", err)
  1679. return err
  1680. }
  1681. rowsAffected, _ := res.RowsAffected()
  1682. if rowsAffected == 0 {
  1683. // Show the correct error message if nothing was updated
  1684. var dummy int
  1685. err := db.QueryRow("SELECT 1 FROM users WHERE id = ?", u.ID).Scan(&dummy)
  1686. switch {
  1687. case err == sql.ErrNoRows:
  1688. return ErrUnauthorizedGeneral
  1689. case err != nil:
  1690. log.Error("Failed selecting from users: %v", err)
  1691. }
  1692. return nil
  1693. }
  1694. if s.NewPass != "" && !u.HasPass {
  1695. u.HasPass = true
  1696. }
  1697. return nil
  1698. }
  1699. func (db *datastore) ChangePassphrase(userID int64, sudo bool, curPass string, hashedPass []byte) error {
  1700. var dbPass []byte
  1701. err := db.QueryRow("SELECT password FROM users WHERE id = ?", userID).Scan(&dbPass)
  1702. switch {
  1703. case err == sql.ErrNoRows:
  1704. return ErrUserNotFound
  1705. case err != nil:
  1706. log.Error("Couldn't SELECT user password for change: %v", err)
  1707. return err
  1708. }
  1709. if !sudo && !auth.Authenticated(dbPass, []byte(curPass)) {
  1710. return impart.HTTPError{http.StatusUnauthorized, "Incorrect password."}
  1711. }
  1712. _, err = db.Exec("UPDATE users SET password = ? WHERE id = ?", hashedPass, userID)
  1713. if err != nil {
  1714. log.Error("Could not update passphrase: %v", err)
  1715. return err
  1716. }
  1717. return nil
  1718. }
  1719. func (db *datastore) RemoveCollectionRedirect(t *sql.Tx, alias string) error {
  1720. _, err := t.Exec("DELETE FROM collectionredirects WHERE prev_alias = ?", alias)
  1721. if err != nil {
  1722. log.Error("Unable to delete from collectionredirects: %v", err)
  1723. return err
  1724. }
  1725. return nil
  1726. }
  1727. func (db *datastore) GetCollectionRedirect(alias string) (new string) {
  1728. row := db.QueryRow("SELECT new_alias FROM collectionredirects WHERE prev_alias = ?", alias)
  1729. err := row.Scan(&new)
  1730. if err != nil && err != sql.ErrNoRows {
  1731. log.Error("Failed selecting from collectionredirects: %v", err)
  1732. }
  1733. return
  1734. }
  1735. func (db *datastore) DeleteCollection(alias string, userID int64) error {
  1736. c := &Collection{Alias: alias}
  1737. var username string
  1738. row := db.QueryRow("SELECT username FROM users WHERE id = ?", userID)
  1739. err := row.Scan(&username)
  1740. if err != nil {
  1741. return err
  1742. }
  1743. // Ensure user isn't deleting their main blog
  1744. if alias == username {
  1745. return impart.HTTPError{http.StatusForbidden, "You cannot currently delete your primary blog."}
  1746. }
  1747. row = db.QueryRow("SELECT id FROM collections WHERE alias = ? AND owner_id = ?", alias, userID)
  1748. err = row.Scan(&c.ID)
  1749. switch {
  1750. case err == sql.ErrNoRows:
  1751. return impart.HTTPError{http.StatusNotFound, "Collection doesn't exist or you're not allowed to delete it."}
  1752. case err != nil:
  1753. log.Error("Failed selecting from collections: %v", err)
  1754. return ErrInternalGeneral
  1755. }
  1756. t, err := db.Begin()
  1757. if err != nil {
  1758. return err
  1759. }
  1760. // Float all collection's posts
  1761. _, err = t.Exec("UPDATE posts SET collection_id = NULL WHERE collection_id = ? AND owner_id = ?", c.ID, userID)
  1762. if err != nil {
  1763. t.Rollback()
  1764. return err
  1765. }
  1766. // Remove redirects to or from this collection
  1767. _, err = t.Exec("DELETE FROM collectionredirects WHERE prev_alias = ? OR new_alias = ?", alias, alias)
  1768. if err != nil {
  1769. t.Rollback()
  1770. return err
  1771. }
  1772. // Remove any optional collection password
  1773. _, err = t.Exec("DELETE FROM collectionpasswords WHERE collection_id = ?", c.ID)
  1774. if err != nil {
  1775. t.Rollback()
  1776. return err
  1777. }
  1778. // Finally, delete collection itself
  1779. _, err = t.Exec("DELETE FROM collections WHERE id = ?", c.ID)
  1780. if err != nil {
  1781. t.Rollback()
  1782. return err
  1783. }
  1784. err = t.Commit()
  1785. if err != nil {
  1786. t.Rollback()
  1787. return err
  1788. }
  1789. return nil
  1790. }
  1791. func (db *datastore) IsCollectionAttributeOn(id int64, attr string) bool {
  1792. var v string
  1793. err := db.QueryRow("SELECT value FROM collectionattributes WHERE collection_id = ? AND attribute = ?", id, attr).Scan(&v)
  1794. switch {
  1795. case err == sql.ErrNoRows:
  1796. return false
  1797. case err != nil:
  1798. log.Error("Couldn't SELECT value in isCollectionAttributeOn for attribute '%s': %v", attr, err)
  1799. return false
  1800. }
  1801. return v == "1"
  1802. }
  1803. func (db *datastore) CollectionHasAttribute(id int64, attr string) bool {
  1804. var dummy string
  1805. err := db.QueryRow("SELECT value FROM collectionattributes WHERE collection_id = ? AND attribute = ?", id, attr).Scan(&dummy)
  1806. switch {
  1807. case err == sql.ErrNoRows:
  1808. return false
  1809. case err != nil:
  1810. log.Error("Couldn't SELECT value in collectionHasAttribute for attribute '%s': %v", attr, err)
  1811. return false
  1812. }
  1813. return true
  1814. }
  1815. func (db *datastore) DeleteAccount(userID int64) (l *string, err error) {
  1816. debug := ""
  1817. l = &debug
  1818. t, err := db.Begin()
  1819. if err != nil {
  1820. stringLogln(l, "Unable to begin: %v", err)
  1821. return
  1822. }
  1823. // Get all collections
  1824. rows, err := db.Query("SELECT id, alias FROM collections WHERE owner_id = ?", userID)
  1825. if err != nil {
  1826. t.Rollback()
  1827. stringLogln(l, "Unable to get collections: %v", err)
  1828. return
  1829. }
  1830. defer rows.Close()
  1831. colls := []Collection{}
  1832. var c Collection
  1833. for rows.Next() {
  1834. err = rows.Scan(&c.ID, &c.Alias)
  1835. if err != nil {
  1836. t.Rollback()
  1837. stringLogln(l, "Unable to scan collection cols: %v", err)
  1838. return
  1839. }
  1840. colls = append(colls, c)
  1841. }
  1842. var res sql.Result
  1843. for _, c := range colls {
  1844. // TODO: user deleteCollection() func
  1845. // Delete tokens
  1846. res, err = t.Exec("DELETE FROM collectionattributes WHERE collection_id = ?", c.ID)
  1847. if err != nil {
  1848. t.Rollback()
  1849. stringLogln(l, "Unable to delete attributes on %s: %v", c.Alias, err)
  1850. return
  1851. }
  1852. rs, _ := res.RowsAffected()
  1853. stringLogln(l, "Deleted %d for %s from collectionattributes", rs, c.Alias)
  1854. // Remove any optional collection password
  1855. res, err = t.Exec("DELETE FROM collectionpasswords WHERE collection_id = ?", c.ID)
  1856. if err != nil {
  1857. t.Rollback()
  1858. stringLogln(l, "Unable to delete passwords on %s: %v", c.Alias, err)
  1859. return
  1860. }
  1861. rs, _ = res.RowsAffected()
  1862. stringLogln(l, "Deleted %d for %s from collectionpasswords", rs, c.Alias)
  1863. // Remove redirects to this collection
  1864. res, err = t.Exec("DELETE FROM collectionredirects WHERE new_alias = ?", c.Alias)
  1865. if err != nil {
  1866. t.Rollback()
  1867. stringLogln(l, "Unable to delete redirects on %s: %v", c.Alias, err)
  1868. return
  1869. }
  1870. rs, _ = res.RowsAffected()
  1871. stringLogln(l, "Deleted %d for %s from collectionredirects", rs, c.Alias)
  1872. }
  1873. // Delete collections
  1874. res, err = t.Exec("DELETE FROM collections WHERE owner_id = ?", userID)
  1875. if err != nil {
  1876. t.Rollback()
  1877. stringLogln(l, "Unable to delete collections: %v", err)
  1878. return
  1879. }
  1880. rs, _ := res.RowsAffected()
  1881. stringLogln(l, "Deleted %d from collections", rs)
  1882. // Delete tokens
  1883. res, err = t.Exec("DELETE FROM accesstokens WHERE user_id = ?", userID)
  1884. if err != nil {
  1885. t.Rollback()
  1886. stringLogln(l, "Unable to delete access tokens: %v", err)
  1887. return
  1888. }
  1889. rs, _ = res.RowsAffected()
  1890. stringLogln(l, "Deleted %d from accesstokens", rs)
  1891. // Delete posts
  1892. res, err = t.Exec("DELETE FROM posts WHERE owner_id = ?", userID)
  1893. if err != nil {
  1894. t.Rollback()
  1895. stringLogln(l, "Unable to delete posts: %v", err)
  1896. return
  1897. }
  1898. rs, _ = res.RowsAffected()
  1899. stringLogln(l, "Deleted %d from posts", rs)
  1900. res, err = t.Exec("DELETE FROM userattributes WHERE user_id = ?", userID)
  1901. if err != nil {
  1902. t.Rollback()
  1903. stringLogln(l, "Unable to delete attributes: %v", err)
  1904. return
  1905. }
  1906. rs, _ = res.RowsAffected()
  1907. stringLogln(l, "Deleted %d from userattributes", rs)
  1908. res, err = t.Exec("DELETE FROM users WHERE id = ?", userID)
  1909. if err != nil {
  1910. t.Rollback()
  1911. stringLogln(l, "Unable to delete user: %v", err)
  1912. return
  1913. }
  1914. rs, _ = res.RowsAffected()
  1915. stringLogln(l, "Deleted %d from users", rs)
  1916. err = t.Commit()
  1917. if err != nil {
  1918. t.Rollback()
  1919. stringLogln(l, "Unable to commit: %v", err)
  1920. return
  1921. }
  1922. return
  1923. }
  1924. func (db *datastore) GetAPActorKeys(collectionID int64) ([]byte, []byte) {
  1925. var pub, priv []byte
  1926. err := db.QueryRow("SELECT public_key, private_key FROM collectionkeys WHERE collection_id = ?", collectionID).Scan(&pub, &priv)
  1927. switch {
  1928. case err == sql.ErrNoRows:
  1929. // Generate keys
  1930. pub, priv = activitypub.GenerateKeys()
  1931. _, err = db.Exec("INSERT INTO collectionkeys (collection_id, public_key, private_key) VALUES (?, ?, ?)", collectionID, pub, priv)
  1932. if err != nil {
  1933. log.Error("Unable to INSERT new activitypub keypair: %v", err)
  1934. return nil, nil
  1935. }
  1936. case err != nil:
  1937. log.Error("Couldn't SELECT collectionkeys: %v", err)
  1938. return nil, nil
  1939. }
  1940. return pub, priv
  1941. }
  1942. func (db *datastore) CreateUserInvite(id string, userID int64, maxUses int, expires *time.Time) error {
  1943. _, err := db.Exec("INSERT INTO userinvites (id, owner_id, max_uses, created, expires, inactive) VALUES (?, ?, ?, "+db.now()+", ?, 0)", id, userID, maxUses, expires)
  1944. return err
  1945. }
  1946. func (db *datastore) GetUserInvites(userID int64) (*[]Invite, error) {
  1947. rows, err := db.Query("SELECT id, max_uses, created, expires, inactive FROM userinvites WHERE owner_id = ? ORDER BY created DESC", userID)
  1948. if err != nil {
  1949. log.Error("Failed selecting from userinvites: %v", err)
  1950. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user invites."}
  1951. }
  1952. defer rows.Close()
  1953. is := []Invite{}
  1954. for rows.Next() {
  1955. i := Invite{}
  1956. err = rows.Scan(&i.ID, &i.MaxUses, &i.Created, &i.Expires, &i.Inactive)
  1957. is = append(is, i)
  1958. }
  1959. return &is, nil
  1960. }
  1961. func (db *datastore) GetUserInvite(id string) (*Invite, error) {
  1962. var i Invite
  1963. err := db.QueryRow("SELECT id, max_uses, created, expires, inactive FROM userinvites WHERE id = ?", id).Scan(&i.ID, &i.MaxUses, &i.Created, &i.Expires, &i.Inactive)
  1964. switch {
  1965. case err == sql.ErrNoRows:
  1966. return nil, impart.HTTPError{http.StatusNotFound, "Invite doesn't exist."}
  1967. case err != nil:
  1968. log.Error("Failed selecting invite: %v", err)
  1969. return nil, err
  1970. }
  1971. return &i, nil
  1972. }
  1973. func (db *datastore) GetUsersInvitedCount(id string) int64 {
  1974. var count int64
  1975. err := db.QueryRow("SELECT COUNT(*) FROM usersinvited WHERE invite_id = ?", id).Scan(&count)
  1976. switch {
  1977. case err == sql.ErrNoRows:
  1978. return 0
  1979. case err != nil:
  1980. log.Error("Failed selecting users invited count: %v", err)
  1981. return 0
  1982. }
  1983. return count
  1984. }
  1985. func (db *datastore) CreateInvitedUser(inviteID string, userID int64) error {
  1986. _, err := db.Exec("INSERT INTO usersinvited (invite_id, user_id) VALUES (?, ?)", inviteID, userID)
  1987. return err
  1988. }
  1989. func (db *datastore) GetInstancePages() ([]*instanceContent, error) {
  1990. return db.GetAllDynamicContent("page")
  1991. }
  1992. func (db *datastore) GetAllDynamicContent(t string) ([]*instanceContent, error) {
  1993. where := ""
  1994. params := []interface{}{}
  1995. if t != "" {
  1996. where = " WHERE content_type = ?"
  1997. params = append(params, t)
  1998. }
  1999. rows, err := db.Query("SELECT id, title, content, updated, content_type FROM appcontent"+where, params...)
  2000. if err != nil {
  2001. log.Error("Failed selecting from appcontent: %v", err)
  2002. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve instance pages."}
  2003. }
  2004. defer rows.Close()
  2005. pages := []*instanceContent{}
  2006. for rows.Next() {
  2007. c := &instanceContent{}
  2008. err = rows.Scan(&c.ID, &c.Title, &c.Content, &c.Updated, &c.Type)
  2009. if err != nil {
  2010. log.Error("Failed scanning row: %v", err)
  2011. break
  2012. }
  2013. pages = append(pages, c)
  2014. }
  2015. err = rows.Err()
  2016. if err != nil {
  2017. log.Error("Error after Next() on rows: %v", err)
  2018. }
  2019. return pages, nil
  2020. }
  2021. func (db *datastore) GetDynamicContent(id string) (*instanceContent, error) {
  2022. c := &instanceContent{
  2023. ID: id,
  2024. }
  2025. err := db.QueryRow("SELECT title, content, updated, content_type FROM appcontent WHERE id = ?", id).Scan(&c.Title, &c.Content, &c.Updated, &c.Type)
  2026. switch {
  2027. case err == sql.ErrNoRows:
  2028. return nil, nil
  2029. case err != nil:
  2030. log.Error("Couldn't SELECT FROM appcontent for id '%s': %v", id, err)
  2031. return nil, err
  2032. }
  2033. return c, nil
  2034. }
  2035. func (db *datastore) UpdateDynamicContent(id, title, content, contentType string) error {
  2036. var err error
  2037. if db.driverName == driverSQLite {
  2038. _, err = db.Exec("INSERT OR REPLACE INTO appcontent (id, title, content, updated, content_type) VALUES (?, ?, ?, "+db.now()+", ?)", id, title, content, contentType)
  2039. } else {
  2040. _, err = db.Exec("INSERT INTO appcontent (id, title, content, updated, content_type) VALUES (?, ?, ?, "+db.now()+", ?) "+db.upsert("id")+" title = ?, content = ?, updated = "+db.now(), id, title, content, contentType, title, content)
  2041. }
  2042. if err != nil {
  2043. log.Error("Unable to INSERT appcontent for '%s': %v", id, err)
  2044. }
  2045. return err
  2046. }
  2047. func (db *datastore) GetAllUsers(page uint) (*[]User, error) {
  2048. limitStr := fmt.Sprintf("0, %d", adminUsersPerPage)
  2049. if page > 1 {
  2050. limitStr = fmt.Sprintf("%d, %d", (page-1)*adminUsersPerPage, adminUsersPerPage)
  2051. }
  2052. rows, err := db.Query("SELECT id, username, created FROM users ORDER BY created DESC LIMIT " + limitStr)
  2053. if err != nil {
  2054. log.Error("Failed selecting from posts: %v", err)
  2055. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user posts."}
  2056. }
  2057. defer rows.Close()
  2058. users := []User{}
  2059. for rows.Next() {
  2060. u := User{}
  2061. err = rows.Scan(&u.ID, &u.Username, &u.Created)
  2062. if err != nil {
  2063. log.Error("Failed scanning GetAllUsers() row: %v", err)
  2064. break
  2065. }
  2066. users = append(users, u)
  2067. }
  2068. return &users, nil
  2069. }
  2070. func (db *datastore) GetAllUsersCount() int64 {
  2071. var count int64
  2072. err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
  2073. switch {
  2074. case err == sql.ErrNoRows:
  2075. return 0
  2076. case err != nil:
  2077. log.Error("Failed selecting all users count: %v", err)
  2078. return 0
  2079. }
  2080. return count
  2081. }
  2082. func (db *datastore) GetUserLastPostTime(id int64) (*time.Time, error) {
  2083. var t time.Time
  2084. err := db.QueryRow("SELECT created FROM posts WHERE owner_id = ? ORDER BY created DESC LIMIT 1", id).Scan(&t)
  2085. switch {
  2086. case err == sql.ErrNoRows:
  2087. return nil, nil
  2088. case err != nil:
  2089. log.Error("Failed selecting last post time from posts: %v", err)
  2090. return nil, err
  2091. }
  2092. return &t, nil
  2093. }
  2094. func (db *datastore) GetCollectionLastPostTime(id int64) (*time.Time, error) {
  2095. var t time.Time
  2096. err := db.QueryRow("SELECT created FROM posts WHERE collection_id = ? ORDER BY created DESC LIMIT 1", id).Scan(&t)
  2097. switch {
  2098. case err == sql.ErrNoRows:
  2099. return nil, nil
  2100. case err != nil:
  2101. log.Error("Failed selecting last post time from posts: %v", err)
  2102. return nil, err
  2103. }
  2104. return &t, nil
  2105. }
  2106. // DatabaseInitialized returns whether or not the current datastore has been
  2107. // initialized with the correct schema.
  2108. // Currently, it checks to see if the `users` table exists.
  2109. func (db *datastore) DatabaseInitialized() bool {
  2110. var dummy string
  2111. var err error
  2112. if db.driverName == driverSQLite {
  2113. err = db.QueryRow("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'users'").Scan(&dummy)
  2114. } else {
  2115. err = db.QueryRow("SHOW TABLES LIKE 'users'").Scan(&dummy)
  2116. }
  2117. switch {
  2118. case err == sql.ErrNoRows:
  2119. return false
  2120. case err != nil:
  2121. log.Error("Couldn't SHOW TABLES: %v", err)
  2122. return false
  2123. }
  2124. return true
  2125. }
  2126. func stringLogln(log *string, s string, v ...interface{}) {
  2127. *log += fmt.Sprintf(s+"\n", v...)
  2128. }
  2129. func handleFailedPostInsert(err error) error {
  2130. log.Error("Couldn't insert into posts: %v", err)
  2131. return err
  2132. }