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.
 
 
 
 
 

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