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.
 
 
 
 
 

2680 lines
82 KiB

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