A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 

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