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

2485 lines
74 KiB

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