diff --git a/migrations/drivers.go b/migrations/drivers.go index 1813615..0770f8c 100644 --- a/migrations/drivers.go +++ b/migrations/drivers.go @@ -1,214 +1,214 @@ /* * Copyright © 2019 Musing Studio LLC. * * This file is part of WriteFreely. * * WriteFreely is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, included * in the LICENSE file in this source code package. */ package migrations import ( "fmt" ) const INVALID_DRIVER_MSG = "Invalid database driver, check configuration file" // TODO: use now() from writefreely pkg func (db *datastore) now() string { switch db.driverName { case driverSQLite: return "strftime('%Y-%m-%d %H:%M:%S','now')" case driverMySQL, driverPostgres: return "NOW()" } panic(INVALID_DRIVER_MSG) } func (db *datastore) typeInt() string { switch db.driverName { case driverSQLite, driverMySQL, driverPostgres: return "INTEGER" } panic(INVALID_DRIVER_MSG) } func (db *datastore) typeSmallInt() string { switch db.driverName { case driverSQLite: return "INTEGER" case driverMySQL, driverPostgres: return "SMALLINT" } panic(INVALID_DRIVER_MSG) } func (db *datastore) typeTinyInt() string { switch db.driverName { case driverSQLite: return "INTEGER" case driverMySQL: return "TINYINT" case driverPostgres: return "SMALLINT" } panic(INVALID_DRIVER_MSG) } func (db *datastore) typeText() string { return "TEXT" } func (db *datastore) typeChar(l int) string { switch db.driverName { case driverSQLite, driverPostgres: return "TEXT" case driverMySQL: return fmt.Sprintf("CHAR(%d)", l) } panic(INVALID_DRIVER_MSG) } func (db *datastore) typeVarChar(l int) string { switch db.driverName { case driverSQLite, driverPostgres: return "TEXT" case driverMySQL: return fmt.Sprintf("VARCHAR(%d)", l) } panic(INVALID_DRIVER_MSG) } func (db *datastore) typeVarBinary(l int) string { switch db.driverName { case driverSQLite: return "BLOB" case driverMySQL: return fmt.Sprintf("VARBINARY(%d)", l) case driverPostgres: return "BYTEA" } panic(INVALID_DRIVER_MSG) } func (db *datastore) typeBool() string { switch db.driverName { case driverSQLite: return "INTEGER" case driverMySQL: return "TINYINT(1)" case driverPostgres: return "BOOLEAN" } panic(INVALID_DRIVER_MSG) } func (db *datastore) typeDateTime() string { switch db.driverName { case driverSQLite: return "DATETIME" case driverMySQL: return "DATETIME" case driverPostgres: - return "TIMESTAMP" + return "TIMESTAMPTZ" } panic(INVALID_DRIVER_MSG) } func (db *datastore) typeIntPrimaryKey() string { switch db.driverName { case driverSQLite: // From docs: "In SQLite, a column with type INTEGER PRIMARY KEY is an alias for the ROWID (except in WITHOUT // ROWID tables) which is always a 64-bit signed integer." return "INTEGER PRIMARY KEY" case driverMySQL: return "INT AUTO_INCREMENT PRIMARY KEY" case driverPostgres: return "INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY" } panic(INVALID_DRIVER_MSG) } func (db *datastore) collateMultiByte() string { if db.driverName == driverMySQL { return " COLLATE utf8_bin" } return "" } func (db *datastore) engine() string { if db.driverName == driverMySQL { return " ENGINE = InnoDB" } return "" } func (db *datastore) after(colName string) string { if db.driverName == driverMySQL { return fmt.Sprintf(" AFTER %s", colName) } return "" } func (db *datastore) boolTrue() string { return "TRUE" } func (db *datastore) boolFalse() string { return "FALSE" } func (db *datastore) limit(offset int, size int) string { switch db.driverName { case driverSQLite, driverMySQL: return fmt.Sprintf(" LIMIT %d, %d", offset, size) case driverPostgres: return fmt.Sprintf(" LIMIT %d OFFSET %d", size, offset) } panic(INVALID_DRIVER_MSG) } /** * MySQL/SQLite use '?' as placeholders in prepared statments * while PostgreSQL use '$1', '$2' ... '$n' instead * * this function converts a prepared statement in MySQL/SQLite * format into PostgreSQL format */ func (db *datastore) QueryWrap(q string) string { if db.driverName != driverPostgres { return q } output := "" escape := false ctr := 0 for i := range len(q) { if q[i] == '\'' || q[i] == '`' { escape = !escape } if q[i] == '?' && !escape { ctr += 1 output += fmt.Sprintf("$%d", ctr) } else { output += string(q[i]) } } return output -} \ No newline at end of file +} diff --git a/postgres.sql b/postgres.sql index 0f955e2..6c37bc3 100644 --- a/postgres.sql +++ b/postgres.sql @@ -1,155 +1,155 @@ CREATE TABLE IF NOT EXISTS "users" ( "id" SERIAL PRIMARY KEY, "username" TEXT NOT NULL, "password" TEXT NOT NULL, "email" BYTEA DEFAULT NULL, - "created" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + "created" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS "collections" ( "id" SERIAL PRIMARY KEY, "alias" TEXT DEFAULT NULL, "title" TEXT NOT NULL, "description" TEXT NOT NULL, "style_sheet" TEXT, "script" TEXT DEFAULT NULL, "format" TEXT DEFAULT NULL, "privacy" INT NOT NULL, "owner_id" INT NOT NULL REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, "view_count" INT NOT NULL ); CREATE TABLE IF NOT EXISTS "accesstokens" ( "token" BYTEA PRIMARY KEY, "user_id" INT NOT NULL REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, "sudo" BOOLEAN NOT NULL DEFAULT FALSE, "one_time" BOOLEAN NOT NULL DEFAULT FALSE, - "created" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - "expires" TIMESTAMP DEFAULT NULL, + "created" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + "expires" TIMESTAMPTZ DEFAULT NULL, "user_agent" TEXT DEFAULT NULL ); CREATE TABLE IF NOT EXISTS "appcontent" ( "id" TEXT PRIMARY KEY, "content" TEXT NOT NULL, - "updated" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + "updated" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE "appmigrations" ( "version" INT NOT NULL, - "migrated" TIMESTAMP NOT NULL, + "migrated" TIMESTAMPTZ NOT NULL, "result" TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS "collectionattributes" ( "collection_id" INT NOT NULL REFERENCES collections(id) ON UPDATE CASCADE ON DELETE CASCADE, "attribute" TEXT NOT NULL, "value" TEXT NOT NULL, PRIMARY KEY ("collection_id", "attribute") ); CREATE TABLE IF NOT EXISTS "collectionkeys" ( "collection_id" INT PRIMARY KEY REFERENCES collections(id) ON UPDATE CASCADE ON DELETE CASCADE, "public_key" BYTEA NOT NULL, "private_key" BYTEA NOT NULL ); CREATE TABLE IF NOT EXISTS "collectionpasswords" ( "collection_id" INT PRIMARY KEY REFERENCES collections(id) ON UPDATE CASCADE ON DELETE CASCADE, "password" TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS "collectionredirects" ( "prev_alias" TEXT NOT NULL PRIMARY KEY, "new_alias" TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS "posts" ( "id" TEXT PRIMARY KEY, "slug" TEXT DEFAULT NULL, "modify_token" TEXT DEFAULT NULL, "text_appearance" TEXT NOT NULL DEFAULT 'norm', "language" TEXT DEFAULT NULL, "rtl" BOOLEAN DEFAULT NULL, "privacy" INT NOT NULL, "owner_id" INT DEFAULT NULL REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, "collection_id" INT DEFAULT NULL REFERENCES collections(id) ON UPDATE CASCADE ON DELETE CASCADE, "pinned_position" SMALLINT NULL, - "created" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "created" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, "view_count" INT NOT NULL, "title" TEXT NOT NULL, "content" TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS "remoteusers" ( "id" SERIAL PRIMARY KEY, "actor_id" TEXT NOT NULL, "inbox" TEXT NOT NULL, "shared_inbox" TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS "remotefollows" ( "collection_id" INT NOT NULL REFERENCES collections(id) ON UPDATE CASCADE ON DELETE CASCADE, "remote_user_id" INT NOT NULL REFERENCES remoteusers(id) ON UPDATE CASCADE ON DELETE CASCADE, - "created" TIMESTAMP NOT NULL, + "created" TIMESTAMPTZ NOT NULL, PRIMARY KEY ("collection_id", "remote_user_id") ); CREATE TABLE IF NOT EXISTS "remoteuserkeys" ( "id" TEXT PRIMARY KEY, "remote_user_id" INT NOT NULL REFERENCES remoteusers(id) ON UPDATE CASCADE ON DELETE CASCADE, "public_key" BYTEA NOT NULL ); CREATE TABLE IF NOT EXISTS "userattributes" ( "user_id" INT NOT NULL REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, "attribute" TEXT NOT NULL, "value" TEXT NOT NULL, PRIMARY KEY ("user_id", "attribute") ); CREATE TABLE IF NOT EXISTS "userinvites" ( "id" TEXT NOT NULL, "owner_id" INT NOT NULL REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, "max_uses" SMALLINT DEFAULT NULL, - "created" TIMESTAMP NOT NULL, - "expires" TIMESTAMP DEFAULT NULL, + "created" TIMESTAMPTZ NOT NULL, + "expires" TIMESTAMPTZ DEFAULT NULL, "inactive" BOOLEAN NOT NULL, PRIMARY KEY (id) ); CREATE TABLE IF NOT EXISTS "usersinvited" ( "invite_id" TEXT NOT NULL REFERENCES userinvites(id) ON UPDATE CASCADE ON DELETE CASCADE, "user_id" INT NOT NULL REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE, PRIMARY KEY (invite_id, user_id) ); CREATE INDEX IF NOT EXISTS "alias" ON "collections" ("alias"); CREATE UNIQUE INDEX IF NOT EXISTS "id_slug" ON "posts" ("collection_id", "slug"); CREATE UNIQUE INDEX IF NOT EXISTS "owner_id" ON "posts" ("owner_id", "id"); CREATE INDEX IF NOT EXISTS "privacy_id" ON "posts" ("privacy", "id"); CREATE UNIQUE INDEX IF NOT EXISTS "remote_user_id" ON "remoteuserkeys" ("remote_user_id"); CREATE UNIQUE INDEX IF NOT EXISTS "actor_id" ON "remoteusers" ("actor_id"); CREATE UNIQUE INDEX IF NOT EXISTS "username" ON "users" ("username");