A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 

2790 linhas
85 KiB

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