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