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.
 
 
 
 
 

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