SQLite support added.

This commit is contained in:
Ben Overmyer 2018-12-01 12:07:25 -06:00
parent dd5f6870d8
commit 6cb86214d7
5 changed files with 245 additions and 13 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@
build
config.ini
writefreely.db

16
app.go
View File

@ -256,9 +256,15 @@ func Serve() {
connectToDatabase(app)
defer shutdown(app)
schema, err := ioutil.ReadFile("schema.sql")
schemaFileName := "schema.sql"
if cfg.Database.Type == "sqlite3" {
schemaFileName = "sqlite.sql"
}
schema, err := ioutil.ReadFile(schemaFileName)
if err != nil {
log.Error("Unable to load schema.sql: %v", err)
log.Error("Unable to load schema file: %v", err)
os.Exit(1)
}
@ -438,15 +444,15 @@ func connectToDatabase(app *app) {
log.Error("%s", err)
os.Exit(1)
}
app.db = &datastore{db}
app.db = &datastore{db, "mysql"}
app.db.SetMaxOpenConns(50)
} else if app.cfg.Database.Type == "sqlite3" {
db, err := sql.Open("sqlite3", "./writefreely.db")
db, err := sql.Open("sqlite3", "./writefreely.db?parseTime=true")
if err != nil {
log.Error("%s", err)
os.Exit(1)
}
app.db = &datastore{db}
app.db = &datastore{db, "sqlite3"}
app.db.SetMaxOpenConns(50)
} else {
log.Error("Invalid database type '%s'. Only 'mysql' and 'sqlite3' are supported right now.", app.cfg.Database.Type)

View File

@ -100,6 +100,7 @@ type writestore interface {
type datastore struct {
*sql.DB
driverName string
}
func (db *datastore) CreateUser(u *User, collectionTitle string) error {
@ -115,7 +116,7 @@ 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) VALUES (?, ?, ?)", u.Username, u.HashedPass, u.Email)
if err != nil {
t.Rollback()
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
@ -478,7 +479,7 @@ func (db *datastore) GetTemporaryOneTimeAccessToken(userID int64, validSecs int,
expirationVal = fmt.Sprintf("DATE_ADD(NOW(), INTERVAL %d SECOND)", validSecs)
}
_, 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, one_time, expires) VALUES (?, ?, ?, "+expirationVal+")", string(binTok), userID, oneTime)
if err != nil {
log.Error("Couldn't INSERT accesstoken: %v", err)
return "", err
@ -571,7 +572,12 @@ func (db *datastore) CreatePost(userID, collID int64, post *SubmittedPost) (*Pos
}
}
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?)")
timeFunction := "NOW()"
if db.driverName == "sqlite3" {
timeFunction = "strftime('%Y-%m-%d %H-%M-%S','now')"
}
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + timeFunction + ", ?)")
if err != nil {
return nil, err
}
@ -668,7 +674,13 @@ func (db *datastore) UpdateOwnedPost(post *AuthenticatedPost, userID int64) erro
return ErrPostNoUpdatableVals
}
queryUpdates += sep + "updated = NOW()"
timeFunction := "NOW()"
if db.driverName == "sqlite3" {
timeFunction = "strftime('%Y-%m-%d %H-%M-%S','now')"
}
queryUpdates += sep + "updated = " + timeFunction
res, err := db.Exec("UPDATE posts SET "+queryUpdates+" WHERE id = ? AND "+authCondition, params...)
if err != nil {
@ -984,6 +996,10 @@ func (db *datastore) GetPostsCount(c *CollectionObj, includeFuture bool) {
timeCondition := ""
if !includeFuture {
timeCondition = "AND created <= NOW()"
if db.driverName == "sqlite3" {
timeCondition = "AND created <= strftime('%Y-%m-%d %H-%M-%S','now')"
}
}
err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE collection_id = ? AND pinned_position IS NULL "+timeCondition, c.ID).Scan(&count)
switch {
@ -1023,6 +1039,10 @@ func (db *datastore) GetPosts(c *Collection, page int, includeFuture, forceRecen
timeCondition := ""
if !includeFuture {
timeCondition = "AND created <= NOW()"
if db.driverName == "sqlite3" {
timeCondition = "AND created <= strftime('%Y-%m-%d %H-%M-%S','now')"
}
}
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 {
@ -1080,6 +1100,10 @@ func (db *datastore) GetPostsTagged(c *Collection, tag string, page int, include
timeCondition := ""
if !includeFuture {
timeCondition = "AND created <= NOW()"
if db.driverName == "sqlite3" {
timeCondition = "AND created <= strftime('%Y-%m-%d %H-%M-%S','now')"
}
}
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 {
@ -1455,7 +1479,11 @@ 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)
clipFunction := "LEFT"
if db.driverName == "sqlite3" {
clipFunction = "SUBSTR"
}
rows, err := db.Query("SELECT id, slug, title, "+clipFunction+"(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."}
@ -2141,7 +2169,13 @@ func (db *datastore) GetDynamicContent(id string) (string, *time.Time, error) {
}
func (db *datastore) UpdateDynamicContent(id, content string) error {
_, err := db.Exec("INSERT INTO appcontent (id, content, updated) VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE content = ?, updated = NOW()", id, content, content)
timeFunction := "NOW()"
if db.driverName == "sqlite3" {
timeFunction = "strftime('%Y-%m-%d %H-%M-%S','now')"
}
_, err := db.Exec("INSERT INTO appcontent (id, content, updated) VALUES (?, ?, "+timeFunction+") ON DUPLICATE KEY UPDATE content = ?, updated = "+timeFunction, id, content, content)
if err != nil {
log.Error("Unable to INSERT appcontent for '%s': %v", id, err)
}

View File

@ -13,7 +13,7 @@ CREATE TABLE IF NOT EXISTS `accesstokens` (
`user_id` int(6) NOT NULL,
`sudo` tinyint(1) NOT NULL DEFAULT '0',
`one_time` tinyint(1) NOT NULL DEFAULT '0',
`created` datetime NOT NULL,
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`expires` datetime DEFAULT NULL,
`user_agent` varchar(255) NOT NULL,
PRIMARY KEY (`token`)
@ -197,7 +197,7 @@ CREATE TABLE IF NOT EXISTS `users` (
`username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`password` char(60) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`email` varbinary(255) DEFAULT NULL,
`created` datetime NOT NULL,
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

191
sqlite.sql Normal file
View File

@ -0,0 +1,191 @@
--
-- Database: writefreely
--
-- --------------------------------------------------------
--
-- Table structure for table accesstokens
--
CREATE TABLE IF NOT EXISTS accesstokens (
token TEXT NOT NULL PRIMARY KEY,
user_id INTEGER NOT NULL,
sudo INTEGER NOT NULL DEFAULT '0',
one_time INTEGER NOT NULL DEFAULT '0',
created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
expires DATETIME DEFAULT NULL,
user_agent TEXT NOT NULL
);
-- --------------------------------------------------------
--
-- Table structure for table appcontent
--
CREATE TABLE IF NOT EXISTS appcontent (
id TEXT NOT NULL PRIMARY KEY,
content TEXT NOT NULL,
updated DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- --------------------------------------------------------
--
-- Table structure for table collectionattributes
--
CREATE TABLE IF NOT EXISTS collectionattributes (
collection_id INTEGER NOT NULL,
attribute TEXT NOT NULL,
value TEXT NOT NULL,
PRIMARY KEY (collection_id, attribute)
);
-- --------------------------------------------------------
--
-- Table structure for table collectionkeys
--
CREATE TABLE IF NOT EXISTS collectionkeys (
collection_id INTEGER PRIMARY KEY,
public_key blob NOT NULL,
private_key blob NOT NULL
);
-- --------------------------------------------------------
--
-- Table structure for table collectionpasswords
--
CREATE TABLE IF NOT EXISTS collectionpasswords (
collection_id INTEGER PRIMARY KEY,
password TEXT NOT NULL
);
-- --------------------------------------------------------
--
-- Table structure for table collectionredirects
--
CREATE TABLE IF NOT EXISTS collectionredirects (
prev_alias TEXT NOT NULL PRIMARY KEY,
new_alias TEXT NOT NULL
);
-- --------------------------------------------------------
--
-- Table structure for table collections
--
CREATE TABLE IF NOT EXISTS collections (
id INTEGER PRIMARY KEY AUTOINCREMENT,
alias TEXT DEFAULT NULL UNIQUE,
title TEXT NOT NULL,
description TEXT NOT NULL,
style_sheet TEXT,
script TEXT,
format TEXT DEFAULT NULL,
privacy INTEGER NOT NULL,
owner_id INTEGER NOT NULL,
view_count INTEGER NOT NULL
);
-- --------------------------------------------------------
--
-- Table structure for table posts
--
CREATE TABLE IF NOT EXISTS posts (
id TEXT NOT NULL,
slug TEXT DEFAULT NULL,
modify_token TEXT DEFAULT NULL,
text_appearance TEXT NOT NULL DEFAULT 'norm',
language TEXT DEFAULT NULL,
rtl INTEGER DEFAULT NULL,
privacy INTEGER NOT NULL,
owner_id INTEGER DEFAULT NULL,
collection_id INTEGER DEFAULT NULL,
pinned_position INTEGER UNSIGNED DEFAULT NULL,
created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
view_count INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
CONSTRAINT id_slug UNIQUE (collection_id, slug),
CONSTRAINT owner_id UNIQUE (owner_id, id),
CONSTRAINT privacy_id UNIQUE (privacy, id)
);
-- --------------------------------------------------------
--
-- Table structure for table remotefollows
--
CREATE TABLE IF NOT EXISTS remotefollows (
collection_id INTEGER NOT NULL,
remote_user_id INTEGER NOT NULL,
created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (collection_id,remote_user_id)
);
-- --------------------------------------------------------
--
-- Table structure for table remoteuserkeys
--
CREATE TABLE IF NOT EXISTS remoteuserkeys (
id TEXT NOT NULL,
remote_user_id INTEGER NOT NULL,
public_key blob NOT NULL,
CONSTRAINT follower_id UNIQUE (remote_user_id)
);
-- --------------------------------------------------------
--
-- Table structure for table remoteusers
--
CREATE TABLE IF NOT EXISTS remoteusers (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
actor_id TEXT NOT NULL,
inbox TEXT NOT NULL,
shared_inbox TEXT NOT NULL,
CONSTRAINT collection_id UNIQUE (actor_id)
);
-- --------------------------------------------------------
--
-- Table structure for table userattributes
--
CREATE TABLE IF NOT EXISTS userattributes (
user_id INTEGER NOT NULL,
attribute TEXT NOT NULL,
value TEXT NOT NULL,
PRIMARY KEY (user_id, attribute)
);
-- --------------------------------------------------------
--
-- Table structure for table users
--
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
email TEXT DEFAULT NULL,
created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);