Initial sqlite3 compatibility

This commit is contained in:
2018-11-13 00:58:44 +00:00
parent e15ca16d17
commit 80e66d2b32
7 changed files with 123 additions and 170 deletions

View File

@@ -7,7 +7,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
"github.com/writeas/activity/streams"
"github.com/writeas/httpsig"
@@ -344,12 +343,10 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
// Add follower locally, since it wasn't found before
res, err := t.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox) VALUES (?, ?, ?)", fullActor.ID, fullActor.Inbox, fullActor.Endpoints.SharedInbox)
if err != nil {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number != mySQLErrDuplicateKey {
t.Rollback()
log.Error("Couldn't add new remoteuser in DB: %v\n", err)
return
}
if isConstraintError(err) {
t.Rollback()
log.Error("Couldn't add new remoteuser in DB: %v\n", err)
return
} else {
t.Rollback()
log.Error("Couldn't add new remoteuser in DB: %v\n", err)
@@ -367,12 +364,10 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
// Add in key
_, err = t.Exec("INSERT INTO remoteuserkeys (id, remote_user_id, public_key) VALUES (?, ?, ?)", fullActor.PublicKey.ID, followerID, fullActor.PublicKey.PublicKeyPEM)
if err != nil {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number != mySQLErrDuplicateKey {
t.Rollback()
log.Error("Couldn't add follower keys in DB: %v\n", err)
return
}
if isConstraintError(err) {
t.Rollback()
log.Error("Couldn't add follower keys in DB: %v\n", err)
return
} else {
t.Rollback()
log.Error("Couldn't add follower keys in DB: %v\n", err)
@@ -382,14 +377,12 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
}
// Add follow
_, err = t.Exec("INSERT INTO remotefollows (collection_id, remote_user_id, created) VALUES (?, ?, NOW())", c.ID, followerID)
_, err = t.Exec("INSERT INTO remotefollows (collection_id, remote_user_id, created) VALUES (?, ?, "+sqlNow+")", c.ID, followerID)
if err != nil {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number != mySQLErrDuplicateKey {
t.Rollback()
log.Error("Couldn't add follower in DB: %v\n", err)
return
}
if isConstraintError(err) {
t.Rollback()
log.Error("Couldn't add follower in DB: %v\n", err)
return
} else {
t.Rollback()
log.Error("Couldn't add follower in DB: %v\n", err)

9
app.go
View File

@@ -4,7 +4,7 @@ import (
"database/sql"
"flag"
"fmt"
_ "github.com/go-sql-driver/mysql"
_ "github.com/mattn/go-sqlite3"
"html/template"
"net/http"
"os"
@@ -258,7 +258,12 @@ func Serve() {
func connectToDatabase(app *app) {
log.Info("Connecting to database...")
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true", app.cfg.Database.User, app.cfg.Database.Password, app.cfg.Database.Host, app.cfg.Database.Port, app.cfg.Database.Database))
if app.cfg.Database.Type != "sqlite3" {
log.Error("This fork of writefreely only supports sqlite3 databases")
os.Exit(1)
}
db, err := sql.Open("sqlite3", app.cfg.Database.Database)
if err != nil {
log.Error("%s", err)
os.Exit(1)

View File

@@ -59,9 +59,8 @@ func New() *Config {
Port: 8080,
},
Database: DatabaseCfg{
Type: "mysql",
Host: "localhost",
Port: 3306,
Type: "sqlite3",
Database: "writefreely.sqlite3",
},
App: AppCfg{
Host: "http://localhost:8080",

View File

@@ -65,30 +65,7 @@ func Configure() (*SetupData, error) {
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Username",
Validate: validateNonEmpty,
Default: data.Config.Database.User,
}
data.Config.Database.User, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Password",
Validate: validateNonEmpty,
Default: data.Config.Database.Password,
Mask: '*',
}
data.Config.Database.Password, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Database name",
Label: "Database filename",
Validate: validateNonEmpty,
Default: data.Config.Database.Database,
}
@@ -97,29 +74,6 @@ func Configure() (*SetupData, error) {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Host",
Validate: validateNonEmpty,
Default: data.Config.Database.Host,
}
data.Config.Database.Host, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Port",
Validate: validatePort,
Default: fmt.Sprintf("%d", data.Config.Database.Port),
}
dbPort, err := prompt.Run()
if err != nil {
return data, err
}
data.Config.Database.Port, _ = strconv.Atoi(dbPort) // Ignore error, as we've already validated number
fmt.Println()
title(" App setup ")
fmt.Println()

View File

@@ -7,7 +7,7 @@ import (
"strings"
"time"
"github.com/go-sql-driver/mysql"
"github.com/mattn/go-sqlite3"
"github.com/guregu/null"
"github.com/guregu/null/zero"
uuid "github.com/nu7hatch/gouuid"
@@ -22,10 +22,19 @@ import (
"github.com/writeas/writefreely/author"
)
const (
mySQLErrDuplicateKey = 1062
var (
sqlNow = `datetime('now')`
sqlExpires = `(expires IS NULL OR expires > `+sqlNow+`)`
)
func isConstraintError(err error) bool {
if sqliteErr, ok := err.(*sqlite3.Error); ok {
return sqliteErr.Code == sqlite3.ErrConstraint
}
return false
}
type writestore interface {
CreateUser(*User, string) error
UpdateUserEmail(keys *keychain, userID int64, email string) error
@@ -110,13 +119,11 @@ func (db *datastore) CreateUser(u *User, collectionTitle string) error {
// 1. Add to `users` table
// NOTE: Assumes User's Password is already hashed!
res, err := t.Exec("INSERT INTO users (username, password, email, created) VALUES (?, ?, ?, NOW())", u.Username, u.HashedPass, u.Email)
res, err := t.Exec("INSERT INTO users (username, password, email, created) VALUES (?, ?, ?, "+sqlNow+")", u.Username, u.HashedPass, u.Email)
if err != nil {
t.Rollback()
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number == mySQLErrDuplicateKey {
return impart.HTTPError{http.StatusConflict, "Username is already taken."}
}
if isConstraintError(err) {
return impart.HTTPError{http.StatusConflict, "Username is already taken."}
}
log.Error("Rolling back users INSERT: %v\n", err)
@@ -136,10 +143,8 @@ func (db *datastore) CreateUser(u *User, collectionTitle string) error {
res, err = t.Exec("INSERT INTO collections (alias, title, description, privacy, owner_id, view_count) VALUES (?, ?, ?, ?, ?, ?)", u.Username, collectionTitle, "", CollUnlisted, u.ID, 0)
if err != nil {
t.Rollback()
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number == mySQLErrDuplicateKey {
return impart.HTTPError{http.StatusConflict, "Username is already taken."}
}
if isConstraintError(err) {
return impart.HTTPError{http.StatusConflict, "Username is already taken."}
}
log.Error("Rolling back collections INSERT: %v\n", err)
return err
@@ -208,10 +213,8 @@ func (db *datastore) CreateCollection(alias, title string, userID int64) (*Colle
// All good, so create new collection
res, err := db.Exec("INSERT INTO collections (alias, title, description, privacy, owner_id, view_count) VALUES (?, ?, ?, ?, ?, ?)", alias, title, "", CollUnlisted, userID, 0)
if err != nil {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number == mySQLErrDuplicateKey {
return nil, impart.HTTPError{http.StatusConflict, "Collection already exists."}
}
if isConstraintError(err) {
return nil, impart.HTTPError{http.StatusConflict, "Collection already exists."}
}
log.Error("Couldn't add to collections: %v\n", err)
return nil, err
@@ -329,7 +332,7 @@ func (db *datastore) GetUserNameFromToken(accessToken string) (string, error) {
var oneTime bool
var username string
err := db.QueryRow("SELECT username, one_time FROM accesstokens LEFT JOIN users ON user_id = id WHERE token = ? AND (expires IS NULL OR expires > NOW())", t).Scan(&username, &oneTime)
err := db.QueryRow("SELECT username, one_time FROM accesstokens LEFT JOIN users ON user_id = id WHERE token = ? AND "+sqlExpires, t).Scan(&username, &oneTime)
switch {
case err == sql.ErrNoRows:
return "", ErrBadAccessToken
@@ -354,7 +357,7 @@ func (db *datastore) GetUserDataFromToken(accessToken string) (int64, string, er
var userID int64
var oneTime bool
var username string
err := db.QueryRow("SELECT user_id, username, one_time FROM accesstokens LEFT JOIN users ON user_id = id WHERE token = ? AND (expires IS NULL OR expires > NOW())", t).Scan(&userID, &username, &oneTime)
err := db.QueryRow("SELECT user_id, username, one_time FROM accesstokens LEFT JOIN users ON user_id = id WHERE token = ? AND "+sqlExpires, t).Scan(&userID, &username, &oneTime)
switch {
case err == sql.ErrNoRows:
return 0, "", ErrBadAccessToken
@@ -393,7 +396,7 @@ func (db *datastore) GetUserIDPrivilege(accessToken string) (userID int64, sudo
}
var oneTime bool
err := db.QueryRow("SELECT user_id, sudo, one_time FROM accesstokens WHERE token = ? AND (expires IS NULL OR expires > NOW())", t).Scan(&userID, &sudo, &oneTime)
err := db.QueryRow("SELECT user_id, sudo, one_time FROM accesstokens WHERE token = ? AND "+sqlExpires, t).Scan(&userID, &sudo, &oneTime)
switch {
case err == sql.ErrNoRows:
return -1, false
@@ -425,7 +428,7 @@ func (db *datastore) DeleteToken(accessToken []byte) error {
// userID.
func (db *datastore) FetchLastAccessToken(userID int64) string {
var t []byte
err := db.QueryRow("SELECT token FROM accesstokens WHERE user_id = ? AND (expires IS NULL OR expires > NOW()) ORDER BY created DESC LIMIT 1", userID).Scan(&t)
err := db.QueryRow("SELECT token FROM accesstokens WHERE user_id = ? AND "+sqlExpires+" ORDER BY created DESC LIMIT 1", userID).Scan(&t)
switch {
case err == sql.ErrNoRows:
return ""
@@ -468,12 +471,16 @@ func (db *datastore) GetTemporaryOneTimeAccessToken(userID int64, validSecs int,
// Insert UUID to `accesstokens`
binTok := u[:]
expirationVal := "NULL"
now := time.Now()
expirationVal := interface{}("NULL")
if validSecs > 0 {
expirationVal = fmt.Sprintf("DATE_ADD(NOW(), INTERVAL %d SECOND)", validSecs)
expirationVal = interface{}(now.Add(time.Duration(validSecs)*time.Second))
}
_, err = db.Exec("INSERT INTO accesstokens (token, user_id, created, one_time, expires) VALUES (?, ?, NOW(), ?, "+expirationVal+")", string(binTok), userID, oneTime)
_, err = db.Exec("INSERT INTO accesstokens (token, user_id, created, one_time, expires) VALUES (?, ?, ?, ?, ?)", string(binTok), userID, now, oneTime, expirationVal)
if err != nil {
log.Error("Couldn't INSERT accesstoken: %v", err)
return "", err
@@ -557,18 +564,16 @@ func (db *datastore) CreatePost(userID, collID int64, post *SubmittedPost) (*Pos
}
}
_, err = db.Exec("INSERT INTO posts (id, slug, title, content, text_appearance, language, rtl, owner_id, collection_id, updated, view_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?)", friendlyID, slug, post.Title, post.Content, appearance, post.Language, post.IsRTL, ownerID, ownerCollID, 0)
_, err = db.Exec("INSERT INTO posts (id, slug, title, content, text_appearance, language, rtl, owner_id, collection_id, updated, view_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, "+sqlNow+", ?)", friendlyID, slug, post.Title, post.Content, appearance, post.Language, post.IsRTL, ownerID, ownerCollID, 0)
if err != nil {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number == mySQLErrDuplicateKey {
// Duplicate entry error; try a new slug
// TODO: make this a little more robust
// TODO: reuse exact same db.Exec statement as above
slug = sql.NullString{id.GenSafeUniqueSlug(slug.String), true}
_, err = db.Exec("INSERT INTO posts (id, slug, title, content, text_appearance, language, rtl, owner_id, collection_id, updated, view_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?)", friendlyID, slug, post.Title, post.Content, appearance, post.Language, post.IsRTL, ownerID, ownerCollID, 0)
if err != nil {
return nil, handleFailedPostInsert(fmt.Errorf("Retried slug generation, still failed: %v", err))
}
if isConstraintError(err) {
// Duplicate entry error; try a new slug
// TODO: make this a little more robust
// TODO: reuse exact same db.Exec statement as above
slug = sql.NullString{id.GenSafeUniqueSlug(slug.String), true}
_, err = db.Exec("INSERT INTO posts (id, slug, title, content, text_appearance, language, rtl, owner_id, collection_id, updated, view_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, "+sqlNow+", ?)", friendlyID, slug, post.Title, post.Content, appearance, post.Language, post.IsRTL, ownerID, ownerCollID, 0)
if err != nil {
return nil, handleFailedPostInsert(fmt.Errorf("Retried slug generation, still failed: %v", err))
} else {
return nil, handleFailedPostInsert(err)
}
@@ -650,7 +655,7 @@ func (db *datastore) UpdateOwnedPost(post *AuthenticatedPost, userID int64) erro
return ErrPostNoUpdatableVals
}
queryUpdates += sep + "updated = NOW()"
queryUpdates += sep + "updated = " + sqlNow
res, err := db.Exec("UPDATE posts SET "+queryUpdates+" WHERE id = ? AND "+authCondition, params...)
if err != nil {
@@ -962,7 +967,7 @@ func (db *datastore) GetPostsCount(c *CollectionObj, includeFuture bool) {
var count int64
timeCondition := ""
if !includeFuture {
timeCondition = "AND created <= NOW()"
timeCondition = "AND created <= " + sqlNow
}
err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE collection_id = ? AND pinned_position IS NULL "+timeCondition, c.ID).Scan(&count)
switch {
@@ -1001,7 +1006,7 @@ func (db *datastore) GetPosts(c *Collection, page int, includeFuture bool) (*[]P
}
timeCondition := ""
if !includeFuture {
timeCondition = "AND created <= NOW()"
timeCondition = "AND created <= " + sqlNow
}
rows, err := db.Query("SELECT "+postCols+" FROM posts WHERE collection_id = ? AND pinned_position IS NULL "+timeCondition+" ORDER BY created "+order+limitStr, collID)
if err != nil {
@@ -1058,7 +1063,7 @@ func (db *datastore) GetPostsTagged(c *Collection, tag string, page int, include
}
timeCondition := ""
if !includeFuture {
timeCondition = "AND created <= NOW()"
timeCondition = "AND created <= " + sqlNow
}
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)+"[[:>:]]")
if err != nil {
@@ -1133,17 +1138,16 @@ func (db *datastore) CanCollect(cpr *ClaimPostRequest, userID int64) bool {
func (db *datastore) AttemptClaim(p *ClaimPostRequest, query string, params []interface{}, slugIdx int) (sql.Result, error) {
qRes, err := db.Exec(query, params...)
if err != nil {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number == mySQLErrDuplicateKey && slugIdx > -1 {
s := id.GenSafeUniqueSlug(p.Slug)
if s == p.Slug {
// Sanity check to prevent infinite recursion
return qRes, fmt.Errorf("GenSafeUniqueSlug generated nothing unique: %s", s)
}
p.Slug = s
params[slugIdx] = p.Slug
return db.AttemptClaim(p, query, params, slugIdx)
if isConstraintError(err) && slugIdx > -1 {
s := id.GenSafeUniqueSlug(p.Slug)
if s == p.Slug {
// Sanity check to prevent infinite recursion
return qRes, fmt.Errorf("GenSafeUniqueSlug generated nothing unique: %s", s)
}
p.Slug = s
params[slugIdx] = p.Slug
return db.AttemptClaim(p, query, params, slugIdx)
}
return qRes, fmt.Errorf("attemptClaim: %s", err)
}
@@ -1431,7 +1435,7 @@ func (db *datastore) GetLastPinnedPostPos(collID int64) int64 {
}
func (db *datastore) GetPinnedPosts(coll *CollectionObj) (*[]PublicPost, error) {
rows, err := db.Query("SELECT id, slug, title, LEFT(content, 80), pinned_position FROM posts WHERE collection_id = ? AND pinned_position IS NOT NULL ORDER BY pinned_position ASC", coll.ID)
rows, err := db.Query("SELECT id, slug, title, SUBSTR(content, 80), pinned_position FROM posts WHERE collection_id = ? AND pinned_position IS NOT NULL ORDER BY pinned_position ASC", coll.ID)
if err != nil {
log.Error("Failed selecting pinned posts: %v", err)
return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve pinned posts."}
@@ -1694,10 +1698,8 @@ func (db *datastore) ChangeSettings(app *app, u *User, s *userSettings) error {
_, err = t.Exec("UPDATE users SET username = ? WHERE id = ?", newUsername, u.ID)
if err != nil {
t.Rollback()
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number == mySQLErrDuplicateKey {
return impart.HTTPError{http.StatusConflict, "Username is already taken."}
}
if isConstraintError(err) {
return impart.HTTPError{http.StatusConflict, "Username is already taken."}
}
log.Error("Unable to update users table: %v", err)
return ErrInternalGeneral
@@ -1706,10 +1708,8 @@ func (db *datastore) ChangeSettings(app *app, u *User, s *userSettings) error {
_, err = t.Exec("UPDATE collections SET alias = ? WHERE alias = ? AND owner_id = ?", newUsername, u.Username, u.ID)
if err != nil {
t.Rollback()
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number == mySQLErrDuplicateKey {
return impart.HTTPError{http.StatusConflict, "Username is already taken."}
}
if isConstraintError(err) {
return impart.HTTPError{http.StatusConflict, "Username is already taken."}
}
log.Error("Unable to update collection: %v", err)
return ErrInternalGeneral

View File

@@ -5,6 +5,7 @@ import (
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/config"
"strings"
"time"
)
type nodeInfoResolver struct {
@@ -67,6 +68,10 @@ func (r nodeInfoResolver) Usage() (nodeinfo.Usage, error) {
log.Error("Unable to fetch post counts: %v", err)
}
ago_1 := time.Now().Add(-(30*24*time.Hour))
ago_6 := time.Now().Add(-(30*24*time.Hour*6))
if r.cfg.App.PublicStats {
// Display bi-yearly / monthly stats
err = r.db.QueryRow(`SELECT COUNT(*) FROM (
@@ -75,7 +80,7 @@ FROM posts
INNER JOIN collections c
ON collection_id = c.id
WHERE collection_id IS NOT NULL
AND updated > DATE_SUB(NOW(), INTERVAL 6 MONTH)) co`).Scan(&activeHalfYear)
AND updated > ? co`, ago_6).Scan(&activeHalfYear)
err = r.db.QueryRow(`SELECT COUNT(*) FROM (
SELECT DISTINCT collection_id
@@ -83,7 +88,7 @@ FROM posts
INNER JOIN FROM collections c
ON collection_id = c.id
WHERE collection_id IS NOT NULL
AND updated > DATE_SUB(NOW(), INTERVAL 1 MONTH)) co`).Scan(&activeMonth)
AND updated > ? co`, ago_1).Scan(&activeMonth)
}
return nodeinfo.Usage{

View File

@@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS `accesstokens` (
`expires` datetime DEFAULT NULL,
`user_agent` varchar(255) NOT NULL,
PRIMARY KEY (`token`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
);
-- --------------------------------------------------------
@@ -28,9 +28,9 @@ CREATE TABLE IF NOT EXISTS `accesstokens` (
CREATE TABLE IF NOT EXISTS `collectionattributes` (
`collection_id` int(6) NOT NULL,
`attribute` varchar(128) NOT NULL,
`value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`value` varchar(255) NOT NULL,
PRIMARY KEY (`collection_id`,`attribute`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
);
-- --------------------------------------------------------
@@ -43,7 +43,7 @@ CREATE TABLE IF NOT EXISTS `collectionkeys` (
`public_key` blob NOT NULL,
`private_key` blob NOT NULL,
PRIMARY KEY (`collection_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
);
-- --------------------------------------------------------
@@ -55,7 +55,7 @@ CREATE TABLE IF NOT EXISTS `collectionpasswords` (
`collection_id` int(6) NOT NULL,
`password` char(60) NOT NULL,
PRIMARY KEY (`collection_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
);
-- --------------------------------------------------------
@@ -67,7 +67,7 @@ CREATE TABLE IF NOT EXISTS `collectionredirects` (
`prev_alias` varchar(100) NOT NULL,
`new_alias` varchar(100) NOT NULL,
PRIMARY KEY (`prev_alias`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
);
-- --------------------------------------------------------
@@ -76,19 +76,18 @@ CREATE TABLE IF NOT EXISTS `collectionredirects` (
--
CREATE TABLE IF NOT EXISTS `collections` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`alias` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`description` varchar(160) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`id` integer PRIMARY KEY ASC,
`alias` varchar(100) DEFAULT NULL,
`title` varchar(255) NOT NULL,
`description` varchar(160) NOT NULL,
`style_sheet` text,
`script` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin,
`script` text,
`format` varchar(8) DEFAULT NULL,
`privacy` tinyint(1) NOT NULL,
`owner_id` int(6) NOT NULL,
`view_count` int(6) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `alias` (`alias`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CONSTRAINT `alias` UNIQUE (`alias`)
);
-- --------------------------------------------------------
@@ -106,17 +105,18 @@ CREATE TABLE IF NOT EXISTS `posts` (
`privacy` tinyint(1) NOT NULL,
`owner_id` int(6) DEFAULT NULL,
`collection_id` int(6) DEFAULT NULL,
`pinned_position` tinyint(1) UNSIGNED DEFAULT NULL,
`pinned_position` tinyint(1) DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated` timestamp NOT NULL,
`view_count` int(6) NOT NULL,
`title` varchar(160) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`title` varchar(160) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_slug` (`collection_id`,`slug`),
UNIQUE KEY `owner_id` (`owner_id`,`id`),
KEY `privacy_id` (`privacy`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CONSTRAINT `id_slug` UNIQUE (`collection_id`,`slug`),
CONSTRAINT `owner_id` UNIQUE (`owner_id`,`id`)
-- FIXME: this is KEY `privacy_id` (`privacy`,`id`) in mysql
-- CONSTRAINT `privacy_id` (`privacy`,`id`)
);
-- --------------------------------------------------------
@@ -129,7 +129,7 @@ CREATE TABLE IF NOT EXISTS `remotefollows` (
`remote_user_id` int(11) NOT NULL,
`created` datetime NOT NULL,
PRIMARY KEY (`collection_id`,`remote_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
);
-- --------------------------------------------------------
@@ -138,12 +138,11 @@ CREATE TABLE IF NOT EXISTS `remotefollows` (
--
CREATE TABLE IF NOT EXISTS `remoteuserkeys` (
`id` varchar(255) NOT NULL,
`id` varchar(255) PRIMARY KEY NOT NULL,
`remote_user_id` int(11) NOT NULL,
`public_key` blob NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `follower_id` (`remote_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CONSTRAINT `follower_id` UNIQUE (`remote_user_id`)
);
-- --------------------------------------------------------
@@ -152,14 +151,13 @@ CREATE TABLE IF NOT EXISTS `remoteuserkeys` (
--
CREATE TABLE IF NOT EXISTS `remoteusers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id` integer PRIMARY KEY ASC,
`actor_id` varchar(255) NOT NULL,
`inbox` varchar(255) NOT NULL,
`shared_inbox` varchar(255) NOT NULL,
`followers` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `collection_id` (`actor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CONSTRAINT `collection_id` UNIQUE (`actor_id`)
);
-- --------------------------------------------------------
@@ -168,11 +166,11 @@ CREATE TABLE IF NOT EXISTS `remoteusers` (
--
CREATE TABLE IF NOT EXISTS `userattributes` (
`user_id` int(6) NOT NULL,
`user_id` integer NOT NULL,
`attribute` varchar(64) NOT NULL,
`value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`value` varchar(255) NOT NULL,
PRIMARY KEY (`user_id`,`attribute`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
);
-- --------------------------------------------------------
@@ -181,11 +179,10 @@ CREATE TABLE IF NOT EXISTS `userattributes` (
--
CREATE TABLE IF NOT EXISTS `users` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`password` char(60) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`id` integer PRIMARY KEY ASC,
`username` varchar(100) NOT NULL,
`password` char(60) NOT NULL,
`email` varbinary(255) DEFAULT NULL,
`created` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CONSTRAINT `username` UNIQUE (`username`)
);