A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

2431 lines
72 KiB

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