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.
 
 
 
 
 

2159 lines
64 KiB

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