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.
 
 
 
 
 

3091 lines
95 KiB

  1. /*
  2. * Copyright © 2018-2021 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. "context"
  13. "database/sql"
  14. "fmt"
  15. "github.com/go-sql-driver/mysql"
  16. "github.com/writeas/web-core/silobridge"
  17. wf_db "github.com/writefreely/writefreely/db"
  18. "net/http"
  19. "strings"
  20. "time"
  21. "github.com/guregu/null"
  22. "github.com/guregu/null/zero"
  23. uuid "github.com/nu7hatch/gouuid"
  24. "github.com/writeas/activityserve"
  25. "github.com/writeas/impart"
  26. "github.com/writeas/web-core/activitypub"
  27. "github.com/writeas/web-core/auth"
  28. "github.com/writeas/web-core/data"
  29. "github.com/writeas/web-core/id"
  30. "github.com/writeas/web-core/log"
  31. "github.com/writeas/web-core/query"
  32. "github.com/writefreely/writefreely/author"
  33. "github.com/writefreely/writefreely/config"
  34. "github.com/writefreely/writefreely/key"
  35. )
  36. const (
  37. mySQLErrDuplicateKey = 1062
  38. mySQLErrCollationMix = 1267
  39. mySQLErrTooManyConns = 1040
  40. mySQLErrMaxUserConns = 1203
  41. driverMySQL = "mysql"
  42. driverSQLite = "sqlite3"
  43. )
  44. var (
  45. SQLiteEnabled bool
  46. )
  47. type writestore interface {
  48. CreateUser(*config.Config, *User, string, string) error
  49. UpdateUserEmail(keys *key.Keychain, userID int64, email string) error
  50. UpdateEncryptedUserEmail(int64, []byte) error
  51. GetUserByID(int64) (*User, error)
  52. GetUserForAuth(string) (*User, error)
  53. GetUserForAuthByID(int64) (*User, error)
  54. GetUserNameFromToken(string) (string, error)
  55. GetUserDataFromToken(string) (int64, string, error)
  56. GetAPIUser(header string) (*User, error)
  57. GetUserID(accessToken string) int64
  58. GetUserIDPrivilege(accessToken string) (userID int64, sudo bool)
  59. DeleteToken(accessToken []byte) error
  60. FetchLastAccessToken(userID int64) string
  61. GetAccessToken(userID int64) (string, error)
  62. GetTemporaryAccessToken(userID int64, validSecs int) (string, error)
  63. GetTemporaryOneTimeAccessToken(userID int64, validSecs int, oneTime bool) (string, error)
  64. DeleteAccount(userID int64) error
  65. ChangeSettings(app *App, u *User, s *userSettings) error
  66. ChangePassphrase(userID int64, sudo bool, curPass string, hashedPass []byte) error
  67. GetCollections(u *User, hostName string) (*[]Collection, error)
  68. GetPublishableCollections(u *User, hostName string) (*[]Collection, error)
  69. GetMeStats(u *User) userMeStats
  70. GetTotalCollections() (int64, error)
  71. GetTotalPosts() (int64, error)
  72. GetTopPosts(u *User, alias string) (*[]PublicPost, error)
  73. GetAnonymousPosts(u *User, page int) (*[]PublicPost, error)
  74. GetUserPosts(u *User) (*[]PublicPost, error)
  75. CreateOwnedPost(post *SubmittedPost, accessToken, collAlias, hostName string) (*PublicPost, error)
  76. CreatePost(userID, collID int64, post *SubmittedPost) (*Post, error)
  77. UpdateOwnedPost(post *AuthenticatedPost, userID int64) error
  78. GetEditablePost(id, editToken string) (*PublicPost, error)
  79. PostIDExists(id string) bool
  80. GetPost(id string, collectionID int64) (*PublicPost, error)
  81. GetOwnedPost(id string, ownerID int64) (*PublicPost, error)
  82. GetPostProperty(id string, collectionID int64, property string) (interface{}, error)
  83. CreateCollectionFromToken(*config.Config, string, string, string) (*Collection, error)
  84. CreateCollection(*config.Config, string, string, int64) (*Collection, error)
  85. GetCollectionBy(condition string, value interface{}) (*Collection, error)
  86. GetCollection(alias string) (*Collection, error)
  87. GetCollectionForPad(alias string) (*Collection, error)
  88. GetCollectionByID(id int64) (*Collection, error)
  89. UpdateCollection(c *SubmittedCollection, alias string) error
  90. DeleteCollection(alias string, userID int64) error
  91. UpdatePostPinState(pinned bool, postID string, collID, ownerID, pos int64) error
  92. GetLastPinnedPostPos(collID int64) int64
  93. GetPinnedPosts(coll *CollectionObj, includeFuture bool) (*[]PublicPost, error)
  94. RemoveCollectionRedirect(t *sql.Tx, alias string) error
  95. GetCollectionRedirect(alias string) (new string)
  96. IsCollectionAttributeOn(id int64, attr string) bool
  97. CollectionHasAttribute(id int64, attr string) bool
  98. CanCollect(cpr *ClaimPostRequest, userID int64) bool
  99. AttemptClaim(p *ClaimPostRequest, query string, params []interface{}, slugIdx int) (sql.Result, error)
  100. DispersePosts(userID int64, postIDs []string) (*[]ClaimPostResult, error)
  101. ClaimPosts(cfg *config.Config, userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error)
  102. GetPostsCount(c *CollectionObj, includeFuture bool)
  103. GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error)
  104. GetPostsTagged(cfg *config.Config, c *Collection, tag string, page int, includeFuture bool) (*[]PublicPost, error)
  105. GetAPFollowers(c *Collection) (*[]RemoteUser, error)
  106. GetAPActorKeys(collectionID int64) ([]byte, []byte)
  107. CreateUserInvite(id string, userID int64, maxUses int, expires *time.Time) error
  108. GetUserInvites(userID int64) (*[]Invite, error)
  109. GetUserInvite(id string) (*Invite, error)
  110. GetUsersInvitedCount(id string) int64
  111. CreateInvitedUser(inviteID string, userID int64) error
  112. GetDynamicContent(id string) (*instanceContent, error)
  113. UpdateDynamicContent(id, title, content, contentType string) error
  114. GetAllUsers(page uint) (*[]User, error)
  115. GetAllUsersCount() int64
  116. GetUserLastPostTime(id int64) (*time.Time, error)
  117. GetCollectionLastPostTime(id int64) (*time.Time, error)
  118. GetIDForRemoteUser(context.Context, string, string, string) (int64, error)
  119. RecordRemoteUserID(context.Context, int64, string, string, string, string) error
  120. ValidateOAuthState(context.Context, string) (string, string, int64, string, error)
  121. GenerateOAuthState(context.Context, string, string, int64, string) (string, error)
  122. GetOauthAccounts(ctx context.Context, userID int64) ([]oauthAccountInfo, error)
  123. RemoveOauth(ctx context.Context, userID int64, provider string, clientID string, remoteUserID string) error
  124. DatabaseInitialized() bool
  125. }
  126. type datastore struct {
  127. *sql.DB
  128. driverName string
  129. }
  130. var _ writestore = &datastore{}
  131. func (db *datastore) now() string {
  132. if db.driverName == driverSQLite {
  133. return "strftime('%Y-%m-%d %H:%M:%S','now')"
  134. }
  135. return "NOW()"
  136. }
  137. func (db *datastore) clip(field string, l int) string {
  138. if db.driverName == driverSQLite {
  139. return fmt.Sprintf("SUBSTR(%s, 0, %d)", field, l)
  140. }
  141. return fmt.Sprintf("LEFT(%s, %d)", field, l)
  142. }
  143. func (db *datastore) upsert(indexedCols ...string) string {
  144. if db.driverName == driverSQLite {
  145. // NOTE: SQLite UPSERT syntax only works in v3.24.0 (2018-06-04) or later
  146. // Leaving this for whenever we can upgrade and include it in our binary
  147. cc := strings.Join(indexedCols, ", ")
  148. return "ON CONFLICT(" + cc + ") DO UPDATE SET"
  149. }
  150. return "ON DUPLICATE KEY UPDATE"
  151. }
  152. func (db *datastore) dateSub(l int, unit string) string {
  153. if db.driverName == driverSQLite {
  154. return fmt.Sprintf("DATETIME('now', '-%d %s')", l, unit)
  155. }
  156. return fmt.Sprintf("DATE_SUB(NOW(), INTERVAL %d %s)", l, unit)
  157. }
  158. // CreateUser creates a new user in the database from the given User, UPDATING it in the process with the user's ID.
  159. func (db *datastore) CreateUser(cfg *config.Config, u *User, collectionTitle string, collectionDesc string) error {
  160. if db.PostIDExists(u.Username) {
  161. return impart.HTTPError{http.StatusConflict, "Invalid collection name."}
  162. }
  163. // New users get a `users` and `collections` row.
  164. t, err := db.Begin()
  165. if err != nil {
  166. return err
  167. }
  168. // 1. Add to `users` table
  169. // NOTE: Assumes User's Password is already hashed!
  170. res, err := t.Exec("INSERT INTO users (username, password, email) VALUES (?, ?, ?)", u.Username, u.HashedPass, u.Email)
  171. if err != nil {
  172. t.Rollback()
  173. if db.isDuplicateKeyErr(err) {
  174. return impart.HTTPError{http.StatusConflict, "Username is already taken."}
  175. }
  176. log.Error("Rolling back users INSERT: %v\n", err)
  177. return err
  178. }
  179. u.ID, err = res.LastInsertId()
  180. if err != nil {
  181. t.Rollback()
  182. log.Error("Rolling back after LastInsertId: %v\n", err)
  183. return err
  184. }
  185. // 2. Create user's Collection
  186. if collectionTitle == "" {
  187. collectionTitle = u.Username
  188. }
  189. res, err = t.Exec("INSERT INTO collections (alias, title, description, privacy, owner_id, view_count) VALUES (?, ?, ?, ?, ?, ?)", u.Username, collectionTitle, collectionDesc, defaultVisibility(cfg), u.ID, 0)
  190. if err != nil {
  191. t.Rollback()
  192. if db.isDuplicateKeyErr(err) {
  193. return impart.HTTPError{http.StatusConflict, "Username is already taken."}
  194. }
  195. log.Error("Rolling back collections INSERT: %v\n", err)
  196. return err
  197. }
  198. db.RemoveCollectionRedirect(t, u.Username)
  199. err = t.Commit()
  200. if err != nil {
  201. t.Rollback()
  202. log.Error("Rolling back after Commit(): %v\n", err)
  203. return err
  204. }
  205. return nil
  206. }
  207. // FIXME: We're returning errors inconsistently in this file. Do we use Errorf
  208. // for returned value, or impart?
  209. func (db *datastore) UpdateUserEmail(keys *key.Keychain, userID int64, email string) error {
  210. encEmail, err := data.Encrypt(keys.EmailKey, email)
  211. if err != nil {
  212. return fmt.Errorf("Couldn't encrypt email %s: %s\n", email, err)
  213. }
  214. return db.UpdateEncryptedUserEmail(userID, encEmail)
  215. }
  216. func (db *datastore) UpdateEncryptedUserEmail(userID int64, encEmail []byte) error {
  217. _, err := db.Exec("UPDATE users SET email = ? WHERE id = ?", encEmail, userID)
  218. if err != nil {
  219. return fmt.Errorf("Unable to update user email: %s", err)
  220. }
  221. return nil
  222. }
  223. func (db *datastore) CreateCollectionFromToken(cfg *config.Config, alias, title, accessToken string) (*Collection, error) {
  224. userID := db.GetUserID(accessToken)
  225. if userID == -1 {
  226. return nil, ErrBadAccessToken
  227. }
  228. return db.CreateCollection(cfg, alias, title, userID)
  229. }
  230. func (db *datastore) GetUserCollectionCount(userID int64) (uint64, error) {
  231. var collCount uint64
  232. err := db.QueryRow("SELECT COUNT(*) FROM collections WHERE owner_id = ?", userID).Scan(&collCount)
  233. switch {
  234. case err == sql.ErrNoRows:
  235. return 0, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user from database."}
  236. case err != nil:
  237. log.Error("Couldn't get collections count for user %d: %v", userID, err)
  238. return 0, err
  239. }
  240. return collCount, nil
  241. }
  242. func (db *datastore) CreateCollection(cfg *config.Config, alias, title string, userID int64) (*Collection, error) {
  243. if db.PostIDExists(alias) {
  244. return nil, impart.HTTPError{http.StatusConflict, "Invalid collection name."}
  245. }
  246. // All good, so create new collection
  247. res, err := db.Exec("INSERT INTO collections (alias, title, description, privacy, owner_id, view_count) VALUES (?, ?, ?, ?, ?, ?)", alias, title, "", defaultVisibility(cfg), userID, 0)
  248. if err != nil {
  249. if db.isDuplicateKeyErr(err) {
  250. return nil, impart.HTTPError{http.StatusConflict, "Collection already exists."}
  251. }
  252. log.Error("Couldn't add to collections: %v\n", err)
  253. return nil, err
  254. }
  255. c := &Collection{
  256. Alias: alias,
  257. Title: title,
  258. OwnerID: userID,
  259. PublicOwner: false,
  260. Public: defaultVisibility(cfg) == CollPublic,
  261. }
  262. c.ID, err = res.LastInsertId()
  263. if err != nil {
  264. log.Error("Couldn't get collection LastInsertId: %v\n", err)
  265. }
  266. return c, nil
  267. }
  268. func (db *datastore) GetUserByID(id int64) (*User, error) {
  269. u := &User{ID: id}
  270. err := db.QueryRow("SELECT username, password, email, created, status FROM users WHERE id = ?", id).Scan(&u.Username, &u.HashedPass, &u.Email, &u.Created, &u.Status)
  271. switch {
  272. case err == sql.ErrNoRows:
  273. return nil, ErrUserNotFound
  274. case err != nil:
  275. log.Error("Couldn't SELECT user password: %v", err)
  276. return nil, err
  277. }
  278. return u, nil
  279. }
  280. // IsUserSilenced returns true if the user account associated with id is
  281. // currently silenced.
  282. func (db *datastore) IsUserSilenced(id int64) (bool, error) {
  283. u := &User{ID: id}
  284. err := db.QueryRow("SELECT status FROM users WHERE id = ?", id).Scan(&u.Status)
  285. switch {
  286. case err == sql.ErrNoRows:
  287. return false, fmt.Errorf("is user silenced: %v", ErrUserNotFound)
  288. case err != nil:
  289. log.Error("Couldn't SELECT user status: %v", err)
  290. return false, fmt.Errorf("is user silenced: %v", err)
  291. }
  292. return u.IsSilenced(), nil
  293. }
  294. // DoesUserNeedAuth returns true if the user hasn't provided any methods for
  295. // authenticating with the account, such a passphrase or email address.
  296. // Any errors are reported to admin and silently quashed, returning false as the
  297. // result.
  298. func (db *datastore) DoesUserNeedAuth(id int64) bool {
  299. var pass, email []byte
  300. // Find out if user has an email set first
  301. err := db.QueryRow("SELECT password, email FROM users WHERE id = ?", id).Scan(&pass, &email)
  302. switch {
  303. case err == sql.ErrNoRows:
  304. // ERROR. Don't give false positives on needing auth methods
  305. return false
  306. case err != nil:
  307. // ERROR. Don't give false positives on needing auth methods
  308. log.Error("Couldn't SELECT user %d from users: %v", id, err)
  309. return false
  310. }
  311. // User doesn't need auth if there's an email
  312. return len(email) == 0 && len(pass) == 0
  313. }
  314. func (db *datastore) IsUserPassSet(id int64) (bool, error) {
  315. var pass []byte
  316. err := db.QueryRow("SELECT password FROM users WHERE id = ?", id).Scan(&pass)
  317. switch {
  318. case err == sql.ErrNoRows:
  319. return false, nil
  320. case err != nil:
  321. log.Error("Couldn't SELECT user %d from users: %v", id, err)
  322. return false, err
  323. }
  324. return len(pass) > 0, nil
  325. }
  326. func (db *datastore) GetUserForAuth(username string) (*User, error) {
  327. u := &User{Username: username}
  328. err := db.QueryRow("SELECT id, password, email, created, status FROM users WHERE username = ?", username).Scan(&u.ID, &u.HashedPass, &u.Email, &u.Created, &u.Status)
  329. switch {
  330. case err == sql.ErrNoRows:
  331. // Check if they've entered the wrong, unnormalized username
  332. username = getSlug(username, "")
  333. if username != u.Username {
  334. err = db.QueryRow("SELECT id FROM users WHERE username = ? LIMIT 1", username).Scan(&u.ID)
  335. if err == nil {
  336. return db.GetUserForAuth(username)
  337. }
  338. }
  339. return nil, ErrUserNotFound
  340. case err != nil:
  341. log.Error("Couldn't SELECT user password: %v", err)
  342. return nil, err
  343. }
  344. return u, nil
  345. }
  346. func (db *datastore) GetUserForAuthByID(userID int64) (*User, error) {
  347. u := &User{ID: userID}
  348. err := db.QueryRow("SELECT id, password, email, created, status FROM users WHERE id = ?", u.ID).Scan(&u.ID, &u.HashedPass, &u.Email, &u.Created, &u.Status)
  349. switch {
  350. case err == sql.ErrNoRows:
  351. return nil, ErrUserNotFound
  352. case err != nil:
  353. log.Error("Couldn't SELECT userForAuthByID: %v", err)
  354. return nil, err
  355. }
  356. return u, nil
  357. }
  358. func (db *datastore) GetUserNameFromToken(accessToken string) (string, error) {
  359. t := auth.GetToken(accessToken)
  360. if len(t) == 0 {
  361. return "", ErrNoAccessToken
  362. }
  363. var oneTime bool
  364. var username string
  365. 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)
  366. switch {
  367. case err == sql.ErrNoRows:
  368. return "", ErrBadAccessToken
  369. case err != nil:
  370. return "", ErrInternalGeneral
  371. }
  372. // Delete token if it was one-time
  373. if oneTime {
  374. db.DeleteToken(t[:])
  375. }
  376. return username, nil
  377. }
  378. func (db *datastore) GetUserDataFromToken(accessToken string) (int64, string, error) {
  379. t := auth.GetToken(accessToken)
  380. if len(t) == 0 {
  381. return 0, "", ErrNoAccessToken
  382. }
  383. var userID int64
  384. var oneTime bool
  385. var username string
  386. 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)
  387. switch {
  388. case err == sql.ErrNoRows:
  389. return 0, "", ErrBadAccessToken
  390. case err != nil:
  391. return 0, "", ErrInternalGeneral
  392. }
  393. // Delete token if it was one-time
  394. if oneTime {
  395. db.DeleteToken(t[:])
  396. }
  397. return userID, username, nil
  398. }
  399. func (db *datastore) GetAPIUser(header string) (*User, error) {
  400. uID := db.GetUserID(header)
  401. if uID == -1 {
  402. return nil, fmt.Errorf(ErrUserNotFound.Error())
  403. }
  404. return db.GetUserByID(uID)
  405. }
  406. // GetUserID takes a hexadecimal accessToken, parses it into its binary
  407. // representation, and gets any user ID associated with the token. If no user
  408. // is associated, -1 is returned.
  409. func (db *datastore) GetUserID(accessToken string) int64 {
  410. i, _ := db.GetUserIDPrivilege(accessToken)
  411. return i
  412. }
  413. func (db *datastore) GetUserIDPrivilege(accessToken string) (userID int64, sudo bool) {
  414. t := auth.GetToken(accessToken)
  415. if len(t) == 0 {
  416. return -1, false
  417. }
  418. var oneTime bool
  419. 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)
  420. switch {
  421. case err == sql.ErrNoRows:
  422. return -1, false
  423. case err != nil:
  424. return -1, false
  425. }
  426. // Delete token if it was one-time
  427. if oneTime {
  428. db.DeleteToken(t[:])
  429. }
  430. return
  431. }
  432. func (db *datastore) DeleteToken(accessToken []byte) error {
  433. res, err := db.Exec("DELETE FROM accesstokens WHERE token LIKE ?", accessToken)
  434. if err != nil {
  435. return err
  436. }
  437. rowsAffected, _ := res.RowsAffected()
  438. if rowsAffected == 0 {
  439. return impart.HTTPError{http.StatusNotFound, "Token is invalid or doesn't exist"}
  440. }
  441. return nil
  442. }
  443. // FetchLastAccessToken creates a new non-expiring, valid access token for the given
  444. // userID.
  445. func (db *datastore) FetchLastAccessToken(userID int64) string {
  446. var t []byte
  447. 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)
  448. switch {
  449. case err == sql.ErrNoRows:
  450. return ""
  451. case err != nil:
  452. log.Error("Failed selecting from accesstoken: %v", err)
  453. return ""
  454. }
  455. u, err := uuid.Parse(t)
  456. if err != nil {
  457. return ""
  458. }
  459. return u.String()
  460. }
  461. // GetAccessToken creates a new non-expiring, valid access token for the given
  462. // userID.
  463. func (db *datastore) GetAccessToken(userID int64) (string, error) {
  464. return db.GetTemporaryOneTimeAccessToken(userID, 0, false)
  465. }
  466. // GetTemporaryAccessToken creates a new valid access token for the given
  467. // userID that remains valid for the given time in seconds. If validSecs is 0,
  468. // the access token doesn't automatically expire.
  469. func (db *datastore) GetTemporaryAccessToken(userID int64, validSecs int) (string, error) {
  470. return db.GetTemporaryOneTimeAccessToken(userID, validSecs, false)
  471. }
  472. // GetTemporaryOneTimeAccessToken creates a new valid access token for the given
  473. // userID that remains valid for the given time in seconds and can only be used
  474. // once if oneTime is true. If validSecs is 0, the access token doesn't
  475. // automatically expire.
  476. func (db *datastore) GetTemporaryOneTimeAccessToken(userID int64, validSecs int, oneTime bool) (string, error) {
  477. u, err := uuid.NewV4()
  478. if err != nil {
  479. log.Error("Unable to generate token: %v", err)
  480. return "", err
  481. }
  482. // Insert UUID to `accesstokens`
  483. binTok := u[:]
  484. expirationVal := "NULL"
  485. if validSecs > 0 {
  486. expirationVal = fmt.Sprintf("DATE_ADD("+db.now()+", INTERVAL %d SECOND)", validSecs)
  487. }
  488. _, err = db.Exec("INSERT INTO accesstokens (token, user_id, one_time, expires) VALUES (?, ?, ?, "+expirationVal+")", string(binTok), userID, oneTime)
  489. if err != nil {
  490. log.Error("Couldn't INSERT accesstoken: %v", err)
  491. return "", err
  492. }
  493. return u.String(), nil
  494. }
  495. func (db *datastore) CreateOwnedPost(post *SubmittedPost, accessToken, collAlias, hostName string) (*PublicPost, error) {
  496. var userID, collID int64 = -1, -1
  497. var coll *Collection
  498. var err error
  499. if accessToken != "" {
  500. userID = db.GetUserID(accessToken)
  501. if userID == -1 {
  502. return nil, ErrBadAccessToken
  503. }
  504. if collAlias != "" {
  505. coll, err = db.GetCollection(collAlias)
  506. if err != nil {
  507. return nil, err
  508. }
  509. coll.hostName = hostName
  510. if coll.OwnerID != userID {
  511. return nil, ErrForbiddenCollection
  512. }
  513. collID = coll.ID
  514. }
  515. }
  516. rp := &PublicPost{}
  517. rp.Post, err = db.CreatePost(userID, collID, post)
  518. if err != nil {
  519. return rp, err
  520. }
  521. if coll != nil {
  522. coll.ForPublic()
  523. rp.Collection = &CollectionObj{Collection: *coll}
  524. }
  525. return rp, nil
  526. }
  527. func (db *datastore) CreatePost(userID, collID int64, post *SubmittedPost) (*Post, error) {
  528. idLen := postIDLen
  529. friendlyID := id.GenerateFriendlyRandomString(idLen)
  530. // Handle appearance / font face
  531. appearance := post.Font
  532. if !post.isFontValid() {
  533. appearance = "norm"
  534. }
  535. var err error
  536. ownerID := sql.NullInt64{
  537. Valid: false,
  538. }
  539. ownerCollID := sql.NullInt64{
  540. Valid: false,
  541. }
  542. slug := sql.NullString{"", false}
  543. // If an alias was supplied, we'll add this to the collection as well.
  544. if userID > 0 {
  545. ownerID.Int64 = userID
  546. ownerID.Valid = true
  547. if collID > 0 {
  548. ownerCollID.Int64 = collID
  549. ownerCollID.Valid = true
  550. var slugVal string
  551. if post.Slug != nil && *post.Slug != "" {
  552. slugVal = *post.Slug
  553. } else {
  554. if post.Title != nil && *post.Title != "" {
  555. slugVal = getSlug(*post.Title, post.Language.String)
  556. if slugVal == "" {
  557. slugVal = getSlug(*post.Content, post.Language.String)
  558. }
  559. } else {
  560. slugVal = getSlug(*post.Content, post.Language.String)
  561. }
  562. }
  563. if slugVal == "" {
  564. slugVal = friendlyID
  565. }
  566. slug = sql.NullString{slugVal, true}
  567. }
  568. }
  569. created := time.Now()
  570. if db.driverName == driverSQLite {
  571. // SQLite stores datetimes in UTC, so convert time.Now() to it here
  572. created = created.UTC()
  573. }
  574. if post.Created != nil {
  575. created, err = time.Parse("2006-01-02T15:04:05Z", *post.Created)
  576. if err != nil {
  577. log.Error("Unable to parse Created time '%s': %v", *post.Created, err)
  578. created = time.Now()
  579. if db.driverName == driverSQLite {
  580. // SQLite stores datetimes in UTC, so convert time.Now() to it here
  581. created = created.UTC()
  582. }
  583. }
  584. }
  585. 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() + ", ?)")
  586. if err != nil {
  587. return nil, err
  588. }
  589. defer stmt.Close()
  590. _, err = stmt.Exec(friendlyID, slug, post.Title, post.Content, appearance, post.Language, post.IsRTL, 0, ownerID, ownerCollID, created, 0)
  591. if err != nil {
  592. if db.isDuplicateKeyErr(err) {
  593. // Duplicate entry error; try a new slug
  594. // TODO: make this a little more robust
  595. slug = sql.NullString{id.GenSafeUniqueSlug(slug.String), true}
  596. _, err = stmt.Exec(friendlyID, slug, post.Title, post.Content, appearance, post.Language, post.IsRTL, 0, ownerID, ownerCollID, created, 0)
  597. if err != nil {
  598. return nil, handleFailedPostInsert(fmt.Errorf("Retried slug generation, still failed: %v", err))
  599. }
  600. } else {
  601. return nil, handleFailedPostInsert(err)
  602. }
  603. }
  604. // TODO: return Created field in proper format
  605. return &Post{
  606. ID: friendlyID,
  607. Slug: null.NewString(slug.String, slug.Valid),
  608. Font: appearance,
  609. Language: zero.NewString(post.Language.String, post.Language.Valid),
  610. RTL: zero.NewBool(post.IsRTL.Bool, post.IsRTL.Valid),
  611. OwnerID: null.NewInt(userID, true),
  612. CollectionID: null.NewInt(userID, true),
  613. Created: created.Truncate(time.Second).UTC(),
  614. Updated: time.Now().Truncate(time.Second).UTC(),
  615. Title: zero.NewString(*(post.Title), true),
  616. Content: *(post.Content),
  617. }, nil
  618. }
  619. // UpdateOwnedPost updates an existing post with only the given fields in the
  620. // supplied AuthenticatedPost.
  621. func (db *datastore) UpdateOwnedPost(post *AuthenticatedPost, userID int64) error {
  622. params := []interface{}{}
  623. var queryUpdates, sep, authCondition string
  624. if post.Slug != nil && *post.Slug != "" {
  625. queryUpdates += sep + "slug = ?"
  626. sep = ", "
  627. params = append(params, getSlug(*post.Slug, ""))
  628. }
  629. if post.Content != nil {
  630. queryUpdates += sep + "content = ?"
  631. sep = ", "
  632. params = append(params, post.Content)
  633. }
  634. if post.Title != nil {
  635. queryUpdates += sep + "title = ?"
  636. sep = ", "
  637. params = append(params, post.Title)
  638. }
  639. if post.Language.Valid {
  640. queryUpdates += sep + "language = ?"
  641. sep = ", "
  642. params = append(params, post.Language.String)
  643. }
  644. if post.IsRTL.Valid {
  645. queryUpdates += sep + "rtl = ?"
  646. sep = ", "
  647. params = append(params, post.IsRTL.Bool)
  648. }
  649. if post.Font != "" {
  650. queryUpdates += sep + "text_appearance = ?"
  651. sep = ", "
  652. params = append(params, post.Font)
  653. }
  654. if post.Created != nil {
  655. createTime, err := time.Parse(postMetaDateFormat, *post.Created)
  656. if err != nil {
  657. log.Error("Unable to parse Created date: %v", err)
  658. return fmt.Errorf("That's the incorrect format for Created date.")
  659. }
  660. queryUpdates += sep + "created = ?"
  661. sep = ", "
  662. params = append(params, createTime)
  663. }
  664. // WHERE parameters...
  665. // id = ?
  666. params = append(params, post.ID)
  667. // AND owner_id = ?
  668. authCondition = "(owner_id = ?)"
  669. params = append(params, userID)
  670. if queryUpdates == "" {
  671. return ErrPostNoUpdatableVals
  672. }
  673. queryUpdates += sep + "updated = " + db.now()
  674. res, err := db.Exec("UPDATE posts SET "+queryUpdates+" WHERE id = ? AND "+authCondition, params...)
  675. if err != nil {
  676. log.Error("Unable to update owned post: %v", err)
  677. return err
  678. }
  679. rowsAffected, _ := res.RowsAffected()
  680. if rowsAffected == 0 {
  681. // Show the correct error message if nothing was updated
  682. var dummy int
  683. err := db.QueryRow("SELECT 1 FROM posts WHERE id = ? AND "+authCondition, post.ID, params[len(params)-1]).Scan(&dummy)
  684. switch {
  685. case err == sql.ErrNoRows:
  686. return ErrUnauthorizedEditPost
  687. case err != nil:
  688. log.Error("Failed selecting from posts: %v", err)
  689. }
  690. return nil
  691. }
  692. return nil
  693. }
  694. func (db *datastore) GetCollectionBy(condition string, value interface{}) (*Collection, error) {
  695. c := &Collection{}
  696. // FIXME: change Collection to reflect database values. Add helper functions to get actual values
  697. var styleSheet, script, signature, format zero.String
  698. row := db.QueryRow("SELECT id, alias, title, description, style_sheet, script, post_signature, format, owner_id, privacy, view_count FROM collections WHERE "+condition, value)
  699. err := row.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &styleSheet, &script, &signature, &format, &c.OwnerID, &c.Visibility, &c.Views)
  700. switch {
  701. case err == sql.ErrNoRows:
  702. return nil, impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."}
  703. case db.isHighLoadError(err):
  704. return nil, ErrUnavailable
  705. case err != nil:
  706. log.Error("Failed selecting from collections: %v", err)
  707. return nil, err
  708. }
  709. c.StyleSheet = styleSheet.String
  710. c.Script = script.String
  711. c.Signature = signature.String
  712. c.Format = format.String
  713. c.Public = c.IsPublic()
  714. c.Monetization = db.GetCollectionAttribute(c.ID, "monetization_pointer")
  715. c.db = db
  716. return c, nil
  717. }
  718. func (db *datastore) GetCollection(alias string) (*Collection, error) {
  719. return db.GetCollectionBy("alias = ?", alias)
  720. }
  721. func (db *datastore) GetCollectionForPad(alias string) (*Collection, error) {
  722. c := &Collection{Alias: alias}
  723. row := db.QueryRow("SELECT id, alias, title, description, privacy FROM collections WHERE alias = ?", alias)
  724. err := row.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &c.Visibility)
  725. switch {
  726. case err == sql.ErrNoRows:
  727. return c, impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."}
  728. case err != nil:
  729. log.Error("Failed selecting from collections: %v", err)
  730. return c, ErrInternalGeneral
  731. }
  732. c.Public = c.IsPublic()
  733. return c, nil
  734. }
  735. func (db *datastore) GetCollectionByID(id int64) (*Collection, error) {
  736. return db.GetCollectionBy("id = ?", id)
  737. }
  738. func (db *datastore) GetCollectionFromDomain(host string) (*Collection, error) {
  739. return db.GetCollectionBy("host = ?", host)
  740. }
  741. func (db *datastore) UpdateCollection(c *SubmittedCollection, alias string) error {
  742. q := query.NewUpdate().
  743. SetStringPtr(c.Title, "title").
  744. SetStringPtr(c.Description, "description").
  745. SetNullString(c.StyleSheet, "style_sheet").
  746. SetNullString(c.Script, "script").
  747. SetNullString(c.Signature, "post_signature")
  748. if c.Format != nil {
  749. cf := &CollectionFormat{Format: c.Format.String}
  750. if cf.Valid() {
  751. q.SetNullString(c.Format, "format")
  752. }
  753. }
  754. var updatePass bool
  755. if c.Visibility != nil && (collVisibility(*c.Visibility)&CollProtected == 0 || c.Pass != "") {
  756. q.SetIntPtr(c.Visibility, "privacy")
  757. if c.Pass != "" {
  758. updatePass = true
  759. }
  760. }
  761. // WHERE values
  762. q.Where("alias = ? AND owner_id = ?", alias, c.OwnerID)
  763. if q.Updates == "" && c.Monetization == nil {
  764. return ErrPostNoUpdatableVals
  765. }
  766. // Find any current domain
  767. var collID int64
  768. var rowsAffected int64
  769. var changed bool
  770. var res sql.Result
  771. err := db.QueryRow("SELECT id FROM collections WHERE alias = ?", alias).Scan(&collID)
  772. if err != nil {
  773. log.Error("Failed selecting from collections: %v. Some things won't work.", err)
  774. }
  775. // Update MathJax value
  776. if c.MathJax {
  777. if db.driverName == driverSQLite {
  778. _, err = db.Exec("INSERT OR REPLACE INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?)", collID, "render_mathjax", "1")
  779. } else {
  780. _, err = db.Exec("INSERT INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?) "+db.upsert("collection_id", "attribute")+" value = ?", collID, "render_mathjax", "1", "1")
  781. }
  782. if err != nil {
  783. log.Error("Unable to insert render_mathjax value: %v", err)
  784. return err
  785. }
  786. } else {
  787. _, err = db.Exec("DELETE FROM collectionattributes WHERE collection_id = ? AND attribute = ?", collID, "render_mathjax")
  788. if err != nil {
  789. log.Error("Unable to delete render_mathjax value: %v", err)
  790. return err
  791. }
  792. }
  793. // Update Monetization value
  794. if c.Monetization != nil {
  795. skipUpdate := false
  796. if *c.Monetization != "" {
  797. // Strip away any excess spaces
  798. trimmed := strings.TrimSpace(*c.Monetization)
  799. // Only update value when it starts with "$", per spec: https://paymentpointers.org
  800. if strings.HasPrefix(trimmed, "$") {
  801. c.Monetization = &trimmed
  802. } else {
  803. // Value appears invalid, so don't update
  804. skipUpdate = true
  805. }
  806. }
  807. if !skipUpdate {
  808. _, err = db.Exec("INSERT INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE value = ?", collID, "monetization_pointer", *c.Monetization, *c.Monetization)
  809. if err != nil {
  810. log.Error("Unable to insert monetization_pointer value: %v", err)
  811. return err
  812. }
  813. }
  814. }
  815. // Update EmailSub value
  816. if c.EmailSubs {
  817. // TODO: ensure these work with SQLite
  818. _, err = db.Exec("INSERT INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE value = ?", collID, "email_subs", "1", "1")
  819. if err != nil {
  820. log.Error("Unable to insert email_subs value: %v", err)
  821. return err
  822. }
  823. skipUpdate := false
  824. if c.LetterReply != nil {
  825. // Strip away any excess spaces
  826. trimmed := strings.TrimSpace(*c.LetterReply)
  827. // Only update value when it contains "@"
  828. if strings.IndexRune(trimmed, '@') > 0 {
  829. c.LetterReply = &trimmed
  830. } else {
  831. // Value appears invalid, so don't update
  832. skipUpdate = true
  833. }
  834. if !skipUpdate {
  835. _, err = db.Exec("INSERT INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE value = ?", collID, collAttrLetterReplyTo, *c.LetterReply, *c.LetterReply)
  836. if err != nil {
  837. log.Error("Unable to insert %s value: %v", collAttrLetterReplyTo, err)
  838. return err
  839. }
  840. }
  841. }
  842. } else {
  843. _, err = db.Exec("DELETE FROM collectionattributes WHERE collection_id = ? AND attribute = ?", collID, "email_subs")
  844. if err != nil {
  845. log.Error("Unable to delete email_subs value: %v", err)
  846. return err
  847. }
  848. }
  849. // Update rest of the collection data
  850. if q.Updates != "" {
  851. res, err = db.Exec("UPDATE collections SET "+q.Updates+" WHERE "+q.Conditions, q.Params...)
  852. if err != nil {
  853. log.Error("Unable to update collection: %v", err)
  854. return err
  855. }
  856. }
  857. rowsAffected, _ = res.RowsAffected()
  858. if !changed || rowsAffected == 0 {
  859. // Show the correct error message if nothing was updated
  860. var dummy int
  861. err := db.QueryRow("SELECT 1 FROM collections WHERE alias = ? AND owner_id = ?", alias, c.OwnerID).Scan(&dummy)
  862. switch {
  863. case err == sql.ErrNoRows:
  864. return ErrUnauthorizedEditPost
  865. case err != nil:
  866. log.Error("Failed selecting from collections: %v", err)
  867. }
  868. if !updatePass {
  869. return nil
  870. }
  871. }
  872. if updatePass {
  873. hashedPass, err := auth.HashPass([]byte(c.Pass))
  874. if err != nil {
  875. log.Error("Unable to create hash: %s", err)
  876. return impart.HTTPError{http.StatusInternalServerError, "Could not create password hash."}
  877. }
  878. if db.driverName == driverSQLite {
  879. _, err = db.Exec("INSERT OR REPLACE INTO collectionpasswords (collection_id, password) VALUES ((SELECT id FROM collections WHERE alias = ?), ?)", alias, hashedPass)
  880. } else {
  881. _, err = db.Exec("INSERT INTO collectionpasswords (collection_id, password) VALUES ((SELECT id FROM collections WHERE alias = ?), ?) "+db.upsert("collection_id")+" password = ?", alias, hashedPass, hashedPass)
  882. }
  883. if err != nil {
  884. return err
  885. }
  886. }
  887. return nil
  888. }
  889. const postCols = "id, slug, text_appearance, language, rtl, privacy, owner_id, collection_id, pinned_position, created, updated, view_count, title, content"
  890. // getEditablePost returns a PublicPost with the given ID only if the given
  891. // edit token is valid for the post.
  892. func (db *datastore) GetEditablePost(id, editToken string) (*PublicPost, error) {
  893. // FIXME: code duplicated from getPost()
  894. // TODO: add slight logic difference to getPost / one func
  895. var ownerName sql.NullString
  896. p := &Post{}
  897. row := db.QueryRow("SELECT "+postCols+", (SELECT username FROM users WHERE users.id = posts.owner_id) AS username FROM posts WHERE id = ? LIMIT 1", id)
  898. 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)
  899. switch {
  900. case err == sql.ErrNoRows:
  901. return nil, ErrPostNotFound
  902. case err != nil:
  903. log.Error("Failed selecting from collections: %v", err)
  904. return nil, err
  905. }
  906. if p.Content == "" && p.Title.String == "" {
  907. return nil, ErrPostUnpublished
  908. }
  909. res := p.processPost()
  910. if ownerName.Valid {
  911. res.Owner = &PublicUser{Username: ownerName.String}
  912. }
  913. return &res, nil
  914. }
  915. func (db *datastore) PostIDExists(id string) bool {
  916. var dummy bool
  917. err := db.QueryRow("SELECT 1 FROM posts WHERE id = ?", id).Scan(&dummy)
  918. return err == nil && dummy
  919. }
  920. // GetPost gets a public-facing post object from the database. If collectionID
  921. // is > 0, the post will be retrieved by slug and collection ID, rather than
  922. // post ID.
  923. // TODO: break this into two functions:
  924. // - GetPost(id string)
  925. // - GetCollectionPost(slug string, collectionID int64)
  926. func (db *datastore) GetPost(id string, collectionID int64) (*PublicPost, error) {
  927. var ownerName sql.NullString
  928. p := &Post{}
  929. var row *sql.Row
  930. var where string
  931. params := []interface{}{id}
  932. if collectionID > 0 {
  933. where = "slug = ? AND collection_id = ?"
  934. params = append(params, collectionID)
  935. } else {
  936. where = "id = ?"
  937. }
  938. row = db.QueryRow("SELECT "+postCols+", (SELECT username FROM users WHERE users.id = posts.owner_id) AS username FROM posts WHERE "+where+" LIMIT 1", params...)
  939. 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)
  940. switch {
  941. case err == sql.ErrNoRows:
  942. if collectionID > 0 {
  943. return nil, ErrCollectionPageNotFound
  944. }
  945. return nil, ErrPostNotFound
  946. case err != nil:
  947. log.Error("Failed selecting from collections: %v", err)
  948. return nil, err
  949. }
  950. if p.Content == "" && p.Title.String == "" {
  951. return nil, ErrPostUnpublished
  952. }
  953. res := p.processPost()
  954. if ownerName.Valid {
  955. res.Owner = &PublicUser{Username: ownerName.String}
  956. }
  957. return &res, nil
  958. }
  959. // TODO: don't duplicate getPost() functionality
  960. func (db *datastore) GetOwnedPost(id string, ownerID int64) (*PublicPost, error) {
  961. p := &Post{}
  962. var row *sql.Row
  963. where := "id = ? AND owner_id = ?"
  964. params := []interface{}{id, ownerID}
  965. row = db.QueryRow("SELECT "+postCols+" FROM posts WHERE "+where+" LIMIT 1", params...)
  966. 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)
  967. switch {
  968. case err == sql.ErrNoRows:
  969. return nil, ErrPostNotFound
  970. case err != nil:
  971. log.Error("Failed selecting from collections: %v", err)
  972. return nil, err
  973. }
  974. if p.Content == "" && p.Title.String == "" {
  975. return nil, ErrPostUnpublished
  976. }
  977. res := p.processPost()
  978. return &res, nil
  979. }
  980. func (db *datastore) GetPostProperty(id string, collectionID int64, property string) (interface{}, error) {
  981. propSelects := map[string]string{
  982. "views": "view_count AS views",
  983. }
  984. selectQuery, ok := propSelects[property]
  985. if !ok {
  986. return nil, impart.HTTPError{http.StatusBadRequest, fmt.Sprintf("Invalid property: %s.", property)}
  987. }
  988. var res interface{}
  989. var row *sql.Row
  990. if collectionID != 0 {
  991. row = db.QueryRow("SELECT "+selectQuery+" FROM posts WHERE slug = ? AND collection_id = ? LIMIT 1", id, collectionID)
  992. } else {
  993. row = db.QueryRow("SELECT "+selectQuery+" FROM posts WHERE id = ? LIMIT 1", id)
  994. }
  995. err := row.Scan(&res)
  996. switch {
  997. case err == sql.ErrNoRows:
  998. return nil, impart.HTTPError{http.StatusNotFound, "Post not found."}
  999. case err != nil:
  1000. log.Error("Failed selecting post: %v", err)
  1001. return nil, err
  1002. }
  1003. return res, nil
  1004. }
  1005. // GetPostsCount modifies the CollectionObj to include the correct number of
  1006. // standard (non-pinned) posts. It will return future posts if `includeFuture`
  1007. // is true.
  1008. func (db *datastore) GetPostsCount(c *CollectionObj, includeFuture bool) {
  1009. var count int64
  1010. timeCondition := ""
  1011. if !includeFuture {
  1012. timeCondition = "AND created <= " + db.now()
  1013. }
  1014. err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE collection_id = ? AND pinned_position IS NULL "+timeCondition, c.ID).Scan(&count)
  1015. switch {
  1016. case err == sql.ErrNoRows:
  1017. c.TotalPosts = 0
  1018. case err != nil:
  1019. log.Error("Failed selecting from collections: %v", err)
  1020. c.TotalPosts = 0
  1021. }
  1022. c.TotalPosts = int(count)
  1023. }
  1024. // GetPosts retrieves all posts for the given Collection.
  1025. // It will return future posts if `includeFuture` is true.
  1026. // It will include only standard (non-pinned) posts unless `includePinned` is true.
  1027. // TODO: change includeFuture to isOwner, since that's how it's used
  1028. func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error) {
  1029. collID := c.ID
  1030. cf := c.NewFormat()
  1031. order := "DESC"
  1032. if cf.Ascending() && !forceRecentFirst {
  1033. order = "ASC"
  1034. }
  1035. pagePosts := cf.PostsPerPage()
  1036. start := page*pagePosts - pagePosts
  1037. if page == 0 {
  1038. start = 0
  1039. pagePosts = 1000
  1040. }
  1041. limitStr := ""
  1042. if page > 0 {
  1043. limitStr = fmt.Sprintf(" LIMIT %d, %d", start, pagePosts)
  1044. }
  1045. timeCondition := ""
  1046. if !includeFuture {
  1047. timeCondition = "AND created <= " + db.now()
  1048. }
  1049. pinnedCondition := ""
  1050. if !includePinned {
  1051. pinnedCondition = "AND pinned_position IS NULL"
  1052. }
  1053. rows, err := db.Query("SELECT "+postCols+" FROM posts WHERE collection_id = ? "+pinnedCondition+" "+timeCondition+" ORDER BY created "+order+limitStr, collID)
  1054. if err != nil {
  1055. log.Error("Failed selecting from posts: %v", err)
  1056. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve collection posts."}
  1057. }
  1058. defer rows.Close()
  1059. // TODO: extract this common row scanning logic for queries using `postCols`
  1060. posts := []PublicPost{}
  1061. for rows.Next() {
  1062. p := &Post{}
  1063. 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)
  1064. if err != nil {
  1065. log.Error("Failed scanning row: %v", err)
  1066. break
  1067. }
  1068. p.extractData()
  1069. p.augmentContent(c)
  1070. p.formatContent(cfg, c, includeFuture, false)
  1071. posts = append(posts, p.processPost())
  1072. }
  1073. err = rows.Err()
  1074. if err != nil {
  1075. log.Error("Error after Next() on rows: %v", err)
  1076. }
  1077. return &posts, nil
  1078. }
  1079. // GetPostsTagged retrieves all posts on the given Collection that contain the
  1080. // given tag.
  1081. // It will return future posts if `includeFuture` is true.
  1082. // TODO: change includeFuture to isOwner, since that's how it's used
  1083. func (db *datastore) GetPostsTagged(cfg *config.Config, c *Collection, tag string, page int, includeFuture bool) (*[]PublicPost, error) {
  1084. collID := c.ID
  1085. cf := c.NewFormat()
  1086. order := "DESC"
  1087. if cf.Ascending() {
  1088. order = "ASC"
  1089. }
  1090. pagePosts := cf.PostsPerPage()
  1091. start := page*pagePosts - pagePosts
  1092. if page == 0 {
  1093. start = 0
  1094. pagePosts = 1000
  1095. }
  1096. limitStr := ""
  1097. if page > 0 {
  1098. limitStr = fmt.Sprintf(" LIMIT %d, %d", start, pagePosts)
  1099. }
  1100. timeCondition := ""
  1101. if !includeFuture {
  1102. timeCondition = "AND created <= " + db.now()
  1103. }
  1104. var rows *sql.Rows
  1105. var err error
  1106. if db.driverName == driverSQLite {
  1107. 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.*`)
  1108. } else {
  1109. 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)+"[[:>:]]")
  1110. }
  1111. if err != nil {
  1112. log.Error("Failed selecting from posts: %v", err)
  1113. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve collection posts."}
  1114. }
  1115. defer rows.Close()
  1116. // TODO: extract this common row scanning logic for queries using `postCols`
  1117. posts := []PublicPost{}
  1118. for rows.Next() {
  1119. p := &Post{}
  1120. 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)
  1121. if err != nil {
  1122. log.Error("Failed scanning row: %v", err)
  1123. break
  1124. }
  1125. p.extractData()
  1126. p.augmentContent(c)
  1127. p.formatContent(cfg, c, includeFuture, false)
  1128. posts = append(posts, p.processPost())
  1129. }
  1130. err = rows.Err()
  1131. if err != nil {
  1132. log.Error("Error after Next() on rows: %v", err)
  1133. }
  1134. return &posts, nil
  1135. }
  1136. func (db *datastore) GetAPFollowers(c *Collection) (*[]RemoteUser, error) {
  1137. 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)
  1138. if err != nil {
  1139. log.Error("Failed selecting from followers: %v", err)
  1140. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve followers."}
  1141. }
  1142. defer rows.Close()
  1143. followers := []RemoteUser{}
  1144. for rows.Next() {
  1145. f := RemoteUser{}
  1146. err = rows.Scan(&f.ActorID, &f.Inbox, &f.SharedInbox)
  1147. followers = append(followers, f)
  1148. }
  1149. return &followers, nil
  1150. }
  1151. // CanCollect returns whether or not the given user can add the given post to a
  1152. // collection. This is true when a post is already owned by the user.
  1153. // NOTE: this is currently only used to potentially add owned posts to a
  1154. // collection. This has the SIDE EFFECT of also generating a slug for the post.
  1155. // FIXME: make this side effect more explicit (or extract it)
  1156. func (db *datastore) CanCollect(cpr *ClaimPostRequest, userID int64) bool {
  1157. var title, content string
  1158. var lang sql.NullString
  1159. err := db.QueryRow("SELECT title, content, language FROM posts WHERE id = ? AND owner_id = ?", cpr.ID, userID).Scan(&title, &content, &lang)
  1160. switch {
  1161. case err == sql.ErrNoRows:
  1162. return false
  1163. case err != nil:
  1164. log.Error("Failed on post CanCollect(%s, %d): %v", cpr.ID, userID, err)
  1165. return false
  1166. }
  1167. // Since we have the post content and the post is collectable, generate the
  1168. // post's slug now.
  1169. cpr.Slug = getSlugFromPost(title, content, lang.String)
  1170. return true
  1171. }
  1172. func (db *datastore) AttemptClaim(p *ClaimPostRequest, query string, params []interface{}, slugIdx int) (sql.Result, error) {
  1173. qRes, err := db.Exec(query, params...)
  1174. if err != nil {
  1175. if db.isDuplicateKeyErr(err) && slugIdx > -1 {
  1176. s := id.GenSafeUniqueSlug(p.Slug)
  1177. if s == p.Slug {
  1178. // Sanity check to prevent infinite recursion
  1179. return qRes, fmt.Errorf("GenSafeUniqueSlug generated nothing unique: %s", s)
  1180. }
  1181. p.Slug = s
  1182. params[slugIdx] = p.Slug
  1183. return db.AttemptClaim(p, query, params, slugIdx)
  1184. }
  1185. return qRes, fmt.Errorf("attemptClaim: %s", err)
  1186. }
  1187. return qRes, nil
  1188. }
  1189. func (db *datastore) DispersePosts(userID int64, postIDs []string) (*[]ClaimPostResult, error) {
  1190. postClaimReqs := map[string]bool{}
  1191. res := []ClaimPostResult{}
  1192. for i := range postIDs {
  1193. postID := postIDs[i]
  1194. r := ClaimPostResult{Code: 0, ErrorMessage: ""}
  1195. // Perform post validation
  1196. if postID == "" {
  1197. r.ErrorMessage = "Missing post ID. "
  1198. }
  1199. if _, ok := postClaimReqs[postID]; ok {
  1200. r.Code = 429
  1201. r.ErrorMessage = "You've already tried anonymizing this post."
  1202. r.ID = postID
  1203. res = append(res, r)
  1204. continue
  1205. }
  1206. postClaimReqs[postID] = true
  1207. var err error
  1208. // Get full post information to return
  1209. var fullPost *PublicPost
  1210. fullPost, err = db.GetPost(postID, 0)
  1211. if err != nil {
  1212. if err, ok := err.(impart.HTTPError); ok {
  1213. r.Code = err.Status
  1214. r.ErrorMessage = err.Message
  1215. r.ID = postID
  1216. res = append(res, r)
  1217. continue
  1218. } else {
  1219. log.Error("Error getting post in dispersePosts: %v", err)
  1220. }
  1221. }
  1222. if fullPost.OwnerID.Int64 != userID {
  1223. r.Code = http.StatusConflict
  1224. r.ErrorMessage = "Post is already owned by someone else."
  1225. r.ID = postID
  1226. res = append(res, r)
  1227. continue
  1228. }
  1229. var qRes sql.Result
  1230. var query string
  1231. var params []interface{}
  1232. // Do AND owner_id = ? for sanity.
  1233. // This should've been caught and returned with a good error message
  1234. // just above.
  1235. query = "UPDATE posts SET collection_id = NULL WHERE id = ? AND owner_id = ?"
  1236. params = []interface{}{postID, userID}
  1237. qRes, err = db.Exec(query, params...)
  1238. if err != nil {
  1239. r.Code = http.StatusInternalServerError
  1240. r.ErrorMessage = "A glitch happened on our end."
  1241. r.ID = postID
  1242. res = append(res, r)
  1243. log.Error("dispersePosts (post %s): %v", postID, err)
  1244. continue
  1245. }
  1246. // Post was successfully dispersed
  1247. r.Code = http.StatusOK
  1248. r.Post = fullPost
  1249. rowsAffected, _ := qRes.RowsAffected()
  1250. if rowsAffected == 0 {
  1251. // This was already claimed, but return 200
  1252. r.Code = http.StatusOK
  1253. }
  1254. res = append(res, r)
  1255. }
  1256. return &res, nil
  1257. }
  1258. func (db *datastore) ClaimPosts(cfg *config.Config, userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error) {
  1259. postClaimReqs := map[string]bool{}
  1260. res := []ClaimPostResult{}
  1261. postCollAlias := collAlias
  1262. for i := range *posts {
  1263. p := (*posts)[i]
  1264. if &p == nil {
  1265. continue
  1266. }
  1267. r := ClaimPostResult{Code: 0, ErrorMessage: ""}
  1268. // Perform post validation
  1269. if p.ID == "" {
  1270. r.ErrorMessage = "Missing post ID `id`. "
  1271. }
  1272. if _, ok := postClaimReqs[p.ID]; ok {
  1273. r.Code = 429
  1274. r.ErrorMessage = "You've already tried claiming this post."
  1275. r.ID = p.ID
  1276. res = append(res, r)
  1277. continue
  1278. }
  1279. postClaimReqs[p.ID] = true
  1280. canCollect := db.CanCollect(&p, userID)
  1281. if !canCollect && p.Token == "" {
  1282. // TODO: ensure post isn't owned by anyone else when a valid modify
  1283. // token is given.
  1284. r.ErrorMessage += "Missing post Edit Token `token`."
  1285. }
  1286. if r.ErrorMessage != "" {
  1287. // Post validate failed
  1288. r.Code = http.StatusBadRequest
  1289. r.ID = p.ID
  1290. res = append(res, r)
  1291. continue
  1292. }
  1293. var err error
  1294. var qRes sql.Result
  1295. var query string
  1296. var params []interface{}
  1297. var slugIdx int = -1
  1298. var coll *Collection
  1299. if collAlias == "" {
  1300. // Posts are being claimed at /posts/claim, not
  1301. // /collections/{alias}/collect, so use given individual collection
  1302. // to associate post with.
  1303. postCollAlias = p.CollectionAlias
  1304. }
  1305. if postCollAlias != "" {
  1306. // Associate this post with a collection
  1307. if p.CreateCollection {
  1308. // This is a new collection
  1309. // TODO: consider removing this. This seriously complicates this
  1310. // method and adds another (unnecessary?) logic path.
  1311. coll, err = db.CreateCollection(cfg, postCollAlias, "", userID)
  1312. if err != nil {
  1313. if err, ok := err.(impart.HTTPError); ok {
  1314. r.Code = err.Status
  1315. r.ErrorMessage = err.Message
  1316. } else {
  1317. r.Code = http.StatusInternalServerError
  1318. r.ErrorMessage = "Unknown error occurred creating collection"
  1319. }
  1320. r.ID = p.ID
  1321. res = append(res, r)
  1322. continue
  1323. }
  1324. } else {
  1325. // Attempt to add to existing collection
  1326. coll, err = db.GetCollection(postCollAlias)
  1327. if err != nil {
  1328. if err, ok := err.(impart.HTTPError); ok {
  1329. if err.Status == http.StatusNotFound {
  1330. // Show obfuscated "forbidden" response, as if attempting to add to an
  1331. // unowned blog.
  1332. r.Code = ErrForbiddenCollection.Status
  1333. r.ErrorMessage = ErrForbiddenCollection.Message
  1334. } else {
  1335. r.Code = err.Status
  1336. r.ErrorMessage = err.Message
  1337. }
  1338. } else {
  1339. r.Code = http.StatusInternalServerError
  1340. r.ErrorMessage = "Unknown error occurred claiming post with collection"
  1341. }
  1342. r.ID = p.ID
  1343. res = append(res, r)
  1344. continue
  1345. }
  1346. if coll.OwnerID != userID {
  1347. r.Code = ErrForbiddenCollection.Status
  1348. r.ErrorMessage = ErrForbiddenCollection.Message
  1349. r.ID = p.ID
  1350. res = append(res, r)
  1351. continue
  1352. }
  1353. }
  1354. if p.Slug == "" {
  1355. p.Slug = p.ID
  1356. }
  1357. if canCollect {
  1358. // User already owns this post, so just add it to the given
  1359. // collection.
  1360. query = "UPDATE posts SET collection_id = ?, slug = ? WHERE id = ? AND owner_id = ?"
  1361. params = []interface{}{coll.ID, p.Slug, p.ID, userID}
  1362. slugIdx = 1
  1363. } else {
  1364. query = "UPDATE posts SET owner_id = ?, collection_id = ?, slug = ? WHERE id = ? AND modify_token = ? AND owner_id IS NULL"
  1365. params = []interface{}{userID, coll.ID, p.Slug, p.ID, p.Token}
  1366. slugIdx = 2
  1367. }
  1368. } else {
  1369. query = "UPDATE posts SET owner_id = ? WHERE id = ? AND modify_token = ? AND owner_id IS NULL"
  1370. params = []interface{}{userID, p.ID, p.Token}
  1371. }
  1372. qRes, err = db.AttemptClaim(&p, query, params, slugIdx)
  1373. if err != nil {
  1374. r.Code = http.StatusInternalServerError
  1375. r.ErrorMessage = "An unknown error occurred."
  1376. r.ID = p.ID
  1377. res = append(res, r)
  1378. log.Error("claimPosts (post %s): %v", p.ID, err)
  1379. continue
  1380. }
  1381. // Get full post information to return
  1382. var fullPost *PublicPost
  1383. if p.Token != "" {
  1384. fullPost, err = db.GetEditablePost(p.ID, p.Token)
  1385. } else {
  1386. fullPost, err = db.GetPost(p.ID, 0)
  1387. }
  1388. if err != nil {
  1389. if err, ok := err.(impart.HTTPError); ok {
  1390. r.Code = err.Status
  1391. r.ErrorMessage = err.Message
  1392. r.ID = p.ID
  1393. res = append(res, r)
  1394. continue
  1395. }
  1396. }
  1397. if fullPost.OwnerID.Int64 != userID {
  1398. r.Code = http.StatusConflict
  1399. r.ErrorMessage = "Post is already owned by someone else."
  1400. r.ID = p.ID
  1401. res = append(res, r)
  1402. continue
  1403. }
  1404. // Post was successfully claimed
  1405. r.Code = http.StatusOK
  1406. r.Post = fullPost
  1407. if coll != nil {
  1408. r.Post.Collection = &CollectionObj{Collection: *coll}
  1409. }
  1410. rowsAffected, _ := qRes.RowsAffected()
  1411. if rowsAffected == 0 {
  1412. // This was already claimed, but return 200
  1413. r.Code = http.StatusOK
  1414. }
  1415. res = append(res, r)
  1416. }
  1417. return &res, nil
  1418. }
  1419. func (db *datastore) UpdatePostPinState(pinned bool, postID string, collID, ownerID, pos int64) error {
  1420. if pos <= 0 || pos > 20 {
  1421. pos = db.GetLastPinnedPostPos(collID) + 1
  1422. if pos == -1 {
  1423. pos = 1
  1424. }
  1425. }
  1426. var err error
  1427. if pinned {
  1428. _, err = db.Exec("UPDATE posts SET pinned_position = ? WHERE id = ?", pos, postID)
  1429. } else {
  1430. _, err = db.Exec("UPDATE posts SET pinned_position = NULL WHERE id = ?", postID)
  1431. }
  1432. if err != nil {
  1433. log.Error("Unable to update pinned post: %v", err)
  1434. return err
  1435. }
  1436. return nil
  1437. }
  1438. func (db *datastore) GetLastPinnedPostPos(collID int64) int64 {
  1439. var lastPos sql.NullInt64
  1440. err := db.QueryRow("SELECT MAX(pinned_position) FROM posts WHERE collection_id = ? AND pinned_position IS NOT NULL", collID).Scan(&lastPos)
  1441. switch {
  1442. case err == sql.ErrNoRows:
  1443. return -1
  1444. case err != nil:
  1445. log.Error("Failed selecting from posts: %v", err)
  1446. return -1
  1447. }
  1448. if !lastPos.Valid {
  1449. return -1
  1450. }
  1451. return lastPos.Int64
  1452. }
  1453. func (db *datastore) GetPinnedPosts(coll *CollectionObj, includeFuture bool) (*[]PublicPost, error) {
  1454. // FIXME: sqlite-backed instances don't include ellipsis on truncated titles
  1455. timeCondition := ""
  1456. if !includeFuture {
  1457. timeCondition = "AND created <= " + db.now()
  1458. }
  1459. 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 "+timeCondition+" ORDER BY pinned_position ASC", coll.ID)
  1460. if err != nil {
  1461. log.Error("Failed selecting pinned posts: %v", err)
  1462. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve pinned posts."}
  1463. }
  1464. defer rows.Close()
  1465. posts := []PublicPost{}
  1466. for rows.Next() {
  1467. p := &Post{}
  1468. err = rows.Scan(&p.ID, &p.Slug, &p.Title, &p.Content, &p.PinnedPosition)
  1469. if err != nil {
  1470. log.Error("Failed scanning row: %v", err)
  1471. break
  1472. }
  1473. p.extractData()
  1474. p.augmentContent(&coll.Collection)
  1475. pp := p.processPost()
  1476. pp.Collection = coll
  1477. posts = append(posts, pp)
  1478. }
  1479. return &posts, nil
  1480. }
  1481. func (db *datastore) GetCollections(u *User, hostName string) (*[]Collection, error) {
  1482. rows, err := db.Query("SELECT id, alias, title, description, privacy, view_count FROM collections WHERE owner_id = ? ORDER BY id ASC", u.ID)
  1483. if err != nil {
  1484. log.Error("Failed selecting from collections: %v", err)
  1485. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user collections."}
  1486. }
  1487. defer rows.Close()
  1488. colls := []Collection{}
  1489. for rows.Next() {
  1490. c := Collection{}
  1491. err = rows.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &c.Visibility, &c.Views)
  1492. if err != nil {
  1493. log.Error("Failed scanning row: %v", err)
  1494. break
  1495. }
  1496. c.hostName = hostName
  1497. c.URL = c.CanonicalURL()
  1498. c.Public = c.IsPublic()
  1499. /*
  1500. // NOTE: future functionality
  1501. if visibility != nil { // TODO: && visibility == CollPublic {
  1502. // Add Monetization info when retrieving all public collections
  1503. c.Monetization = db.GetCollectionAttribute(c.ID, "monetization_pointer")
  1504. }
  1505. */
  1506. colls = append(colls, c)
  1507. }
  1508. err = rows.Err()
  1509. if err != nil {
  1510. log.Error("Error after Next() on rows: %v", err)
  1511. }
  1512. return &colls, nil
  1513. }
  1514. func (db *datastore) GetPublishableCollections(u *User, hostName string) (*[]Collection, error) {
  1515. c, err := db.GetCollections(u, hostName)
  1516. if err != nil {
  1517. return nil, err
  1518. }
  1519. if len(*c) == 0 {
  1520. 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."}
  1521. }
  1522. return c, nil
  1523. }
  1524. func (db *datastore) GetPublicCollections(hostName string) (*[]Collection, error) {
  1525. rows, err := db.Query(`SELECT c.id, alias, title, description, privacy, view_count
  1526. FROM collections c
  1527. LEFT JOIN users u ON u.id = c.owner_id
  1528. WHERE c.privacy = 1 AND u.status = 0
  1529. ORDER BY id ASC`)
  1530. if err != nil {
  1531. log.Error("Failed selecting public collections: %v", err)
  1532. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve public collections."}
  1533. }
  1534. defer rows.Close()
  1535. colls := []Collection{}
  1536. for rows.Next() {
  1537. c := Collection{}
  1538. err = rows.Scan(&c.ID, &c.Alias, &c.Title, &c.Description, &c.Visibility, &c.Views)
  1539. if err != nil {
  1540. log.Error("Failed scanning row: %v", err)
  1541. break
  1542. }
  1543. c.hostName = hostName
  1544. c.URL = c.CanonicalURL()
  1545. c.Public = c.IsPublic()
  1546. // Add Monetization information
  1547. c.Monetization = db.GetCollectionAttribute(c.ID, "monetization_pointer")
  1548. colls = append(colls, c)
  1549. }
  1550. err = rows.Err()
  1551. if err != nil {
  1552. log.Error("Error after Next() on rows: %v", err)
  1553. }
  1554. return &colls, nil
  1555. }
  1556. func (db *datastore) GetMeStats(u *User) userMeStats {
  1557. s := userMeStats{}
  1558. // User counts
  1559. colls, _ := db.GetUserCollectionCount(u.ID)
  1560. s.TotalCollections = colls
  1561. var articles, collPosts uint64
  1562. err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE owner_id = ? AND collection_id IS NULL", u.ID).Scan(&articles)
  1563. if err != nil && err != sql.ErrNoRows {
  1564. log.Error("Couldn't get articles count for user %d: %v", u.ID, err)
  1565. }
  1566. s.TotalArticles = articles
  1567. err = db.QueryRow("SELECT COUNT(*) FROM posts WHERE owner_id = ? AND collection_id IS NOT NULL", u.ID).Scan(&collPosts)
  1568. if err != nil && err != sql.ErrNoRows {
  1569. log.Error("Couldn't get coll posts count for user %d: %v", u.ID, err)
  1570. }
  1571. s.CollectionPosts = collPosts
  1572. return s
  1573. }
  1574. func (db *datastore) GetTotalCollections() (collCount int64, err error) {
  1575. err = db.QueryRow(`
  1576. SELECT COUNT(*)
  1577. FROM collections c
  1578. LEFT JOIN users u ON u.id = c.owner_id
  1579. WHERE u.status = 0`).Scan(&collCount)
  1580. if err != nil {
  1581. log.Error("Unable to fetch collections count: %v", err)
  1582. }
  1583. return
  1584. }
  1585. func (db *datastore) GetTotalPosts() (postCount int64, err error) {
  1586. err = db.QueryRow(`
  1587. SELECT COUNT(*)
  1588. FROM posts p
  1589. LEFT JOIN users u ON u.id = p.owner_id
  1590. WHERE u.status = 0`).Scan(&postCount)
  1591. if err != nil {
  1592. log.Error("Unable to fetch posts count: %v", err)
  1593. }
  1594. return
  1595. }
  1596. func (db *datastore) GetTopPosts(u *User, alias string) (*[]PublicPost, error) {
  1597. params := []interface{}{u.ID}
  1598. where := ""
  1599. if alias != "" {
  1600. where = " AND alias = ?"
  1601. params = append(params, alias)
  1602. }
  1603. 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...)
  1604. if err != nil {
  1605. log.Error("Failed selecting from posts: %v", err)
  1606. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user top posts."}
  1607. }
  1608. defer rows.Close()
  1609. posts := []PublicPost{}
  1610. var gotErr bool
  1611. for rows.Next() {
  1612. p := Post{}
  1613. c := Collection{}
  1614. var alias, title, description sql.NullString
  1615. var views sql.NullInt64
  1616. err = rows.Scan(&p.ID, &p.Slug, &p.ViewCount, &p.Title, &alias, &title, &description, &views)
  1617. if err != nil {
  1618. log.Error("Failed scanning User.getPosts() row: %v", err)
  1619. gotErr = true
  1620. break
  1621. }
  1622. p.extractData()
  1623. pubPost := p.processPost()
  1624. if alias.Valid && alias.String != "" {
  1625. c.Alias = alias.String
  1626. c.Title = title.String
  1627. c.Description = description.String
  1628. c.Views = views.Int64
  1629. pubPost.Collection = &CollectionObj{Collection: c}
  1630. }
  1631. posts = append(posts, pubPost)
  1632. }
  1633. err = rows.Err()
  1634. if err != nil {
  1635. log.Error("Error after Next() on rows: %v", err)
  1636. }
  1637. if gotErr && len(posts) == 0 {
  1638. // There were a lot of errors
  1639. return nil, impart.HTTPError{http.StatusInternalServerError, "Unable to get data."}
  1640. }
  1641. return &posts, nil
  1642. }
  1643. func (db *datastore) GetAnonymousPosts(u *User, page int) (*[]PublicPost, error) {
  1644. pagePosts := 10
  1645. start := page*pagePosts - pagePosts
  1646. if page == 0 {
  1647. start = 0
  1648. pagePosts = 1000
  1649. }
  1650. limitStr := ""
  1651. if page > 0 {
  1652. limitStr = fmt.Sprintf(" LIMIT %d, %d", start, pagePosts)
  1653. }
  1654. 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"+limitStr, u.ID)
  1655. if err != nil {
  1656. log.Error("Failed selecting from posts: %v", err)
  1657. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user anonymous posts."}
  1658. }
  1659. defer rows.Close()
  1660. posts := []PublicPost{}
  1661. for rows.Next() {
  1662. p := Post{}
  1663. err = rows.Scan(&p.ID, &p.ViewCount, &p.Title, &p.Created, &p.Updated, &p.Content)
  1664. if err != nil {
  1665. log.Error("Failed scanning row: %v", err)
  1666. break
  1667. }
  1668. p.extractData()
  1669. posts = append(posts, p.processPost())
  1670. }
  1671. err = rows.Err()
  1672. if err != nil {
  1673. log.Error("Error after Next() on rows: %v", err)
  1674. }
  1675. return &posts, nil
  1676. }
  1677. func (db *datastore) GetUserPosts(u *User) (*[]PublicPost, error) {
  1678. 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)
  1679. if err != nil {
  1680. log.Error("Failed selecting from posts: %v", err)
  1681. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user posts."}
  1682. }
  1683. defer rows.Close()
  1684. posts := []PublicPost{}
  1685. var gotErr bool
  1686. for rows.Next() {
  1687. p := Post{}
  1688. c := Collection{}
  1689. var alias, title, description sql.NullString
  1690. var views sql.NullInt64
  1691. 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)
  1692. if err != nil {
  1693. log.Error("Failed scanning User.getPosts() row: %v", err)
  1694. gotErr = true
  1695. break
  1696. }
  1697. p.extractData()
  1698. pubPost := p.processPost()
  1699. if alias.Valid && alias.String != "" {
  1700. c.Alias = alias.String
  1701. c.Title = title.String
  1702. c.Description = description.String
  1703. c.Views = views.Int64
  1704. pubPost.Collection = &CollectionObj{Collection: c}
  1705. }
  1706. posts = append(posts, pubPost)
  1707. }
  1708. err = rows.Err()
  1709. if err != nil {
  1710. log.Error("Error after Next() on rows: %v", err)
  1711. }
  1712. if gotErr && len(posts) == 0 {
  1713. // There were a lot of errors
  1714. return nil, impart.HTTPError{http.StatusInternalServerError, "Unable to get data."}
  1715. }
  1716. return &posts, nil
  1717. }
  1718. func (db *datastore) GetUserPostsCount(userID int64) int64 {
  1719. var count int64
  1720. err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE owner_id = ?", userID).Scan(&count)
  1721. switch {
  1722. case err == sql.ErrNoRows:
  1723. return 0
  1724. case err != nil:
  1725. log.Error("Failed selecting posts count for user %d: %v", userID, err)
  1726. return 0
  1727. }
  1728. return count
  1729. }
  1730. // ChangeSettings takes a User and applies the changes in the given
  1731. // userSettings, MODIFYING THE USER with successful changes.
  1732. func (db *datastore) ChangeSettings(app *App, u *User, s *userSettings) error {
  1733. var errPass error
  1734. q := query.NewUpdate()
  1735. // Update email if given
  1736. if s.Email != "" {
  1737. encEmail, err := data.Encrypt(app.keys.EmailKey, s.Email)
  1738. if err != nil {
  1739. log.Error("Couldn't encrypt email %s: %s\n", s.Email, err)
  1740. return impart.HTTPError{http.StatusInternalServerError, "Unable to encrypt email address."}
  1741. }
  1742. q.SetBytes(encEmail, "email")
  1743. // Update the email if something goes awry updating the password
  1744. defer func() {
  1745. if errPass != nil {
  1746. db.UpdateEncryptedUserEmail(u.ID, encEmail)
  1747. }
  1748. }()
  1749. u.Email = zero.StringFrom(s.Email)
  1750. }
  1751. // Update username if given
  1752. var newUsername string
  1753. if s.Username != "" {
  1754. var ie *impart.HTTPError
  1755. newUsername, ie = getValidUsername(app, s.Username, u.Username)
  1756. if ie != nil {
  1757. // Username is invalid
  1758. return *ie
  1759. }
  1760. if !author.IsValidUsername(app.cfg, newUsername) {
  1761. // Ensure the username is syntactically correct.
  1762. return impart.HTTPError{http.StatusPreconditionFailed, "Username isn't valid."}
  1763. }
  1764. t, err := db.Begin()
  1765. if err != nil {
  1766. log.Error("Couldn't start username change transaction: %v", err)
  1767. return err
  1768. }
  1769. _, err = t.Exec("UPDATE users SET username = ? WHERE id = ?", newUsername, u.ID)
  1770. if err != nil {
  1771. t.Rollback()
  1772. if db.isDuplicateKeyErr(err) {
  1773. return impart.HTTPError{http.StatusConflict, "Username is already taken."}
  1774. }
  1775. log.Error("Unable to update users table: %v", err)
  1776. return ErrInternalGeneral
  1777. }
  1778. _, err = t.Exec("UPDATE collections SET alias = ? WHERE alias = ? AND owner_id = ?", newUsername, u.Username, u.ID)
  1779. if err != nil {
  1780. t.Rollback()
  1781. if db.isDuplicateKeyErr(err) {
  1782. return impart.HTTPError{http.StatusConflict, "Username is already taken."}
  1783. }
  1784. log.Error("Unable to update collection: %v", err)
  1785. return ErrInternalGeneral
  1786. }
  1787. // Keep track of name changes for redirection
  1788. db.RemoveCollectionRedirect(t, newUsername)
  1789. _, err = t.Exec("UPDATE collectionredirects SET new_alias = ? WHERE new_alias = ?", newUsername, u.Username)
  1790. if err != nil {
  1791. log.Error("Unable to update collectionredirects: %v", err)
  1792. }
  1793. _, err = t.Exec("INSERT INTO collectionredirects (prev_alias, new_alias) VALUES (?, ?)", u.Username, newUsername)
  1794. if err != nil {
  1795. log.Error("Unable to add new collectionredirect: %v", err)
  1796. }
  1797. err = t.Commit()
  1798. if err != nil {
  1799. t.Rollback()
  1800. log.Error("Rolling back after Commit(): %v\n", err)
  1801. return err
  1802. }
  1803. u.Username = newUsername
  1804. }
  1805. // Update passphrase if given
  1806. if s.NewPass != "" {
  1807. // Check if user has already set a password
  1808. var err error
  1809. u.HasPass, err = db.IsUserPassSet(u.ID)
  1810. if err != nil {
  1811. errPass = impart.HTTPError{http.StatusInternalServerError, "Unable to retrieve user data."}
  1812. return errPass
  1813. }
  1814. if u.HasPass {
  1815. // Check if currently-set password is correct
  1816. hashedPass := u.HashedPass
  1817. if len(hashedPass) == 0 {
  1818. authUser, err := db.GetUserForAuthByID(u.ID)
  1819. if err != nil {
  1820. errPass = err
  1821. return errPass
  1822. }
  1823. hashedPass = authUser.HashedPass
  1824. }
  1825. if !auth.Authenticated(hashedPass, []byte(s.OldPass)) {
  1826. errPass = impart.HTTPError{http.StatusUnauthorized, "Incorrect password."}
  1827. return errPass
  1828. }
  1829. }
  1830. hashedPass, err := auth.HashPass([]byte(s.NewPass))
  1831. if err != nil {
  1832. errPass = impart.HTTPError{http.StatusInternalServerError, "Could not create password hash."}
  1833. return errPass
  1834. }
  1835. q.SetBytes(hashedPass, "password")
  1836. }
  1837. // WHERE values
  1838. q.Append(u.ID)
  1839. if q.Updates == "" {
  1840. if s.Username == "" {
  1841. return ErrPostNoUpdatableVals
  1842. }
  1843. // Nothing to update except username. That was successful, so return now.
  1844. return nil
  1845. }
  1846. res, err := db.Exec("UPDATE users SET "+q.Updates+" WHERE id = ?", q.Params...)
  1847. if err != nil {
  1848. log.Error("Unable to update collection: %v", err)
  1849. return err
  1850. }
  1851. rowsAffected, _ := res.RowsAffected()
  1852. if rowsAffected == 0 {
  1853. // Show the correct error message if nothing was updated
  1854. var dummy int
  1855. err := db.QueryRow("SELECT 1 FROM users WHERE id = ?", u.ID).Scan(&dummy)
  1856. switch {
  1857. case err == sql.ErrNoRows:
  1858. return ErrUnauthorizedGeneral
  1859. case err != nil:
  1860. log.Error("Failed selecting from users: %v", err)
  1861. }
  1862. return nil
  1863. }
  1864. if s.NewPass != "" && !u.HasPass {
  1865. u.HasPass = true
  1866. }
  1867. return nil
  1868. }
  1869. func (db *datastore) ChangePassphrase(userID int64, sudo bool, curPass string, hashedPass []byte) error {
  1870. var dbPass []byte
  1871. err := db.QueryRow("SELECT password FROM users WHERE id = ?", userID).Scan(&dbPass)
  1872. switch {
  1873. case err == sql.ErrNoRows:
  1874. return ErrUserNotFound
  1875. case err != nil:
  1876. log.Error("Couldn't SELECT user password for change: %v", err)
  1877. return err
  1878. }
  1879. if !sudo && !auth.Authenticated(dbPass, []byte(curPass)) {
  1880. return impart.HTTPError{http.StatusUnauthorized, "Incorrect password."}
  1881. }
  1882. _, err = db.Exec("UPDATE users SET password = ? WHERE id = ?", hashedPass, userID)
  1883. if err != nil {
  1884. log.Error("Could not update passphrase: %v", err)
  1885. return err
  1886. }
  1887. return nil
  1888. }
  1889. func (db *datastore) RemoveCollectionRedirect(t *sql.Tx, alias string) error {
  1890. _, err := t.Exec("DELETE FROM collectionredirects WHERE prev_alias = ?", alias)
  1891. if err != nil {
  1892. log.Error("Unable to delete from collectionredirects: %v", err)
  1893. return err
  1894. }
  1895. return nil
  1896. }
  1897. func (db *datastore) GetCollectionRedirect(alias string) (new string) {
  1898. row := db.QueryRow("SELECT new_alias FROM collectionredirects WHERE prev_alias = ?", alias)
  1899. err := row.Scan(&new)
  1900. if err != nil && err != sql.ErrNoRows && !db.isIgnorableError(err) {
  1901. log.Error("Failed selecting from collectionredirects: %v", err)
  1902. }
  1903. return
  1904. }
  1905. func (db *datastore) DeleteCollection(alias string, userID int64) error {
  1906. c := &Collection{Alias: alias}
  1907. var username string
  1908. row := db.QueryRow("SELECT username FROM users WHERE id = ?", userID)
  1909. err := row.Scan(&username)
  1910. if err != nil {
  1911. return err
  1912. }
  1913. // Ensure user isn't deleting their main blog
  1914. if alias == username {
  1915. return impart.HTTPError{http.StatusForbidden, "You cannot currently delete your primary blog."}
  1916. }
  1917. row = db.QueryRow("SELECT id FROM collections WHERE alias = ? AND owner_id = ?", alias, userID)
  1918. err = row.Scan(&c.ID)
  1919. switch {
  1920. case err == sql.ErrNoRows:
  1921. return impart.HTTPError{http.StatusNotFound, "Collection doesn't exist or you're not allowed to delete it."}
  1922. case err != nil:
  1923. log.Error("Failed selecting from collections: %v", err)
  1924. return ErrInternalGeneral
  1925. }
  1926. t, err := db.Begin()
  1927. if err != nil {
  1928. return err
  1929. }
  1930. // Float all collection's posts
  1931. _, err = t.Exec("UPDATE posts SET collection_id = NULL WHERE collection_id = ? AND owner_id = ?", c.ID, userID)
  1932. if err != nil {
  1933. t.Rollback()
  1934. return err
  1935. }
  1936. // Remove redirects to or from this collection
  1937. _, err = t.Exec("DELETE FROM collectionredirects WHERE prev_alias = ? OR new_alias = ?", alias, alias)
  1938. if err != nil {
  1939. t.Rollback()
  1940. return err
  1941. }
  1942. // Remove any optional collection password
  1943. _, err = t.Exec("DELETE FROM collectionpasswords WHERE collection_id = ?", c.ID)
  1944. if err != nil {
  1945. t.Rollback()
  1946. return err
  1947. }
  1948. // Finally, delete collection itself
  1949. _, err = t.Exec("DELETE FROM collections WHERE id = ?", c.ID)
  1950. if err != nil {
  1951. t.Rollback()
  1952. return err
  1953. }
  1954. err = t.Commit()
  1955. if err != nil {
  1956. t.Rollback()
  1957. return err
  1958. }
  1959. return nil
  1960. }
  1961. func (db *datastore) IsCollectionAttributeOn(id int64, attr string) bool {
  1962. var v string
  1963. err := db.QueryRow("SELECT value FROM collectionattributes WHERE collection_id = ? AND attribute = ?", id, attr).Scan(&v)
  1964. switch {
  1965. case err == sql.ErrNoRows:
  1966. return false
  1967. case err != nil:
  1968. log.Error("Couldn't SELECT value in isCollectionAttributeOn for attribute '%s': %v", attr, err)
  1969. return false
  1970. }
  1971. return v == "1"
  1972. }
  1973. func (db *datastore) CollectionHasAttribute(id int64, attr string) bool {
  1974. var dummy string
  1975. err := db.QueryRow("SELECT value FROM collectionattributes WHERE collection_id = ? AND attribute = ?", id, attr).Scan(&dummy)
  1976. switch {
  1977. case err == sql.ErrNoRows:
  1978. return false
  1979. case err != nil:
  1980. log.Error("Couldn't SELECT value in collectionHasAttribute for attribute '%s': %v", attr, err)
  1981. return false
  1982. }
  1983. return true
  1984. }
  1985. func (db *datastore) GetCollectionAttribute(id int64, attr string) string {
  1986. var v string
  1987. err := db.QueryRow("SELECT value FROM collectionattributes WHERE collection_id = ? AND attribute = ?", id, attr).Scan(&v)
  1988. switch {
  1989. case err == sql.ErrNoRows:
  1990. return ""
  1991. case err != nil:
  1992. log.Error("Couldn't SELECT value in getCollectionAttribute for attribute '%s': %v", attr, err)
  1993. return ""
  1994. }
  1995. return v
  1996. }
  1997. func (db *datastore) SetCollectionAttribute(id int64, attr, v string) error {
  1998. _, err := db.Exec("INSERT INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?)", id, attr, v)
  1999. if err != nil {
  2000. log.Error("Unable to INSERT into collectionattributes: %v", err)
  2001. return err
  2002. }
  2003. return nil
  2004. }
  2005. // DeleteAccount will delete the entire account for userID
  2006. func (db *datastore) DeleteAccount(userID int64) error {
  2007. // Get all collections
  2008. rows, err := db.Query("SELECT id, alias FROM collections WHERE owner_id = ?", userID)
  2009. if err != nil {
  2010. log.Error("Unable to get collections: %v", err)
  2011. return err
  2012. }
  2013. defer rows.Close()
  2014. colls := []Collection{}
  2015. var c Collection
  2016. for rows.Next() {
  2017. err = rows.Scan(&c.ID, &c.Alias)
  2018. if err != nil {
  2019. log.Error("Unable to scan collection cols: %v", err)
  2020. return err
  2021. }
  2022. colls = append(colls, c)
  2023. }
  2024. // Start transaction
  2025. t, err := db.Begin()
  2026. if err != nil {
  2027. log.Error("Unable to begin: %v", err)
  2028. return err
  2029. }
  2030. // Clean up all collection related information
  2031. var res sql.Result
  2032. for _, c := range colls {
  2033. // Delete tokens
  2034. res, err = t.Exec("DELETE FROM collectionattributes WHERE collection_id = ?", c.ID)
  2035. if err != nil {
  2036. t.Rollback()
  2037. log.Error("Unable to delete attributes on %s: %v", c.Alias, err)
  2038. return err
  2039. }
  2040. rs, _ := res.RowsAffected()
  2041. log.Info("Deleted %d for %s from collectionattributes", rs, c.Alias)
  2042. // Remove any optional collection password
  2043. res, err = t.Exec("DELETE FROM collectionpasswords WHERE collection_id = ?", c.ID)
  2044. if err != nil {
  2045. t.Rollback()
  2046. log.Error("Unable to delete passwords on %s: %v", c.Alias, err)
  2047. return err
  2048. }
  2049. rs, _ = res.RowsAffected()
  2050. log.Info("Deleted %d for %s from collectionpasswords", rs, c.Alias)
  2051. // Remove redirects to this collection
  2052. res, err = t.Exec("DELETE FROM collectionredirects WHERE new_alias = ?", c.Alias)
  2053. if err != nil {
  2054. t.Rollback()
  2055. log.Error("Unable to delete redirects on %s: %v", c.Alias, err)
  2056. return err
  2057. }
  2058. rs, _ = res.RowsAffected()
  2059. log.Info("Deleted %d for %s from collectionredirects", rs, c.Alias)
  2060. // Remove any collection keys
  2061. res, err = t.Exec("DELETE FROM collectionkeys WHERE collection_id = ?", c.ID)
  2062. if err != nil {
  2063. t.Rollback()
  2064. log.Error("Unable to delete keys on %s: %v", c.Alias, err)
  2065. return err
  2066. }
  2067. rs, _ = res.RowsAffected()
  2068. log.Info("Deleted %d for %s from collectionkeys", rs, c.Alias)
  2069. // TODO: federate delete collection
  2070. // Remove remote follows
  2071. res, err = t.Exec("DELETE FROM remotefollows WHERE collection_id = ?", c.ID)
  2072. if err != nil {
  2073. t.Rollback()
  2074. log.Error("Unable to delete remote follows on %s: %v", c.Alias, err)
  2075. return err
  2076. }
  2077. rs, _ = res.RowsAffected()
  2078. log.Info("Deleted %d for %s from remotefollows", rs, c.Alias)
  2079. }
  2080. // Delete collections
  2081. res, err = t.Exec("DELETE FROM collections WHERE owner_id = ?", userID)
  2082. if err != nil {
  2083. t.Rollback()
  2084. log.Error("Unable to delete collections: %v", err)
  2085. return err
  2086. }
  2087. rs, _ := res.RowsAffected()
  2088. log.Info("Deleted %d from collections", rs)
  2089. // Delete tokens
  2090. res, err = t.Exec("DELETE FROM accesstokens WHERE user_id = ?", userID)
  2091. if err != nil {
  2092. t.Rollback()
  2093. log.Error("Unable to delete access tokens: %v", err)
  2094. return err
  2095. }
  2096. rs, _ = res.RowsAffected()
  2097. log.Info("Deleted %d from accesstokens", rs)
  2098. // Delete user attributes
  2099. res, err = t.Exec("DELETE FROM oauth_users WHERE user_id = ?", userID)
  2100. if err != nil {
  2101. t.Rollback()
  2102. log.Error("Unable to delete oauth_users: %v", err)
  2103. return err
  2104. }
  2105. rs, _ = res.RowsAffected()
  2106. log.Info("Deleted %d from oauth_users", rs)
  2107. // Delete posts
  2108. // TODO: should maybe get each row so we can federate a delete
  2109. // if so needs to be outside of transaction like collections
  2110. res, err = t.Exec("DELETE FROM posts WHERE owner_id = ?", userID)
  2111. if err != nil {
  2112. t.Rollback()
  2113. log.Error("Unable to delete posts: %v", err)
  2114. return err
  2115. }
  2116. rs, _ = res.RowsAffected()
  2117. log.Info("Deleted %d from posts", rs)
  2118. // Delete user attributes
  2119. res, err = t.Exec("DELETE FROM userattributes WHERE user_id = ?", userID)
  2120. if err != nil {
  2121. t.Rollback()
  2122. log.Error("Unable to delete attributes: %v", err)
  2123. return err
  2124. }
  2125. rs, _ = res.RowsAffected()
  2126. log.Info("Deleted %d from userattributes", rs)
  2127. // Delete user invites
  2128. res, err = t.Exec("DELETE FROM userinvites WHERE owner_id = ?", userID)
  2129. if err != nil {
  2130. t.Rollback()
  2131. log.Error("Unable to delete invites: %v", err)
  2132. return err
  2133. }
  2134. rs, _ = res.RowsAffected()
  2135. log.Info("Deleted %d from userinvites", rs)
  2136. // Delete the user
  2137. res, err = t.Exec("DELETE FROM users WHERE id = ?", userID)
  2138. if err != nil {
  2139. t.Rollback()
  2140. log.Error("Unable to delete user: %v", err)
  2141. return err
  2142. }
  2143. rs, _ = res.RowsAffected()
  2144. log.Info("Deleted %d from users", rs)
  2145. // Commit all changes to the database
  2146. err = t.Commit()
  2147. if err != nil {
  2148. t.Rollback()
  2149. log.Error("Unable to commit: %v", err)
  2150. return err
  2151. }
  2152. // TODO: federate delete actor
  2153. return nil
  2154. }
  2155. func (db *datastore) GetAPActorKeys(collectionID int64) ([]byte, []byte) {
  2156. var pub, priv []byte
  2157. err := db.QueryRow("SELECT public_key, private_key FROM collectionkeys WHERE collection_id = ?", collectionID).Scan(&pub, &priv)
  2158. switch {
  2159. case err == sql.ErrNoRows:
  2160. // Generate keys
  2161. pub, priv = activitypub.GenerateKeys()
  2162. _, err = db.Exec("INSERT INTO collectionkeys (collection_id, public_key, private_key) VALUES (?, ?, ?)", collectionID, pub, priv)
  2163. if err != nil {
  2164. log.Error("Unable to INSERT new activitypub keypair: %v", err)
  2165. return nil, nil
  2166. }
  2167. case err != nil:
  2168. log.Error("Couldn't SELECT collectionkeys: %v", err)
  2169. return nil, nil
  2170. }
  2171. return pub, priv
  2172. }
  2173. func (db *datastore) CreateUserInvite(id string, userID int64, maxUses int, expires *time.Time) error {
  2174. _, err := db.Exec("INSERT INTO userinvites (id, owner_id, max_uses, created, expires, inactive) VALUES (?, ?, ?, "+db.now()+", ?, 0)", id, userID, maxUses, expires)
  2175. return err
  2176. }
  2177. func (db *datastore) GetUserInvites(userID int64) (*[]Invite, error) {
  2178. rows, err := db.Query("SELECT id, max_uses, created, expires, inactive FROM userinvites WHERE owner_id = ? ORDER BY created DESC", userID)
  2179. if err != nil {
  2180. log.Error("Failed selecting from userinvites: %v", err)
  2181. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user invites."}
  2182. }
  2183. defer rows.Close()
  2184. is := []Invite{}
  2185. for rows.Next() {
  2186. i := Invite{}
  2187. err = rows.Scan(&i.ID, &i.MaxUses, &i.Created, &i.Expires, &i.Inactive)
  2188. is = append(is, i)
  2189. }
  2190. return &is, nil
  2191. }
  2192. func (db *datastore) GetUserInvite(id string) (*Invite, error) {
  2193. var i Invite
  2194. 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)
  2195. switch {
  2196. case err == sql.ErrNoRows, db.isIgnorableError(err):
  2197. return nil, impart.HTTPError{http.StatusNotFound, "Invite doesn't exist."}
  2198. case err != nil:
  2199. log.Error("Failed selecting invite: %v", err)
  2200. return nil, err
  2201. }
  2202. return &i, nil
  2203. }
  2204. // IsUsersInvite returns true if the user with ID created the invite with code
  2205. // and an error other than sql no rows, if any. Will return false in the event
  2206. // of an error.
  2207. func (db *datastore) IsUsersInvite(code string, userID int64) (bool, error) {
  2208. var id string
  2209. err := db.QueryRow("SELECT id FROM userinvites WHERE id = ? AND owner_id = ?", code, userID).Scan(&id)
  2210. if err != nil && err != sql.ErrNoRows {
  2211. log.Error("Failed selecting invite: %v", err)
  2212. return false, err
  2213. }
  2214. return id != "", nil
  2215. }
  2216. func (db *datastore) GetUsersInvitedCount(id string) int64 {
  2217. var count int64
  2218. err := db.QueryRow("SELECT COUNT(*) FROM usersinvited WHERE invite_id = ?", id).Scan(&count)
  2219. switch {
  2220. case err == sql.ErrNoRows:
  2221. return 0
  2222. case err != nil:
  2223. log.Error("Failed selecting users invited count: %v", err)
  2224. return 0
  2225. }
  2226. return count
  2227. }
  2228. func (db *datastore) CreateInvitedUser(inviteID string, userID int64) error {
  2229. _, err := db.Exec("INSERT INTO usersinvited (invite_id, user_id) VALUES (?, ?)", inviteID, userID)
  2230. return err
  2231. }
  2232. func (db *datastore) GetInstancePages() ([]*instanceContent, error) {
  2233. return db.GetAllDynamicContent("page")
  2234. }
  2235. func (db *datastore) GetAllDynamicContent(t string) ([]*instanceContent, error) {
  2236. where := ""
  2237. params := []interface{}{}
  2238. if t != "" {
  2239. where = " WHERE content_type = ?"
  2240. params = append(params, t)
  2241. }
  2242. rows, err := db.Query("SELECT id, title, content, updated, content_type FROM appcontent"+where, params...)
  2243. if err != nil {
  2244. log.Error("Failed selecting from appcontent: %v", err)
  2245. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve instance pages."}
  2246. }
  2247. defer rows.Close()
  2248. pages := []*instanceContent{}
  2249. for rows.Next() {
  2250. c := &instanceContent{}
  2251. err = rows.Scan(&c.ID, &c.Title, &c.Content, &c.Updated, &c.Type)
  2252. if err != nil {
  2253. log.Error("Failed scanning row: %v", err)
  2254. break
  2255. }
  2256. pages = append(pages, c)
  2257. }
  2258. err = rows.Err()
  2259. if err != nil {
  2260. log.Error("Error after Next() on rows: %v", err)
  2261. }
  2262. return pages, nil
  2263. }
  2264. func (db *datastore) GetDynamicContent(id string) (*instanceContent, error) {
  2265. c := &instanceContent{
  2266. ID: id,
  2267. }
  2268. err := db.QueryRow("SELECT title, content, updated, content_type FROM appcontent WHERE id = ?", id).Scan(&c.Title, &c.Content, &c.Updated, &c.Type)
  2269. switch {
  2270. case err == sql.ErrNoRows:
  2271. return nil, nil
  2272. case err != nil:
  2273. log.Error("Couldn't SELECT FROM appcontent for id '%s': %v", id, err)
  2274. return nil, err
  2275. }
  2276. return c, nil
  2277. }
  2278. func (db *datastore) UpdateDynamicContent(id, title, content, contentType string) error {
  2279. var err error
  2280. if db.driverName == driverSQLite {
  2281. _, err = db.Exec("INSERT OR REPLACE INTO appcontent (id, title, content, updated, content_type) VALUES (?, ?, ?, "+db.now()+", ?)", id, title, content, contentType)
  2282. } else {
  2283. _, 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)
  2284. }
  2285. if err != nil {
  2286. log.Error("Unable to INSERT appcontent for '%s': %v", id, err)
  2287. }
  2288. return err
  2289. }
  2290. func (db *datastore) GetAllUsers(page uint) (*[]User, error) {
  2291. limitStr := fmt.Sprintf("0, %d", adminUsersPerPage)
  2292. if page > 1 {
  2293. limitStr = fmt.Sprintf("%d, %d", (page-1)*adminUsersPerPage, adminUsersPerPage)
  2294. }
  2295. rows, err := db.Query("SELECT id, username, created, status FROM users ORDER BY created DESC LIMIT " + limitStr)
  2296. if err != nil {
  2297. log.Error("Failed selecting from users: %v", err)
  2298. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve all users."}
  2299. }
  2300. defer rows.Close()
  2301. users := []User{}
  2302. for rows.Next() {
  2303. u := User{}
  2304. err = rows.Scan(&u.ID, &u.Username, &u.Created, &u.Status)
  2305. if err != nil {
  2306. log.Error("Failed scanning GetAllUsers() row: %v", err)
  2307. break
  2308. }
  2309. users = append(users, u)
  2310. }
  2311. return &users, nil
  2312. }
  2313. func (db *datastore) GetAllUsersCount() int64 {
  2314. var count int64
  2315. err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
  2316. switch {
  2317. case err == sql.ErrNoRows:
  2318. return 0
  2319. case err != nil:
  2320. log.Error("Failed selecting all users count: %v", err)
  2321. return 0
  2322. }
  2323. return count
  2324. }
  2325. func (db *datastore) GetUserLastPostTime(id int64) (*time.Time, error) {
  2326. var t time.Time
  2327. err := db.QueryRow("SELECT created FROM posts WHERE owner_id = ? ORDER BY created DESC LIMIT 1", id).Scan(&t)
  2328. switch {
  2329. case err == sql.ErrNoRows:
  2330. return nil, nil
  2331. case err != nil:
  2332. log.Error("Failed selecting last post time from posts: %v", err)
  2333. return nil, err
  2334. }
  2335. return &t, nil
  2336. }
  2337. // SetUserStatus changes a user's status in the database. see Users.UserStatus
  2338. func (db *datastore) SetUserStatus(id int64, status UserStatus) error {
  2339. _, err := db.Exec("UPDATE users SET status = ? WHERE id = ?", status, id)
  2340. if err != nil {
  2341. return fmt.Errorf("failed to update user status: %v", err)
  2342. }
  2343. return nil
  2344. }
  2345. func (db *datastore) GetCollectionLastPostTime(id int64) (*time.Time, error) {
  2346. var t time.Time
  2347. err := db.QueryRow("SELECT created FROM posts WHERE collection_id = ? ORDER BY created DESC LIMIT 1", id).Scan(&t)
  2348. switch {
  2349. case err == sql.ErrNoRows:
  2350. return nil, nil
  2351. case err != nil:
  2352. log.Error("Failed selecting last post time from posts: %v", err)
  2353. return nil, err
  2354. }
  2355. return &t, nil
  2356. }
  2357. func (db *datastore) GenerateOAuthState(ctx context.Context, provider string, clientID string, attachUser int64, inviteCode string) (string, error) {
  2358. state := id.Generate62RandomString(24)
  2359. attachUserVal := sql.NullInt64{Valid: attachUser > 0, Int64: attachUser}
  2360. inviteCodeVal := sql.NullString{Valid: inviteCode != "", String: inviteCode}
  2361. _, err := db.ExecContext(ctx, "INSERT INTO oauth_client_states (state, provider, client_id, used, created_at, attach_user_id, invite_code) VALUES (?, ?, ?, FALSE, "+db.now()+", ?, ?)", state, provider, clientID, attachUserVal, inviteCodeVal)
  2362. if err != nil {
  2363. return "", fmt.Errorf("unable to record oauth client state: %w", err)
  2364. }
  2365. return state, nil
  2366. }
  2367. func (db *datastore) ValidateOAuthState(ctx context.Context, state string) (string, string, int64, string, error) {
  2368. var provider string
  2369. var clientID string
  2370. var attachUserID sql.NullInt64
  2371. var inviteCode sql.NullString
  2372. err := wf_db.RunTransactionWithOptions(ctx, db.DB, &sql.TxOptions{}, func(ctx context.Context, tx *sql.Tx) error {
  2373. err := tx.
  2374. QueryRowContext(ctx, "SELECT provider, client_id, attach_user_id, invite_code FROM oauth_client_states WHERE state = ? AND used = FALSE", state).
  2375. Scan(&provider, &clientID, &attachUserID, &inviteCode)
  2376. if err != nil {
  2377. return err
  2378. }
  2379. res, err := tx.ExecContext(ctx, "UPDATE oauth_client_states SET used = TRUE WHERE state = ?", state)
  2380. if err != nil {
  2381. return err
  2382. }
  2383. rowsAffected, err := res.RowsAffected()
  2384. if err != nil {
  2385. return err
  2386. }
  2387. if rowsAffected != 1 {
  2388. return fmt.Errorf("state not found")
  2389. }
  2390. return nil
  2391. })
  2392. if err != nil {
  2393. return "", "", 0, "", nil
  2394. }
  2395. return provider, clientID, attachUserID.Int64, inviteCode.String, nil
  2396. }
  2397. func (db *datastore) RecordRemoteUserID(ctx context.Context, localUserID int64, remoteUserID, provider, clientID, accessToken string) error {
  2398. var err error
  2399. if db.driverName == driverSQLite {
  2400. _, err = db.ExecContext(ctx, "INSERT OR REPLACE INTO oauth_users (user_id, remote_user_id, provider, client_id, access_token) VALUES (?, ?, ?, ?, ?)", localUserID, remoteUserID, provider, clientID, accessToken)
  2401. } else {
  2402. _, err = db.ExecContext(ctx, "INSERT INTO oauth_users (user_id, remote_user_id, provider, client_id, access_token) VALUES (?, ?, ?, ?, ?) "+db.upsert("user")+" access_token = ?", localUserID, remoteUserID, provider, clientID, accessToken, accessToken)
  2403. }
  2404. if err != nil {
  2405. log.Error("Unable to INSERT oauth_users for '%d': %v", localUserID, err)
  2406. }
  2407. return err
  2408. }
  2409. // GetIDForRemoteUser returns a user ID associated with a remote user ID.
  2410. func (db *datastore) GetIDForRemoteUser(ctx context.Context, remoteUserID, provider, clientID string) (int64, error) {
  2411. var userID int64 = -1
  2412. err := db.
  2413. QueryRowContext(ctx, "SELECT user_id FROM oauth_users WHERE remote_user_id = ? AND provider = ? AND client_id = ?", remoteUserID, provider, clientID).
  2414. Scan(&userID)
  2415. // Not finding a record is OK.
  2416. if err != nil && err != sql.ErrNoRows {
  2417. return -1, err
  2418. }
  2419. return userID, nil
  2420. }
  2421. type oauthAccountInfo struct {
  2422. Provider string
  2423. ClientID string
  2424. RemoteUserID string
  2425. DisplayName string
  2426. AllowDisconnect bool
  2427. }
  2428. func (db *datastore) GetOauthAccounts(ctx context.Context, userID int64) ([]oauthAccountInfo, error) {
  2429. rows, err := db.QueryContext(ctx, "SELECT provider, client_id, remote_user_id FROM oauth_users WHERE user_id = ? ", userID)
  2430. if err != nil {
  2431. log.Error("Failed selecting from oauth_users: %v", err)
  2432. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user oauth accounts."}
  2433. }
  2434. defer rows.Close()
  2435. var records []oauthAccountInfo
  2436. for rows.Next() {
  2437. info := oauthAccountInfo{}
  2438. err = rows.Scan(&info.Provider, &info.ClientID, &info.RemoteUserID)
  2439. if err != nil {
  2440. log.Error("Failed scanning GetAllUsers() row: %v", err)
  2441. break
  2442. }
  2443. records = append(records, info)
  2444. }
  2445. return records, nil
  2446. }
  2447. // DatabaseInitialized returns whether or not the current datastore has been
  2448. // initialized with the correct schema.
  2449. // Currently, it checks to see if the `users` table exists.
  2450. func (db *datastore) DatabaseInitialized() bool {
  2451. var dummy string
  2452. var err error
  2453. if db.driverName == driverSQLite {
  2454. err = db.QueryRow("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'users'").Scan(&dummy)
  2455. } else {
  2456. err = db.QueryRow("SHOW TABLES LIKE 'users'").Scan(&dummy)
  2457. }
  2458. switch {
  2459. case err == sql.ErrNoRows:
  2460. return false
  2461. case err != nil:
  2462. log.Error("Couldn't SHOW TABLES: %v", err)
  2463. return false
  2464. }
  2465. return true
  2466. }
  2467. func (db *datastore) RemoveOauth(ctx context.Context, userID int64, provider string, clientID string, remoteUserID string) error {
  2468. _, err := db.ExecContext(ctx, `DELETE FROM oauth_users WHERE user_id = ? AND provider = ? AND client_id = ? AND remote_user_id = ?`, userID, provider, clientID, remoteUserID)
  2469. return err
  2470. }
  2471. func stringLogln(log *string, s string, v ...interface{}) {
  2472. *log += fmt.Sprintf(s+"\n", v...)
  2473. }
  2474. func handleFailedPostInsert(err error) error {
  2475. log.Error("Couldn't insert into posts: %v", err)
  2476. return err
  2477. }
  2478. func (db *datastore) GetProfilePageFromHandle(app *App, handle string) (string, error) {
  2479. handle = strings.TrimLeft(handle, "@")
  2480. actorIRI := ""
  2481. parts := strings.Split(handle, "@")
  2482. if len(parts) != 2 {
  2483. return "", fmt.Errorf("invalid handle format")
  2484. }
  2485. domain := parts[1]
  2486. // Check non-AP instances
  2487. if siloProfileURL := silobridge.Profile(parts[0], domain); siloProfileURL != "" {
  2488. return siloProfileURL, nil
  2489. }
  2490. remoteUser, err := getRemoteUserFromHandle(app, handle)
  2491. if err != nil {
  2492. // can't find using handle in the table but the table may already have this user without
  2493. // handle from a previous version
  2494. // TODO: Make this determination. We should know whether a user exists without a handle, or doesn't exist at all
  2495. actorIRI = RemoteLookup(handle)
  2496. _, errRemoteUser := getRemoteUser(app, actorIRI)
  2497. // if it exists then we need to update the handle
  2498. if errRemoteUser == nil {
  2499. _, err := app.db.Exec("UPDATE remoteusers SET handle = ? WHERE actor_id = ?", handle, actorIRI)
  2500. if err != nil {
  2501. log.Error("Couldn't update handle '%s' for user %s", handle, actorIRI)
  2502. }
  2503. } else {
  2504. // this probably means we don't have the user in the table so let's try to insert it
  2505. // here we need to ask the server for the inboxes
  2506. remoteActor, err := activityserve.NewRemoteActor(actorIRI)
  2507. if err != nil {
  2508. log.Error("Couldn't fetch remote actor: %v", err)
  2509. }
  2510. if debugging {
  2511. log.Info("%s %s %s %s", actorIRI, remoteActor.GetInbox(), remoteActor.GetSharedInbox(), handle)
  2512. }
  2513. _, err = app.db.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox, handle) VALUES(?, ?, ?, ?)", actorIRI, remoteActor.GetInbox(), remoteActor.GetSharedInbox(), handle)
  2514. if err != nil {
  2515. log.Error("Couldn't insert remote user: %v", err)
  2516. return "", err
  2517. }
  2518. }
  2519. } else {
  2520. actorIRI = remoteUser.ActorID
  2521. }
  2522. return actorIRI, nil
  2523. }
  2524. func (db *datastore) AddEmailSubscription(collID, userID int64, email string, confirmed bool) (*EmailSubscriber, error) {
  2525. friendlyChars := "0123456789BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz"
  2526. subID := id.GenerateRandomString(friendlyChars, 8)
  2527. token := id.GenerateRandomString(friendlyChars, 16)
  2528. emailVal := sql.NullString{
  2529. String: email,
  2530. Valid: email != "",
  2531. }
  2532. userIDVal := sql.NullInt64{
  2533. Int64: userID,
  2534. Valid: userID > 0,
  2535. }
  2536. _, err := db.Exec("INSERT INTO emailsubscribers (id, collection_id, user_id, email, subscribed, token, confirmed) VALUES (?, ?, ?, ?, NOW(), ?, ?)", subID, collID, userIDVal, emailVal, token, confirmed)
  2537. if err != nil {
  2538. if mysqlErr, ok := err.(*mysql.MySQLError); ok {
  2539. if mysqlErr.Number == mySQLErrDuplicateKey {
  2540. // Duplicate, so just return existing subscriber information
  2541. log.Info("Duplicate subscriber for email %s, user %d; returning existing subscriber", email, userID)
  2542. return db.FetchEmailSubscriber(email, userID, collID)
  2543. }
  2544. }
  2545. return nil, err
  2546. }
  2547. return &EmailSubscriber{
  2548. ID: subID,
  2549. CollID: collID,
  2550. UserID: userIDVal,
  2551. Email: emailVal,
  2552. Token: token,
  2553. }, nil
  2554. }
  2555. func (db *datastore) IsEmailSubscriber(email string, userID, collID int64) bool {
  2556. var dummy int
  2557. var err error
  2558. if email != "" {
  2559. err = db.QueryRow("SELECT 1 FROM emailsubscribers WHERE email = ? AND collection_id = ?", email, collID).Scan(&dummy)
  2560. } else {
  2561. err = db.QueryRow("SELECT 1 FROM emailsubscribers WHERE user_id = ? AND collection_id = ?", userID, collID).Scan(&dummy)
  2562. }
  2563. switch {
  2564. case err == sql.ErrNoRows:
  2565. return false
  2566. case err != nil:
  2567. return false
  2568. }
  2569. return true
  2570. }
  2571. func (db *datastore) GetEmailSubscribers(collID int64, reqConfirmed bool) ([]*EmailSubscriber, error) {
  2572. cond := ""
  2573. if reqConfirmed {
  2574. cond = " AND confirmed = 1"
  2575. }
  2576. rows, err := db.Query(`SELECT s.id, collection_id, user_id, s.email, u.email, subscribed, token, confirmed, allow_export
  2577. FROM emailsubscribers s
  2578. LEFT JOIN users u
  2579. ON u.id = user_id
  2580. WHERE collection_id = ?`+cond+`
  2581. ORDER BY subscribed DESC`, collID)
  2582. if err != nil {
  2583. log.Error("Failed selecting email subscribers for collection %d: %v", collID, err)
  2584. return nil, err
  2585. }
  2586. defer rows.Close()
  2587. var subs []*EmailSubscriber
  2588. for rows.Next() {
  2589. s := &EmailSubscriber{}
  2590. err = rows.Scan(&s.ID, &s.CollID, &s.UserID, &s.Email, &s.acctEmail, &s.Subscribed, &s.Token, &s.Confirmed, &s.AllowExport)
  2591. if err != nil {
  2592. log.Error("Failed scanning row from email subscribers: %v", err)
  2593. continue
  2594. }
  2595. subs = append(subs, s)
  2596. }
  2597. return subs, nil
  2598. }
  2599. func (db *datastore) FetchEmailSubscriberEmail(subID, token string) (string, error) {
  2600. var email sql.NullString
  2601. // TODO: return user email if there's a user_id ?
  2602. err := db.QueryRow("SELECT email FROM emailsubscribers WHERE id = ? AND token = ?", subID, token).Scan(&email)
  2603. switch {
  2604. case err == sql.ErrNoRows:
  2605. return "", fmt.Errorf("Subscriber doesn't exist or token is invalid.")
  2606. case err != nil:
  2607. log.Error("Couldn't SELECT email from emailsubscribers: %v", err)
  2608. return "", fmt.Errorf("Something went very wrong.")
  2609. }
  2610. return email.String, nil
  2611. }
  2612. func (db *datastore) FetchEmailSubscriber(email string, userID, collID int64) (*EmailSubscriber, error) {
  2613. const emailSubCols = "id, collection_id, user_id, email, subscribed, token, confirmed, allow_export"
  2614. s := &EmailSubscriber{}
  2615. var row *sql.Row
  2616. if email != "" {
  2617. row = db.QueryRow("SELECT "+emailSubCols+" FROM emailsubscribers WHERE email = ? AND collection_id = ?", email, collID)
  2618. } else {
  2619. row = db.QueryRow("SELECT "+emailSubCols+" FROM emailsubscribers WHERE user_id = ? AND collection_id = ?", userID, collID)
  2620. }
  2621. err := row.Scan(&s.ID, &s.CollID, &s.UserID, &s.Email, &s.Subscribed, &s.Token, &s.Confirmed, &s.AllowExport)
  2622. switch {
  2623. case err == sql.ErrNoRows:
  2624. return nil, nil
  2625. case err != nil:
  2626. return nil, err
  2627. }
  2628. return s, nil
  2629. }
  2630. func (db *datastore) DeleteEmailSubscriber(subID, token string) error {
  2631. res, err := db.Exec("DELETE FROM emailsubscribers WHERE id = ? AND token = ?", subID, token)
  2632. if err != nil {
  2633. return err
  2634. }
  2635. rowsAffected, _ := res.RowsAffected()
  2636. if rowsAffected == 0 {
  2637. return impart.HTTPError{http.StatusNotFound, "Invalid token, or subscriber doesn't exist"}
  2638. }
  2639. return nil
  2640. }
  2641. func (db *datastore) DeleteEmailSubscriberByUser(email string, userID, collID int64) error {
  2642. var res sql.Result
  2643. var err error
  2644. if email != "" {
  2645. res, err = db.Exec("DELETE FROM emailsubscribers WHERE email = ? AND collection_id = ?", email, collID)
  2646. } else {
  2647. res, err = db.Exec("DELETE FROM emailsubscribers WHERE user_id = ? AND collection_id = ?", userID, collID)
  2648. }
  2649. if err != nil {
  2650. return err
  2651. }
  2652. rowsAffected, _ := res.RowsAffected()
  2653. if rowsAffected == 0 {
  2654. return impart.HTTPError{http.StatusNotFound, "Subscriber doesn't exist"}
  2655. }
  2656. return nil
  2657. }
  2658. func (db *datastore) UpdateSubscriberConfirmed(subID, token string) error {
  2659. email, err := db.FetchEmailSubscriberEmail(subID, token)
  2660. if err != nil {
  2661. log.Error("Didn't fetch email subscriber: %v", err)
  2662. return err
  2663. }
  2664. // TODO: ensure all addresses with original name are also confirmed, e.g. matt+fake@write.as and matt@write.as are now confirmed
  2665. _, err = db.Exec("UPDATE emailsubscribers SET confirmed = 1 WHERE email = ?", email)
  2666. if err != nil {
  2667. log.Error("Could not update email subscriber confirmation status: %v", err)
  2668. return err
  2669. }
  2670. return nil
  2671. }
  2672. func (db *datastore) IsSubscriberConfirmed(email string) bool {
  2673. var dummy int64
  2674. err := db.QueryRow("SELECT 1 FROM emailsubscribers WHERE email = ? AND confirmed = 1", email).Scan(&dummy)
  2675. switch {
  2676. case err == sql.ErrNoRows:
  2677. return false
  2678. case err != nil:
  2679. log.Error("Couldn't SELECT in isSubscriberConfirmed: %v", err)
  2680. return false
  2681. }
  2682. return true
  2683. }
  2684. func (db *datastore) InsertJob(j *PostJob) error {
  2685. res, err := db.Exec("INSERT INTO publishjobs (post_id, action, delay) VALUES (?, ?, ?)", j.PostID, j.Action, j.Delay)
  2686. if err != nil {
  2687. return err
  2688. }
  2689. jobID, err := res.LastInsertId()
  2690. if err != nil {
  2691. log.Error("[jobs] Couldn't get last insert ID! %s", err)
  2692. }
  2693. log.Info("[jobs] Queued %s job #%d for post %s, delayed %d minutes", j.Action, jobID, j.PostID, j.Delay)
  2694. return nil
  2695. }
  2696. func (db *datastore) UpdateJobForPost(postID string, delay int64) error {
  2697. _, err := db.Exec("UPDATE publishjobs SET delay = ? WHERE post_id = ?", delay, postID)
  2698. if err != nil {
  2699. return fmt.Errorf("Unable to update publish job: %s", err)
  2700. }
  2701. log.Info("Updated job for post %s: delay %d", postID, delay)
  2702. return nil
  2703. }
  2704. func (db *datastore) DeleteJob(id int64) error {
  2705. _, err := db.Exec("DELETE FROM publishjobs WHERE id = ?", id)
  2706. if err != nil {
  2707. return err
  2708. }
  2709. log.Info("[job #%d] Deleted.", id)
  2710. return nil
  2711. }
  2712. func (db *datastore) DeleteJobByPost(postID string) error {
  2713. _, err := db.Exec("DELETE FROM publishjobs WHERE post_id = ?", postID)
  2714. if err != nil {
  2715. return err
  2716. }
  2717. log.Info("[job] Deleted job for post %s", postID)
  2718. return nil
  2719. }
  2720. func (db *datastore) GetJobsToRun(action string) ([]*PostJob, error) {
  2721. rows, err := db.Query(`SELECT pj.id, post_id, action, delay
  2722. FROM publishjobs pj
  2723. INNER JOIN posts p
  2724. ON post_id = p.id
  2725. WHERE action = ? AND created < DATE_SUB(NOW(), INTERVAL delay MINUTE) AND created > DATE_SUB(NOW(), INTERVAL delay + 5 MINUTE)
  2726. ORDER BY created ASC`, action)
  2727. if err != nil {
  2728. log.Error("Failed selecting from publishjobs: %v", err)
  2729. return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve publish jobs."}
  2730. }
  2731. defer rows.Close()
  2732. jobs := []*PostJob{}
  2733. for rows.Next() {
  2734. j := &PostJob{}
  2735. err = rows.Scan(&j.ID, &j.PostID, &j.Action, &j.Delay)
  2736. jobs = append(jobs, j)
  2737. }
  2738. return jobs, nil
  2739. }