A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 
 

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