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.
 
 
 
 
 

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