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.
 
 
 
 
 

3298 lines
101 KiB

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