1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-25 08:09:43 +02:00

Merge pull request #178 from documize/sql-store

Pluggable storage provider + PostgreSQL support
This commit is contained in:
Saul S 2018-10-07 14:02:45 +01:00 committed by GitHub
commit 4aa3bba7bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
269 changed files with 19927 additions and 23963 deletions

1
.gitignore vendored
View file

@ -59,7 +59,6 @@ npm-debug.log
debug
*.pem
*.crt
Dockerfile
container.sh
make.sh
jsconfig.json

25
Gopkg.lock generated
View file

@ -66,12 +66,12 @@
version = "v1.0"
[[projects]]
digest = "1:850c49ca338a10fec2cb9e78f793043ed23965489d09e30bcc19fe29719da313"
digest = "1:adea5a94903eb4384abef30f3d878dc9ff6b6b5b0722da25b82e5169216dfb61"
name = "github.com/go-sql-driver/mysql"
packages = ["."]
pruneopts = "UT"
revision = "a0583e0143b1624142adab07e0e97fe106d99561"
version = "v1.3"
revision = "d523deb1b23d913de5bdada721a6071e71283618"
version = "v1.4.0"
[[projects]]
digest = "1:ffc060c551980d37ee9e428ef528ee2813137249ccebb0bfc412ef83071cac91"
@ -115,14 +115,25 @@
[[projects]]
branch = "master"
digest = "1:692a381c4e8423b0c7d1fdf06324677c930a4a53ca4ee205fc0fa64b2fc9d9af"
digest = "1:7654989089e5bd5b6734ec3be8b695e87d3f1f8d95620b343fd7d3995a5b60d7"
name = "github.com/jmoiron/sqlx"
packages = [
".",
"reflectx",
]
pruneopts = "UT"
revision = "05cef0741ade10ca668982355b3f3f0bcf0ff0a8"
revision = "0dae4fefe7c0e190f7b5a78dac28a1c82cc8d849"
[[projects]]
digest = "1:8ef506fc2bb9ced9b151dafa592d4046063d744c646c1bbe801982ce87e4bc24"
name = "github.com/lib/pq"
packages = [
".",
"oid",
]
pruneopts = "UT"
revision = "4ded0e9383f75c197b3a2aaa6d590ac52df6fd79"
version = "v1.0.0"
[[projects]]
branch = "master"
@ -211,9 +222,10 @@
revision = "543e37812f10c46c622c9575afd7ad22f22a12ba"
[[projects]]
digest = "1:9cf45e754ab2dff5f53a553e6948b994ff738e4d1092ca608dcada945d8be0ef"
digest = "1:f40806967647e80fc51b941a586afefea6058592692c0bbfb3be7ea6b2b2a82d"
name = "google.golang.org/appengine"
packages = [
"cloudsql",
"internal",
"internal/base",
"internal/datastore",
@ -273,6 +285,7 @@
"github.com/google/go-github/github",
"github.com/gorilla/mux",
"github.com/jmoiron/sqlx",
"github.com/lib/pq",
"github.com/nu7hatch/gouuid",
"github.com/pkg/errors",
"golang.org/x/crypto/bcrypt",

View file

@ -1,4 +1,4 @@
> We're committed to providing frequent product releases to ensure self-host customers enjoy the same product as our cloud/SaaS customers.
> We provide frequent product releases ensuring self-host customers enjoy the same features as our cloud/SaaS customers.
>
> Harvey Kandola, CEO & Founder, Documize Inc.
@ -58,19 +58,32 @@ Space view.
## Latest version
[Community edition: v1.70.0](https://github.com/documize/community/releases)
[Community edition: v1.71.0](https://github.com/documize/community/releases)
[Enterprise edition: v1.72.0](https://documize.com/downloads)
[Enterprise edition: v1.73.0](https://documize.com/downloads)
## OS support
Documize runs on the following:
Documize can be installed and run on:
- Linux
- Windows
- macOS
# Browser support
Heck, Documize will probably run just fine on a Raspberry Pi 3.
## Database support
Documize supports the following database systems:
- PostgreSQL (v9.6+)
- MySQL (v5.7.10+ and v8.0.0+)
- Percona (v5.7.16-10+)
- MariaDB (10.3.0+)
Coming soon: Microsoft SQL Server 2017 (Linux/Windows).
## Browser support
Documize supports the following (evergreen) browsers:
@ -78,6 +91,8 @@ Documize supports the following (evergreen) browsers:
- Firefox
- Safari
- Brave
- Vivaldi
- Opera
- MS Edge (16+)
## Technology stack
@ -87,14 +102,6 @@ Documize is built with the following technologies:
- EmberJS (v3.1.2)
- Go (v1.10.3)
...and supports the following databases:
- MySQL (v5.7.10+)
- Percona (v5.7.16-10+)
- MariaDB (10.3.0+)
Coming soon, PostgreSQL and Microsoft SQL Server database support.
## Authentication options
Besides email/password login, you can also leverage the following options.

View file

@ -32,9 +32,12 @@ copy core\database\templates\*.html embed\bindata
rd /s /q embed\bindata\scripts
mkdir embed\bindata\scripts
mkdir embed\bindata\scripts\mysql
mkdir embed\bindata\scripts\postgresql
echo "Copying database scripts folder"
robocopy /e /NFL /NDL /NJH core\database\scripts\autobuild embed\bindata\scripts
robocopy /e /NFL /NDL /NJH core\database\scripts\mysql embed\bindata\scripts\mysql
robocopy /e /NFL /NDL /NJH core\database\scripts\postgresql embed\bindata\scripts\postgresql
echo "Generating in-memory static assets..."
go get -u github.com/jteeuwen/go-bindata/...

View file

@ -27,7 +27,10 @@ cp domain/mail/*.html embed/bindata/mail
cp core/database/templates/*.html embed/bindata
rm -rf embed/bindata/scripts
mkdir -p embed/bindata/scripts
cp -r core/database/scripts/autobuild/*.sql embed/bindata/scripts
mkdir -p embed/bindata/scripts/mysql
mkdir -p embed/bindata/scripts/postgresql
cp -r core/database/scripts/mysql/*.sql embed/bindata/scripts/mysql
cp -r core/database/scripts/postgresql/*.sql embed/bindata/scripts/postgresql
echo "Generating in-memory static assets..."
# go get -u github.com/jteeuwen/go-bindata/...

View file

@ -22,7 +22,7 @@ import (
"github.com/documize/community/core/api/convert/html"
"github.com/documize/community/core/api/convert/md"
api "github.com/documize/community/core/convapi"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/glick"
)
@ -49,7 +49,7 @@ var Lib *glick.Library
// Setup configures the global library at Lib,
// largely based on the "config.json" file. It should be called only once.
func Setup(s *domain.Store) error {
func Setup(s *store.Store) error {
if insecure == "true" {
glick.InsecureSkipVerifyTLS = true
}

View file

@ -12,191 +12,85 @@
package database
import (
"errors"
"fmt"
"strconv"
"strings"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/server/web"
)
var dbCheckOK bool // default false
// Check that the database is configured correctly and that all the required tables exist.
// It must be the first function called in this package.
func Check(runtime *env.Runtime) bool {
runtime.Log.Info("Database checks: started")
runtime.Log.Info("Database: checking state")
csBits := strings.Split(runtime.Flags.DBConn, "/")
if len(csBits) > 1 {
web.SiteInfo.DBname = strings.Split(csBits[len(csBits)-1], "?")[0]
}
web.SiteInfo.DBname = runtime.StoreProvider.DatabaseName()
rows, err := runtime.Db.Query("SELECT VERSION() AS version, @@version_comment as comment, @@character_set_database AS charset, @@collation_database AS collation")
rows, err := runtime.Db.Query(runtime.StoreProvider.QueryMeta())
if err != nil {
runtime.Log.Error("Can't get MySQL configuration", err)
web.SiteInfo.Issue = "Can't get MySQL configuration: " + err.Error()
runtime.Log.Error("Database: unable to load meta information from database provider", err)
web.SiteInfo.Issue = "Unable to load meta information from database provider: " + err.Error()
runtime.Flags.SiteMode = env.SiteModeBadDB
return false
}
defer streamutil.Close(rows)
var version, dbComment, charset, collation string
if rows.Next() {
err = rows.Scan(&version, &dbComment, &charset, &collation)
}
if err == nil {
err = rows.Err() // get any error encountered during iteration
}
if err != nil {
runtime.Log.Error("no MySQL configuration returned", err)
web.SiteInfo.Issue = "no MySQL configuration return issue: " + err.Error()
runtime.Log.Error("Database: no meta data returned by database provider", err)
web.SiteInfo.Issue = "No meta data returned by database provider: " + err.Error()
runtime.Flags.SiteMode = env.SiteModeBadDB
return false
}
// Get SQL variant as this affects minimum version checking logic.
// MySQL and Percona share same version scheme (e..g 5.7.10).
// MariaDB starts at 10.2.x
runtime.DbVariant = GetSQLVariant(runtime.Flags.DBType, dbComment)
runtime.Log.Info(fmt.Sprintf("Database checks: SQL variant %v", runtime.DbVariant))
runtime.Log.Info("Database checks: SQL version " + version)
runtime.Log.Info(fmt.Sprintf("Database: provider name %v", runtime.StoreProvider.Type()))
runtime.Log.Info(fmt.Sprintf("Database: provider version %s", version))
verNums, err := GetSQLVersion(version)
if err != nil {
runtime.Log.Error("Database version check failed", err)
// Version OK?
versionOK, minVersion := runtime.StoreProvider.VerfiyVersion(version)
if !versionOK {
msg := fmt.Sprintf("*** ERROR: database version needs to be %s or above ***", minVersion)
runtime.Log.Info(msg)
web.SiteInfo.Issue = msg
runtime.Flags.SiteMode = env.SiteModeBadDB
return false
}
// Check minimum MySQL version as we need JSON column type.
verInts := []int{5, 7, 10} // Minimum MySQL version
if runtime.DbVariant == env.DBVariantMariaDB {
verInts = []int{10, 3, 0} // Minimum MariaDB version
// Character set and collation OK?
charOK, charRequired := runtime.StoreProvider.VerfiyCharacterCollation(charset, collation)
if !charOK {
msg := fmt.Sprintf("*** ERROR: %s ***", charRequired)
runtime.Log.Info(msg)
web.SiteInfo.Issue = msg
runtime.Flags.SiteMode = env.SiteModeBadDB
return false
}
for k, v := range verInts {
// If major release is higher then skip minor/patch checks (e.g. 8.x.x > 5.x.x)
if k == 0 && len(verNums) > 0 && verNums[0] > verInts[0] {
break
}
if verNums[k] < v {
want := fmt.Sprintf("%d.%d.%d", verInts[0], verInts[1], verInts[2])
runtime.Log.Error("MySQL version element "+strconv.Itoa(k+1)+" of '"+version+"' not high enough, need at least version "+want, errors.New("bad MySQL version"))
web.SiteInfo.Issue = "MySQL version element " + strconv.Itoa(k+1) + " of '" + version + "' not high enough, need at least version " + want
runtime.Flags.SiteMode = env.SiteModeBadDB
return false
}
// if there are no rows in the database, enter set-up mode
var flds []string
if err := runtime.Db.Select(&flds, runtime.StoreProvider.QueryTableList()); err != nil {
msg := fmt.Sprintf("Database: unable to get database table list ")
runtime.Log.Error(msg, err)
web.SiteInfo.Issue = msg + err.Error()
runtime.Flags.SiteMode = env.SiteModeBadDB
return false
}
{ // check the MySQL character set and collation
if charset != "utf8" && charset != "utf8mb4" {
runtime.Log.Error("MySQL character set not utf8/utf8mb4:", errors.New(charset))
web.SiteInfo.Issue = "MySQL character set not utf8/utf8mb4: " + charset
runtime.Flags.SiteMode = env.SiteModeBadDB
return false
}
if !strings.HasPrefix(collation, "utf8") {
runtime.Log.Error("MySQL collation sequence not utf8...:", errors.New(collation))
web.SiteInfo.Issue = "MySQL collation sequence not utf8...: " + collation
runtime.Flags.SiteMode = env.SiteModeBadDB
return false
}
if len(flds) == 0 {
runtime.Log.Info("Database: starting setup mode for empty database")
runtime.Flags.SiteMode = env.SiteModeSetup
return false
}
{ // if there are no rows in the database, enter set-up mode
var flds []string
if err := runtime.Db.Select(&flds,
`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '`+web.SiteInfo.DBname+
`' and TABLE_TYPE='BASE TABLE'`); err != nil {
runtime.Log.Error("Can't get MySQL number of tables", err)
web.SiteInfo.Issue = "Can't get MySQL number of tables: " + err.Error()
runtime.Flags.SiteMode = env.SiteModeBadDB
return false
}
if strings.TrimSpace(flds[0]) == "0" {
runtime.Log.Info("Entering database set-up mode because the database is empty.....")
runtime.Flags.SiteMode = env.SiteModeSetup
return false
}
}
// We have good database, so proceed with app boot process.
runtime.Flags.SiteMode = env.SiteModeNormal
web.SiteInfo.DBname = ""
{ // check all the required tables exist
var tables = []string{`account`,
`attachment`, `document`,
`label`, `organization`,
`page`, `revision`, `search`, `user`}
for _, table := range tables {
var dummy []string
if err := runtime.Db.Select(&dummy, "SELECT 1 FROM "+table+" LIMIT 1;"); err != nil {
runtime.Log.Error("Entering bad database mode because: SELECT 1 FROM "+table+" LIMIT 1;", err)
web.SiteInfo.Issue = "MySQL database is not empty, but does not contain table: " + table
runtime.Flags.SiteMode = env.SiteModeBadDB
return false
}
}
}
runtime.Flags.SiteMode = env.SiteModeNormal // actually no need to do this (as already ""), this for documentation
web.SiteInfo.DBname = "" // do not give this info when not in set-up mode
dbCheckOK = true
return true
}
// GetSQLVariant uses database value form @@version_comment to deduce MySQL variant.
func GetSQLVariant(dbType, vc string) env.DbVariant {
vc = strings.ToLower(vc)
dbType = strings.ToLower(dbType)
// determine type from database
if strings.Contains(vc, "mariadb") {
return env.DBVariantMariaDB
} else if strings.Contains(vc, "percona") {
return env.DBVariantPercona
} else if strings.Contains(vc, "mysql") {
return env.DbVariantMySQL
}
// now determine type from command line switch
if strings.Contains(dbType, "mariadb") {
return env.DBVariantMariaDB
} else if strings.Contains(dbType, "percona") {
return env.DBVariantPercona
} else if strings.Contains(dbType, "mysql") {
return env.DbVariantMySQL
}
// horrid default could cause app to crash
return env.DbVariantMySQL
}
// GetSQLVersion returns SQL version as major,minor,patch numerics.
func GetSQLVersion(v string) (ints []int, err error) {
ints = []int{0, 0, 0}
pos := strings.Index(v, "-")
if pos > 1 {
v = v[:pos]
}
vs := strings.Split(v, ".")
if len(vs) < 3 {
err = errors.New("MySQL version not of the form a.b.c")
return
}
for key, val := range vs {
num, err := strconv.Atoi(val)
if err != nil {
return ints, err
}
ints[key] = num
}
return
}

View file

@ -1,42 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package database
import "testing"
// go test github.com/documize/community/core/database -run TestGetVersion
func TestGetVersion(t *testing.T) {
ts2(t, "5.7.10", []int{5, 7, 10})
ts2(t, "5.7.10-log", []int{5, 7, 10})
ts2(t, "5.7.10-demo", []int{5, 7, 10})
ts2(t, "5.7.10-debug", []int{5, 7, 10})
ts2(t, "5.7.16-10", []int{5, 7, 16})
ts2(t, "5.7.12-0ubuntu0-12.12.3", []int{5, 7, 12})
ts2(t, "10.1.20-MariaDB-1~jessie", []int{10, 1, 20})
ts2(t, "ubuntu0-12.12.3", []int{0, 0, 0})
ts2(t, "junk-string", []int{0, 0, 0})
ts2(t, "somethingstring", []int{0, 0, 0})
}
func ts2(t *testing.T, in string, out []int) {
got, _ := GetSQLVersion(in)
// if err != nil {
// t.Errorf("Unable to GetSQLVersion %s", err)
// }
for k, v := range got {
if v != out[k] {
t.Errorf("version input of %s got %d for position %d but expected %d\n", in, v, k, out[k])
}
}
}

74
core/database/db_test.go Normal file
View file

@ -0,0 +1,74 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package database
import (
"github.com/documize/community/core/env"
"testing"
)
// go test github.com/documize/community/core/database -run TestGetVersion
// func TestGetVersion(t *testing.T) {
// ts2(t, "5.7.10", []int{5, 7, 10})
// ts2(t, "5.7.10-log", []int{5, 7, 10})
// ts2(t, "5.7.10-demo", []int{5, 7, 10})
// ts2(t, "5.7.10-debug", []int{5, 7, 10})
// ts2(t, "5.7.16-10", []int{5, 7, 16})
// ts2(t, "5.7.12-0ubuntu0-12.12.3", []int{5, 7, 12})
// ts2(t, "10.1.20-MariaDB-1~jessie", []int{10, 1, 20})
// ts2(t, "ubuntu0-12.12.3", []int{0, 0, 0})
// ts2(t, "junk-string", []int{0, 0, 0})
// ts2(t, "somethingstring", []int{0, 0, 0})
// }
// func ts2(t *testing.T, in string, out []int) {
// got, _ := GetSQLVersion(in)
// // if err != nil {
// // t.Errorf("Unable to GetSQLVersion %s", err)
// // }
// for k, v := range got {
// if v != out[k] {
// t.Errorf("version input of %s got %d for position %d but expected %d\n", in, v, k, out[k])
// }
// }
// }
func TestDatabaseVersionLegacy(t *testing.T) {
i := extractVersionNumber("db_00021.sql")
if i != 21 {
t.Errorf("expected 21 got %d", i)
}
i = extractVersionNumber("db_000.sql")
if i != 0 {
t.Errorf("expected 0 got %d", i)
}
i = extractVersionNumber("26")
if i != 26 {
t.Errorf("expected 26 got %d", i)
}
}
func TestParamRebind(t *testing.T) {
q1in := "INSERT INTO dmz_org (c_refid, c_company, c_title) VALUES (?, ?, ?)"
q1out := "INSERT INTO dmz_org (c_refid, c_company, c_title) VALUES ($1, $2, $3)"
test1 := RebindParams(q1in, env.StoreTypePostgreSQL)
if test1 != q1out {
t.Errorf("expected %s got %s", q1in, test1)
}
t.Log(test1)
}

257
core/database/installer.go Normal file
View file

@ -0,0 +1,257 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package database
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/documize/community/core/env"
"github.com/jmoiron/sqlx"
)
// InstallUpgrade creates new database or upgrades existing database.
func InstallUpgrade(runtime *env.Runtime, existingDB bool) (err error) {
// amLeader := false
// Get all SQL scripts.
scripts, err := LoadScripts()
if err != nil {
runtime.Log.Error("Database: unable to load scripts", err)
return
}
// Filter out database specific scripts.
dbTypeScripts := SpecificScripts(runtime, scripts)
if len(dbTypeScripts) == 0 {
runtime.Log.Info(fmt.Sprintf("Database: unable to load scripts for database type %s", runtime.StoreProvider.Type()))
return
}
runtime.Log.Info(fmt.Sprintf("Database: loaded %d SQL scripts for provider %s", len(dbTypeScripts), runtime.StoreProvider.Type()))
// Get current database version.
currentVersion := 0
if existingDB {
currentVersion, err = CurrentVersion(runtime)
if err != nil {
runtime.Log.Error("Database: unable to get current version", err)
return
}
runtime.Log.Info(fmt.Sprintf("Database: current version number is %d", currentVersion))
}
// Make a list of scripts to execute based upon current database state.
toProcess := []Script{}
for _, s := range dbTypeScripts {
if s.Version > currentVersion || currentVersion == 0 {
toProcess = append(toProcess, s)
}
}
runtime.Log.Info(fmt.Sprintf("Database: %d scripts to process", len(toProcess)))
// For MySQL type there was major new schema introduced in v24.
// We check for this release and bypass usual locking code
// because tables have changed.
legacyMigration := runtime.StoreProvider.Type() == env.StoreTypeMySQL &&
currentVersion > 0 && currentVersion < 25 && len(toProcess) >= 26 && toProcess[len(toProcess)-1].Version == 25
if legacyMigration {
// Bypass all DB locking/checking processes as these look for new schema
// which we are about to install.
toProcess = toProcess[len(toProcess)-1:]
runtime.Log.Info(fmt.Sprintf("Database: legacy schema has %d scripts to process", len(toProcess)))
}
tx, err := runtime.Db.Beginx()
if err != nil {
return err
}
err = runScripts(runtime, tx, toProcess)
if err != nil {
runtime.Log.Error("Database: error processing SQL scripts", err)
tx.Rollback()
}
tx.Commit()
return nil
// New style schema
// if existingDB {
// amLeader, err = Lock(runtime, len(toProcess))
// if err != nil {
// runtime.Log.Error("Database: failed to lock existing database for processing", err)
// }
// } else {
// // New installation hopes that you are only spinning up one instance of Documize.
// // Assumption: nobody will perform the intial setup in a clustered environment.
// amLeader = true
// }
// tx, err := runtime.Db.Beginx()
// if err != nil {
// return Unlock(runtime, tx, err, amLeader)
// }
// // If currently running process is database leader then we perform upgrade.
// if amLeader {
// runtime.Log.Info(fmt.Sprintf("Database: %d SQL scripts to process", len(toProcess)))
// err = runScripts(runtime, tx, toProcess)
// if err != nil {
// runtime.Log.Error("Database: error processing SQL script", err)
// }
// return Unlock(runtime, tx, err, amLeader)
// }
// // If currently running process is a slave instance then we wait for migration to complete.
// targetVersion := toProcess[len(toProcess)-1].Version
// for targetVersion != currentVersion {
// time.Sleep(time.Second)
// runtime.Log.Info("Database: slave instance polling for upgrade process completion")
// tx.Rollback()
// // Get database version and check again.
// currentVersion, err = CurrentVersion(runtime)
// if err != nil {
// return Unlock(runtime, tx, err, amLeader)
// }
// }
// return Unlock(runtime, tx, nil, amLeader)
}
// Run SQL scripts to instal or upgrade this database.
func runScripts(runtime *env.Runtime, tx *sqlx.Tx, scripts []Script) (err error) {
// We can have multiple scripts as each Documize database change has it's own SQL script.
for _, script := range scripts {
runtime.Log.Info(fmt.Sprintf("Database: processing SQL script %d", script.Version))
err = executeSQL(tx, runtime.StoreProvider.Type(), runtime.StoreProvider.TypeVariant(), script.Script)
if err != nil {
runtime.Log.Error(fmt.Sprintf("error executing SQL script %d", script.Version), err)
return err
}
// Record the fact we have processed this database script version.
_, err = tx.Exec(runtime.StoreProvider.QueryRecordVersionUpgrade(script.Version))
if err != nil {
// For MySQL we try the legacy DB schema.
if runtime.StoreProvider.Type() == env.StoreTypeMySQL {
runtime.Log.Info(fmt.Sprintf("Database: attempting legacy fallback for SQL script %d", script.Version))
_, err = tx.Exec(runtime.StoreProvider.QueryRecordVersionUpgradeLegacy(script.Version))
if err != nil {
runtime.Log.Error(fmt.Sprintf("error recording execution of SQL script %d", script.Version), err)
return err
}
} else {
// Unknown issue running script on non-MySQL database.
runtime.Log.Error(fmt.Sprintf("error executing SQL script %d", script.Version), err)
return err
}
}
}
return nil
}
// executeSQL runs specified SQL commands.
func executeSQL(tx *sqlx.Tx, st env.StoreType, variant env.StoreType, SQLfile []byte) error {
// Turn SQL file contents into runnable SQL statements.
stmts := getStatements(SQLfile)
for _, stmt := range stmts {
// MariaDB has no specific JSON column type (but has JSON queries)
if st == env.StoreTypeMySQL && variant == env.StoreTypeMariaDB {
stmt = strings.Replace(stmt, "` JSON", "` TEXT", -1)
}
_, err := tx.Exec(stmt)
if err != nil {
fmt.Println("sql statement error:", stmt)
return err
}
}
return nil
}
// getStatement strips out the comments and returns all the individual SQL commands (apart from "USE") as a []string.
func getStatements(bytes []byte) (stmts []string) {
// Strip comments of the form '-- comment' or like this one /**/
stripped := regexp.MustCompile("(?s)--.*?\n|/\\*.*?\\*/").ReplaceAll(bytes, []byte("\n"))
// Break into lines using ; terminator.
lines := strings.Split(string(stripped), ";")
// Prepare return data.
stmts = make([]string, 0, len(lines))
for _, v := range lines {
trimmed := strings.TrimSpace(v)
// Process non-empty lines and exclude "USE dbname" command
if len(trimmed) > 0 && !strings.HasPrefix(strings.ToUpper(trimmed), "USE ") {
stmts = append(stmts, trimmed+";")
}
}
return
}
// CurrentVersion returns number that represents the current database version number.
// For example 23 represents the 23rd iteration of the database.
func CurrentVersion(runtime *env.Runtime) (version int, err error) {
currentVersion := "0"
row := runtime.Db.QueryRow(runtime.StoreProvider.QueryGetDatabaseVersion())
err = row.Scan(&currentVersion)
if err != nil {
// For MySQL we try the legacy DB checks.
if runtime.StoreProvider.Type() == env.StoreTypeMySQL {
row := runtime.Db.QueryRow(runtime.StoreProvider.QueryGetDatabaseVersionLegacy())
err = row.Scan(&currentVersion)
}
}
return extractVersionNumber(currentVersion), nil
}
// Turns legacy "db_00021.sql" and new "21" format into version number 21.
func extractVersionNumber(s string) int {
// Good practice in case of human tampering.
s = strings.TrimSpace(s)
s = strings.ToLower(s)
// Remove any quotes from JSON string.
s = strings.Replace(s, "\"", "", -1)
// Remove legacy version string formatting.
// We know just store the number.
s = strings.Replace(s, "db_000", "", 1)
s = strings.Replace(s, ".sql", "", 1)
i, err := strconv.Atoi(s)
if err != nil {
i = 0
}
return i
}

110
core/database/lock.go Normal file
View file

@ -0,0 +1,110 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package database
// import (
// "crypto/rand"
// "time"
// "github.com/documize/community/core/env"
// "github.com/jmoiron/sqlx"
// )
// // Lock will try to lock the database instance to the running process.
// // Uses a "random" delay as a por man's database cluster-aware process.
// // We skip delay if there are no scripts to process.
// func Lock(runtime *env.Runtime, scriptsToProcess int) (bool, error) {
// // Wait for random period of time.
// b := make([]byte, 2)
// _, err := rand.Read(b)
// if err != nil {
// return false, err
// }
// wait := ((time.Duration(b[0]) << 8) | time.Duration(b[1])) * time.Millisecond / 10 // up to 6.5 secs wait
// // Why delay if nothing to process?
// if scriptsToProcess > 0 {
// time.Sleep(wait)
// }
// // Start transaction fotr lock process.
// tx, err := runtime.Db.Beginx()
// if err != nil {
// runtime.Log.Error("Database: unable to start transaction", err)
// return false, err
// }
// // Lock the database.
// _, err = tx.Exec(runtime.StoreProvider.QueryStartLock())
// if err != nil {
// runtime.Log.Error("Database: unable to lock tables", err)
// return false, err
// }
// // Unlock the database at the end of this function.
// defer func() {
// _, err = tx.Exec(runtime.StoreProvider.QueryFinishLock())
// if err != nil {
// runtime.Log.Error("Database: unable to unlock tables", err)
// }
// tx.Commit()
// }()
// // Try to record this process as leader of database migration process.
// _, err = tx.Exec(runtime.StoreProvider.QueryInsertProcessID())
// if err != nil {
// runtime.Log.Info("Database: marked as slave process awaiting upgrade")
// return false, nil
// }
// // We are the leader!
// runtime.Log.Info("Database: marked as database upgrade process leader")
// return true, err
// }
// // Unlock completes process that was started with Lock().
// func Unlock(runtime *env.Runtime, tx *sqlx.Tx, err error, amLeader bool) error {
// if amLeader {
// defer func() {
// doUnlock(runtime)
// }()
// if tx != nil {
// if err == nil {
// tx.Commit()
// runtime.Log.Info("Database: is ready")
// return nil
// }
// tx.Rollback()
// }
// runtime.Log.Error("Database: install/upgrade failed", err)
// return err
// }
// return nil // not the leader, so ignore errors
// }
// // Helper method for defer function called from Unlock().
// func doUnlock(runtime *env.Runtime) error {
// tx, err := runtime.Db.Beginx()
// if err != nil {
// return err
// }
// _, err = tx.Exec(runtime.StoreProvider.QueryDeleteProcessID())
// if err != nil {
// return err
// }
// return tx.Commit()
// }

View file

@ -1,281 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package database
import (
"bytes"
"crypto/rand"
"database/sql"
"fmt"
"os"
"regexp"
"sort"
"strings"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/server/web"
"github.com/jmoiron/sqlx"
)
const migrationsDir = "bindata/scripts"
// migrationsT holds a list of migration sql files to run.
type migrationsT []string
// migrations returns a list of the migrations to update the database as required for this version of the code.
func migrations(lastMigration string) (migrationsT, error) {
lastMigration = strings.TrimPrefix(strings.TrimSuffix(lastMigration, `"`), `"`)
files, err := web.AssetDir(migrationsDir)
if err != nil {
return nil, err
}
sort.Strings(files)
ret := make(migrationsT, 0, len(files))
hadLast := false
if len(lastMigration) == 0 {
hadLast = true
}
for _, v := range files {
if v == lastMigration {
hadLast = true
} else {
if hadLast {
ret = append(ret, v)
}
}
}
//fmt.Println(`DEBUG Migrations("`+lastMigration+`")=`,ret)
return ret, nil
}
// migrate the database as required, by applying the migrations.
func (m migrationsT) migrate(runtime *env.Runtime, tx *sqlx.Tx) error {
for _, v := range m {
runtime.Log.Info("Processing migration file: " + v)
buf, err := web.Asset(migrationsDir + "/" + v)
if err != nil {
return err
}
err = processSQLfile(tx, runtime.DbVariant, buf)
if err != nil {
return err
}
json := `{"database":"` + v + `"}`
sql := "INSERT INTO `config` (`key`,`config`) " +
"VALUES ('META','" + json +
"') ON DUPLICATE KEY UPDATE `config`='" + json + "';"
_, err = tx.Exec(sql) // add a record in the config file to say we have done the upgrade
if err != nil {
return err
}
}
return nil
}
func lockDB(runtime *env.Runtime) (bool, error) {
b := make([]byte, 2)
_, err := rand.Read(b)
if err != nil {
return false, err
}
wait := ((time.Duration(b[0]) << 8) | time.Duration(b[1])) * time.Millisecond / 10 // up to 6.5 secs wait
time.Sleep(wait)
tx, err := runtime.Db.Beginx()
if err != nil {
return false, err
}
_, err = tx.Exec("LOCK TABLE `config` WRITE;")
if err != nil {
return false, err
}
defer func() {
_, err = tx.Exec("UNLOCK TABLES;")
if err != nil {
runtime.Log.Error("unable to unlock tables", err)
}
tx.Commit()
}()
_, err = tx.Exec("INSERT INTO `config` (`key`,`config`) " +
fmt.Sprintf(`VALUES ('DBLOCK','{"pid": "%d"}');`, os.Getpid()))
if err != nil {
// good error would be "Error 1062: Duplicate entry 'DBLOCK' for key 'idx_config_area'"
if strings.HasPrefix(err.Error(), "Error 1062:") {
runtime.Log.Info("Database locked by another Documize instance")
return false, nil
}
return false, err
}
runtime.Log.Info("Database locked by this Documize instance")
return true, err // success!
}
func unlockDB(rt *env.Runtime) error {
tx, err := rt.Db.Beginx()
if err != nil {
return err
}
_, err = tx.Exec("DELETE FROM `config` WHERE `key`='DBLOCK';")
if err != nil {
return err
}
return tx.Commit()
}
func migrateEnd(runtime *env.Runtime, tx *sqlx.Tx, err error, amLeader bool) error {
if amLeader {
defer func() { unlockDB(runtime) }()
if tx != nil {
if err == nil {
tx.Commit()
runtime.Log.Info("Database checks: completed")
return nil
}
tx.Rollback()
}
runtime.Log.Error("Database checks: failed: ", err)
return err
}
return nil // not the leader, so ignore errors
}
func getLastMigration(tx *sqlx.Tx) (lastMigration string, err error) {
var stmt *sql.Stmt
stmt, err = tx.Prepare("SELECT JSON_EXTRACT(`config`,'$.database') FROM `config` WHERE `key` = 'META';")
if err == nil {
defer streamutil.Close(stmt)
var item = make([]uint8, 0)
row := stmt.QueryRow()
err = row.Scan(&item)
if err == nil {
if len(item) > 1 {
q := []byte(`"`)
lastMigration = string(bytes.TrimPrefix(bytes.TrimSuffix(item, q), q))
}
}
}
return
}
// Migrate the database as required, consolidated action.
func Migrate(runtime *env.Runtime, ConfigTableExists bool) error {
amLeader := false
if ConfigTableExists {
var err error
amLeader, err = lockDB(runtime)
if err != nil {
runtime.Log.Error("unable to lock DB", err)
}
} else {
amLeader = true // what else can you do?
}
tx, err := runtime.Db.Beginx()
if err != nil {
return migrateEnd(runtime, tx, err, amLeader)
}
lastMigration := ""
if ConfigTableExists {
lastMigration, err = getLastMigration(tx)
if err != nil {
return migrateEnd(runtime, tx, err, amLeader)
}
runtime.Log.Info("Database checks: last applied " + lastMigration)
}
mig, err := migrations(lastMigration)
if err != nil {
return migrateEnd(runtime, tx, err, amLeader)
}
if len(mig) == 0 {
runtime.Log.Info("Database checks: no updates required")
return migrateEnd(runtime, tx, nil, amLeader) // no migrations to perform
}
if amLeader {
runtime.Log.Info("Database checks: will execute the following update files: " + strings.Join([]string(mig), ", "))
return migrateEnd(runtime, tx, mig.migrate(runtime, tx), amLeader)
}
// a follower instance
targetMigration := string(mig[len(mig)-1])
for targetMigration != lastMigration {
time.Sleep(time.Second)
runtime.Log.Info("Waiting for database migration completion")
tx.Rollback() // ignore error
tx, err := runtime.Db.Beginx() // need this in order to see the changed situation since last tx
if err != nil {
return migrateEnd(runtime, tx, err, amLeader)
}
lastMigration, _ = getLastMigration(tx)
}
return migrateEnd(runtime, tx, nil, amLeader)
}
func processSQLfile(tx *sqlx.Tx, v env.DbVariant, buf []byte) error {
stmts := getStatements(buf)
for _, stmt := range stmts {
// MariaDB has no specific JSON column type (but has JSON queries)
if v == env.DBVariantMariaDB {
stmt = strings.Replace(stmt, "` JSON", "` TEXT", -1)
}
_, err := tx.Exec(stmt)
if err != nil {
return err
}
}
return nil
}
// getStatement strips out the comments and returns all the individual SQL commands (apart from "USE") as a []string.
func getStatements(bytes []byte) []string {
/* Strip comments of the form '-- comment' or like this one */
stripped := regexp.MustCompile("(?s)--.*?\n|/\\*.*?\\*/").ReplaceAll(bytes, []byte("\n"))
sqls := strings.Split(string(stripped), ";")
ret := make([]string, 0, len(sqls))
for _, v := range sqls {
trimmed := strings.TrimSpace(v)
if len(trimmed) > 0 &&
!strings.HasPrefix(strings.ToUpper(trimmed), "USE ") { // make sure we don't USE the wrong database
ret = append(ret, trimmed+";")
}
}
return ret
}

39
core/database/params.go Normal file
View file

@ -0,0 +1,39 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package database
import (
"github.com/documize/community/core/env"
"github.com/jmoiron/sqlx"
)
// RebindParams changes MySQL query parameter placeholder from "?" to
// correct value for given database provider.
//
// MySQL uses ?, ?, ? (default for all Documize queries)
// PostgreSQL uses $1, $2, $3
// MS SQL Server uses @p1, @p2, @p3
func RebindParams(sql string, s env.StoreType) string {
bindParam := sqlx.QUESTION
switch s {
case env.StoreTypePostgreSQL:
bindParam = sqlx.DOLLAR
}
return sqlx.Rebind(bindParam, sql)
}
// RebindPostgreSQL is a helper method on top of RebindParams.
func RebindPostgreSQL(sql string) string {
return RebindParams(sql, env.StoreTypePostgreSQL)
}

85
core/database/scripts.go Normal file
View file

@ -0,0 +1,85 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package database
import (
"fmt"
"sort"
"github.com/documize/community/core/env"
"github.com/documize/community/server/web"
)
// Scripts holds all .SQL files for all supported database providers.
type Scripts struct {
MySQL []Script
PostgreSQL []Script
SQLServer []Script
}
// Script holds SQL script and it's associated version number.
type Script struct {
Version int
Script []byte
}
// LoadScripts returns .SQL scripts for supported database providers.
func LoadScripts() (s Scripts, err error) {
assetDir := "bindata/scripts"
// MySQL
s.MySQL, err = loadFiles(fmt.Sprintf("%s/mysql", assetDir))
if err != nil {
return
}
// PostgreSQL
s.PostgreSQL, err = loadFiles(fmt.Sprintf("%s/postgresql", assetDir))
if err != nil {
return
}
return s, nil
}
// SpecificScripts returns SQL scripts for current databasse provider.
func SpecificScripts(runtime *env.Runtime, all Scripts) (s []Script) {
switch runtime.StoreProvider.Type() {
case env.StoreTypeMySQL, env.StoreTypeMariaDB, env.StoreTypePercona:
return all.MySQL
case env.StoreTypePostgreSQL:
return all.PostgreSQL
case env.StoreTypeMSSQL:
return all.SQLServer
}
return
}
// loadFiles returns all SQL scripts in specified folder as [][]byte.
func loadFiles(path string) (b []Script, err error) {
buf := []byte{}
scripts, err := web.AssetDir(path)
if err != nil {
return
}
sort.Strings(scripts)
for _, file := range scripts {
buf, err = web.Asset(fmt.Sprintf("%s/%s", path, file))
if err != nil {
return
}
b = append(b, Script{Version: extractVersionNumber(file), Script: buf})
}
return b, nil
}

View file

@ -0,0 +1,339 @@
/* community edition */
-- table renaming
RENAME TABLE
`organization` TO dmz_org,
`label` TO dmz_space,
`category` TO dmz_category,
`categorymember` TO dmz_category_member,
`role` TO dmz_group,
`rolemember` TO dmz_group_member,
`permission` TO dmz_permission,
`document` TO dmz_doc,
`share` TO dmz_doc_share,
`vote` TO dmz_doc_vote,
`feedback` TO dmz_doc_comment,
`attachment` TO dmz_doc_attachment,
`link` TO dmz_doc_link,
`page` TO dmz_section,
`pagemeta` TO dmz_section_meta,
`block` TO dmz_section_template,
`revision` TO dmz_section_revision,
`user` TO dmz_user,
`account` TO dmz_user_account,
`useractivity` TO dmz_user_activity,
`userconfig` TO dmz_user_config,
`config` TO dmz_config,
`pin` TO dmz_pin,
`search` TO dmz_search,
`userevent` TO dmz_audit_log,
`useraction` TO dmz_action;
-- field renaming
ALTER TABLE dmz_org
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `company` `c_company` VARCHAR(500) NOT NULL,
CHANGE `title` `c_title` VARCHAR(500) NOT NULL,
CHANGE `message` `c_message` VARCHAR(500) NOT NULL,
CHANGE `domain` `c_domain` VARCHAR(200) NOT NULL DEFAULT '',
CHANGE `service` `c_service` VARCHAR(200) NOT NULL DEFAULT 'https://api.documize.com',
CHANGE `email` `c_email` VARCHAR(500) NOT NULL DEFAULT '',
CHANGE `allowanonymousaccess` `c_anonaccess` BOOL NOT NULL DEFAULT 0,
CHANGE `authprovider` `c_authprovider` CHAR(20) NOT NULL DEFAULT 'documize',
CHANGE `authconfig` `c_authconfig` JSON,
CHANGE `maxtags` `c_maxtags` INT NOT NULL DEFAULT 3,
CHANGE `verified` `c_verified` BOOL NOT NULL DEFAULT 0,
CHANGE `serial` `c_serial` VARCHAR(50) NOT NULL DEFAULT '',
CHANGE `active` `c_active` BOOL NOT NULL DEFAULT 1,
CHANGE `created` `c_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_space
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `type` `c_type` INT NOT NULL DEFAULT 1,
CHANGE `lifecycle` `c_lifecycle` INT NOT NULL DEFAULT 1,
CHANGE `label` `c_name` VARCHAR(300) NOT NULL,
CHANGE `likes` `c_likes` VARCHAR(1000) NOT NULL DEFAULT '',
CHANGE `created` `c_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_category
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `labelid` `c_spaceid` VARCHAR(20) NOT NULL,
CHANGE `category` `c_name` VARCHAR(50) NOT NULL,
CHANGE `created` `c_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_category_member
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `labelid` `c_spaceid` VARCHAR(20) NOT NULL,
CHANGE `categoryid` `c_categoryid` VARCHAR(20) NOT NULL,
CHANGE `documentid` `c_docid` VARCHAR(20) NOT NULL,
CHANGE `created` `c_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_group
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `role` `c_name` VARCHAR(50) NOT NULL DEFAULT '',
CHANGE `purpose` `c_desc` VARCHAR(100) DEFAULT '',
CHANGE `created` `c_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_group_member
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `roleid` `c_groupid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) NOT NULL;
ALTER TABLE dmz_permission
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `who` `c_who` VARCHAR(30) NOT NULL,
CHANGE `whoid` `c_whoid` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `action` `c_action` VARCHAR(30) NOT NULL,
CHANGE `scope` `c_scope` VARCHAR(30) NOT NULL,
CHANGE `location` `c_location` VARCHAR(100) NOT NULL,
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `created` `c_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_doc
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `labelid` `c_spaceid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `job` `c_job` CHAR(36) NOT NULL DEFAULT '',
CHANGE `location` `c_location` VARCHAR(2000) NOT NULL DEFAULT '',
CHANGE `title` `c_name` VARCHAR(2000) NOT NULL DEFAULT '',
CHANGE `excerpt` `c_desc` VARCHAR(2000) NOT NULL DEFAULT '',
CHANGE `slug` `c_slug` VARCHAR(2000) NOT NULL DEFAULT '',
CHANGE `tags` `c_tags` VARCHAR(1000) NOT NULL DEFAULT '',
CHANGE `template` `c_template` BOOL NOT NULL DEFAULT 0,
CHANGE `protection` `c_protection` INT NOT NULL DEFAULT 0,
CHANGE `approval` `c_approval` INT NOT NULL DEFAULT 0,
CHANGE `lifecycle` `c_lifecycle` INT NOT NULL DEFAULT 1,
CHANGE `versioned` `c_versioned` BOOL NOT NULL DEFAULT 0,
CHANGE `versionid` `c_versionid` VARCHAR(100) NOT NULL DEFAULT '',
CHANGE `versionorder` `c_versionorder` INT NOT NULL DEFAULT 0,
CHANGE `groupid` `c_groupid` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `created` `c_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_doc_share
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `documentid` `c_docid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) DEFAULT '',
CHANGE `email` `c_email` VARCHAR(250) NOT NULL DEFAULT '',
CHANGE `message` `c_message` VARCHAR(500) NOT NULL DEFAULT '',
CHANGE `viewed` `c_viewed` VARCHAR(500) NOT NULL DEFAULT '',
CHANGE `secret` `c_secret` VARCHAR(250) NOT NULL DEFAULT '',
CHANGE `expires` `c_expires` VARCHAR(20) DEFAULT '',
CHANGE `active` `c_active` BOOL NOT NULL DEFAULT 1,
CHANGE `created` `c_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_doc_vote
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `documentid` `c_docid` VARCHAR(20) NOT NULL,
CHANGE `voter` `c_voter` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `vote` `c_vote` INT NOT NULL DEFAULT 0,
CHANGE `created` `c_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_doc_comment
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `documentid` `c_docid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) DEFAULT '',
CHANGE `email` `c_email` VARCHAR(250) NOT NULL DEFAULT '',
CHANGE `feedback` `c_feedback` LONGTEXT,
CHANGE `created` `c_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_doc_attachment
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `documentid` `c_docid` VARCHAR(20) NOT NULL,
CHANGE `job` `c_job` CHAR(36) NOT NULL,
CHANGE `fileid` `c_fileid` CHAR(10) NOT NULL,
CHANGE `filename` `c_filename` VARCHAR(255) NOT NULL,
CHANGE `data` `c_data` LONGBLOB,
CHANGE `extension` `c_extension` CHAR(6) NOT NULL,
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_doc_link
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `folderid` `c_spaceid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) NOT NULL,
CHANGE `sourcedocumentid` `c_sourcedocid` VARCHAR(20) NOT NULL,
CHANGE `sourcepageid` `c_sourcesectionid` VARCHAR(20) NOT NULL,
CHANGE `linktype` `c_type` VARCHAR(20) NOT NULL,
CHANGE `targetdocumentid` `c_targetdocid` VARCHAR(20) NOT NULL,
CHANGE `targetid` `c_targetid` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `externalid` `c_externalid` VARCHAR(1000) NOT NULL DEFAULT '',
CHANGE `orphan` `c_orphan` BOOL NOT NULL DEFAULT 0,
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_section
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `documentid` `c_docid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `pagetype` `c_type` CHAR(10) NOT NULL DEFAULT 'section',
CHANGE `contenttype` `c_contenttype` CHAR(20) NOT NULL DEFAULT 'wysiwyg',
CHANGE `blockid` `c_templateid` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `level` `c_level` INT UNSIGNED NOT NULL,
CHANGE `sequence` `c_sequence` DOUBLE NOT NULL,
CHANGE `title` `c_name` VARCHAR(2000) NOT NULL DEFAULT '',
CHANGE `body` `c_body` LONGTEXT,
CHANGE `revisions` `c_revisions` INT UNSIGNED NOT NULL,
CHANGE `status` `c_status` INT NOT NULL DEFAULT 0,
CHANGE `relativeid` `c_relativeid` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_section_meta
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `documentid` `c_docid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `pageid` `c_sectionid` VARCHAR(20) NOT NULL,
CHANGE `rawbody` `c_rawbody` LONGBLOB,
CHANGE `config` `c_config` JSON,
CHANGE `externalsource` `c_external` BOOL DEFAULT 0,
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_section_template
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `labelid` `c_spaceid` VARCHAR(20) DEFAULT '',
CHANGE `userid` `c_userid` VARCHAR(20) DEFAULT '',
CHANGE `pagetype` `c_type` CHAR(10) NOT NULL DEFAULT 'section',
CHANGE `contenttype` `c_contenttype` CHAR(20) NOT NULL DEFAULT 'wysiwyg',
CHANGE `title` `c_name` VARCHAR(2000) NOT NULL DEFAULT '',
CHANGE `body` `c_body` LONGTEXT,
CHANGE `excerpt` `c_desc` VARCHAR(2000) NOT NULL DEFAULT '',
CHANGE `used` `c_used` INT UNSIGNED NOT NULL,
CHANGE `rawbody` `c_rawbody` LONGBLOB,
CHANGE `config` `c_config` JSON,
CHANGE `externalsource` `c_external` BOOL DEFAULT 0,
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_section_revision
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `documentid` `c_docid` VARCHAR(20) NOT NULL,
CHANGE `ownerid` `c_ownerid` VARCHAR(20) DEFAULT '',
CHANGE `pageid` `c_sectionid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) NOT NULL,
CHANGE `pagetype` `c_type` CHAR(10) NOT NULL DEFAULT 'section',
CHANGE `contenttype` `c_contenttype` CHAR(20) NOT NULL DEFAULT 'wysiwyg',
CHANGE `title` `c_name` VARCHAR(2000) NOT NULL DEFAULT '',
CHANGE `body` `c_body` LONGTEXT,
CHANGE `rawbody` `c_rawbody` LONGBLOB,
CHANGE `config` `c_config` JSON,
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_user
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `firstname` `c_firstname` VARCHAR(500) NOT NULL DEFAULT '',
CHANGE `lastname` `c_lastname` VARCHAR(500) NOT NULL DEFAULT '',
CHANGE `email` `c_email` VARCHAR(500) NOT NULL DEFAULT '',
CHANGE `initials` `c_initials` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `global` `c_globaladmin` BOOL NOT NULL DEFAULT 0,
CHANGE `password` `c_password` VARCHAR(500) NOT NULL DEFAULT '',
CHANGE `salt` `c_salt` VARCHAR(500) NOT NULL DEFAULT '',
CHANGE `reset` `c_reset` VARCHAR(500) NOT NULL DEFAULT '',
CHANGE `active` `c_active` BOOL NOT NULL DEFAULT 1,
CHANGE `lastversion` `c_lastversion` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_user_account
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) NOT NULL,
CHANGE `editor` `c_editor` BOOL NOT NULL DEFAULT 0,
CHANGE `admin` `c_admin` BOOL NOT NULL DEFAULT 0,
CHANGE `users` `c_users` BOOL NOT NULL DEFAULT 1,
CHANGE `analytics` `c_analytics` BOOL NOT NULL DEFAULT 0,
CHANGE `active` `c_active` BOOL NOT NULL DEFAULT 1,
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_user_activity
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) NOT NULL,
CHANGE `labelid` `c_spaceid` VARCHAR(20) NOT NULL,
CHANGE `documentid` `c_docid` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `pageid` `c_sectionid` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `sourcetype` `c_sourcetype` INT NOT NULL DEFAULT 0,
CHANGE `activitytype` `c_activitytype` INT NOT NULL DEFAULT 0,
CHANGE `metadata` `c_metadata` VARCHAR(1000) NOT NULL DEFAULT '',
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_user_config
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) NOT NULL,
CHANGE `key` `c_key` CHAR(200) NOT NULL,
CHANGE `config` `c_config` JSON;
ALTER TABLE dmz_config
CHANGE `key` `c_key` CHAR(200) NOT NULL,
CHANGE `config` `c_config` JSON;
ALTER TABLE dmz_pin
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) DEFAULT '',
CHANGE `labelid` `c_spaceid` VARCHAR(20) DEFAULT '',
CHANGE `documentid` `c_docid` VARCHAR(20) DEFAULT '',
CHANGE `sequence` `c_sequence` INT UNSIGNED NOT NULL DEFAULT 99,
CHANGE `pin` `c_name` CHAR(100) NOT NULL DEFAULT '',
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_search
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `documentid` `c_docid` VARCHAR(20) NOT NULL,
CHANGE `itemid` `c_itemid` VARCHAR(20) NOT NULL DEFAULT '',
CHANGE `itemtype` `c_itemtype` VARCHAR(10) NOT NULL,
CHANGE `content` `c_content` LONGTEXT,
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_audit_log
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) NOT NULL,
CHANGE `eventtype` `c_eventtype` VARCHAR(100) NOT NULL DEFAULT '',
CHANGE `ip` `c_ip` VARCHAR(39) NOT NULL DEFAULT '',
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE dmz_action
CHANGE `refid` `c_refid` VARCHAR(20) NOT NULL,
CHANGE `orgid` `c_orgid` VARCHAR(20) NOT NULL,
CHANGE `documentid` `c_docid` VARCHAR(20) NOT NULL,
CHANGE `userid` `c_userid` VARCHAR(20) NOT NULL,
CHANGE `requestorid` `c_requestorid` VARCHAR(20) NOT NULL,
CHANGE `actiontype` `c_actiontype` INT NOT NULL DEFAULT 0,
CHANGE `note` `c_note` VARCHAR(2000) NOT NULL DEFAULT '',
CHANGE `requested` `c_requested` TIMESTAMP NULL,
CHANGE `due` `c_due` TIMESTAMP NULL,
CHANGE `completed` `c_completed` TIMESTAMP NULL,
CHANGE `iscomplete` `c_iscomplete` BOOL NOT NULL DEFAULT 0,
CHANGE `reftype` `c_reftype` CHAR(1) NOT NULL DEFAULT 'D',
CHANGE `reftypeid` `c_reftypeid` VARCHAR(20) NOT NULL,
CHANGE `created` `c_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHANGE `revised` `c_revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
-- deprecations
DROP TABLE IF EXISTS `participant`;

View file

@ -0,0 +1,479 @@
-- SQL to set up the Documize database
-- select * from information_schema.tables WHERE table_catalog='documize';
-- http://www.postgresqltutorial.com/postgresql-json/
-- https://en.wikibooks.org/wiki/Converting_MySQL_to_PostgreSQL
DROP TABLE IF EXISTS dmz_action;
CREATE TABLE dmz_action (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic NOT NULL,
c_docid varchar(20) COLLATE ucs_basic NOT NULL,
c_requestorid varchar(20) COLLATE ucs_basic NOT NULL,
c_actiontype int NOT NULL DEFAULT '0',
c_note varchar(2000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_requested timestamp NULL DEFAULT NULL,
c_due timestamp NULL DEFAULT NULL,
c_completed timestamp NULL DEFAULT NULL,
c_iscomplete bool NOT NULL DEFAULT '0',
c_reftype varchar(1) COLLATE ucs_basic NOT NULL DEFAULT 'D',
c_reftypeid varchar(20) COLLATE ucs_basic NOT NULL,
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
CREATE INDEX idx_action_1 ON dmz_action (c_refid);
CREATE INDEX idx_action_2 ON dmz_action (c_userid);
CREATE INDEX idx_action_3 ON dmz_action (c_docid);
CREATE INDEX idx_action_4 ON dmz_action (c_requestorid);
DROP TABLE IF EXISTS dmz_audit_log;
CREATE TABLE dmz_audit_log (
id bigserial NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic NOT NULL,
c_eventtype varchar(100) COLLATE ucs_basic NOT NULL DEFAULT '',
c_ip varchar(39) COLLATE ucs_basic NOT NULL DEFAULT '',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
CREATE INDEX idx_audit_log_1 ON dmz_audit_log (c_orgid);
CREATE INDEX idx_audit_log_2 ON dmz_audit_log (c_userid);
CREATE INDEX idx_audit_log_3 ON dmz_audit_log (c_eventtype);
DROP TABLE IF EXISTS dmz_category;
CREATE TABLE dmz_category (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_spaceid varchar(20) COLLATE ucs_basic NOT NULL,
c_name varchar(50) COLLATE ucs_basic NOT NULL,
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (id)
);
CREATE INDEX idx_category_1 ON dmz_category (c_refid);
CREATE INDEX idx_category_2 ON dmz_category (c_orgid);
CREATE INDEX idx_category_3 ON dmz_category (c_orgid,c_spaceid);
DROP TABLE IF EXISTS dmz_category_member;
CREATE TABLE dmz_category_member (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_spaceid varchar(20) COLLATE ucs_basic NOT NULL,
c_categoryid varchar(20) COLLATE ucs_basic NOT NULL,
c_docid varchar(20) COLLATE ucs_basic NOT NULL,
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (id)
);
CREATE INDEX idx_category_member_1 ON dmz_category_member (c_docid);
CREATE INDEX idx_category_member_2 ON dmz_category_member (c_orgid,c_docid);
CREATE INDEX idx_category_member_3 ON dmz_category_member (c_orgid,c_spaceid);
DROP TABLE IF EXISTS dmz_config;
CREATE TABLE dmz_config (
c_key varchar(200) COLLATE ucs_basic NOT NULL,
c_config json DEFAULT NULL,
UNIQUE (c_key)
);
DROP TABLE IF EXISTS dmz_doc;
CREATE TABLE dmz_doc (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_spaceid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_job varchar(36) COLLATE ucs_basic NOT NULL DEFAULT '',
c_location varchar(2000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_name varchar(2000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_desc varchar(2000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_slug varchar(2000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_tags varchar(1000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_template bool NOT NULL DEFAULT '0',
c_protection int NOT NULL DEFAULT '0',
c_approval int NOT NULL DEFAULT '0',
c_lifecycle int NOT NULL DEFAULT '1',
c_versioned bool NOT NULL DEFAULT '0',
c_versionid varchar(100) COLLATE ucs_basic NOT NULL DEFAULT '',
c_versionorder int NOT NULL DEFAULT '0',
c_groupid varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (c_refid)
);
CREATE INDEX idx_doc_1 ON dmz_doc (id);
CREATE INDEX idx_doc_2 ON dmz_doc (c_orgid);
CREATE INDEX idx_doc_3 ON dmz_doc (c_spaceid);
DROP TABLE IF EXISTS dmz_doc_attachment;
CREATE TABLE dmz_doc_attachment (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_docid varchar(20) COLLATE ucs_basic NOT NULL,
c_job varchar(36) COLLATE ucs_basic NOT NULL,
c_fileid varchar(10) COLLATE ucs_basic NOT NULL,
c_filename varchar(255) COLLATE ucs_basic NOT NULL,
c_data BYTEA,
c_extension varchar(6) COLLATE ucs_basic NOT NULL,
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (c_refid)
);
CREATE INDEX idx_doc_attachment_1 ON dmz_doc_attachment (id);
CREATE INDEX idx_doc_attachment_2 ON dmz_doc_attachment (c_orgid);
CREATE INDEX idx_doc_attachment_3 ON dmz_doc_attachment (c_docid);
CREATE INDEX idx_doc_attachment_4 ON dmz_doc_attachment (c_job,c_fileid);
DROP TABLE IF EXISTS dmz_doc_comment;
CREATE TABLE dmz_doc_comment (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_docid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic DEFAULT '',
c_email varchar(250) COLLATE ucs_basic NOT NULL DEFAULT '',
c_feedback text COLLATE ucs_basic,
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
CREATE INDEX idx_doc_comment_1 ON dmz_doc_comment (c_refid);
DROP TABLE IF EXISTS dmz_doc_link;
CREATE TABLE dmz_doc_link (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_spaceid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic NOT NULL,
c_sourcedocid varchar(20) COLLATE ucs_basic NOT NULL,
c_sourcesectionid varchar(20) COLLATE ucs_basic NOT NULL,
c_type varchar(20) COLLATE ucs_basic NOT NULL,
c_targetdocid varchar(20) COLLATE ucs_basic NOT NULL,
c_targetid varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_externalid varchar(1000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_orphan bool NOT NULL DEFAULT '0',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
DROP TABLE IF EXISTS dmz_doc_share;
CREATE TABLE dmz_doc_share (
id bigserial NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_docid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic DEFAULT '',
c_email varchar(250) COLLATE ucs_basic NOT NULL DEFAULT '',
c_message varchar(500) COLLATE ucs_basic NOT NULL DEFAULT '',
c_viewed varchar(500) COLLATE ucs_basic NOT NULL DEFAULT '',
c_secret varchar(250) COLLATE ucs_basic NOT NULL DEFAULT '',
c_expires varchar(20) COLLATE ucs_basic DEFAULT '',
c_active bool NOT NULL DEFAULT '1',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
DROP TABLE IF EXISTS dmz_doc_vote;
CREATE TABLE dmz_doc_vote (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_docid varchar(20) COLLATE ucs_basic NOT NULL,
c_voter varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_vote int NOT NULL DEFAULT '0',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (id)
);
CREATE INDEX idx_doc_vote_1 ON dmz_doc_vote (c_refid);
CREATE INDEX idx_doc_vote_2 ON dmz_doc_vote (c_docid);
CREATE INDEX idx_doc_vote_3 ON dmz_doc_vote (c_orgid);
CREATE INDEX idx_doc_vote_4 ON dmz_doc_vote (c_orgid,c_docid);
DROP TABLE IF EXISTS dmz_group;
CREATE TABLE dmz_group (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_name varchar(50) COLLATE ucs_basic NOT NULL DEFAULT '',
c_desc varchar(100) COLLATE ucs_basic DEFAULT '',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (id)
);
CREATE INDEX idx_group_1 ON dmz_group (c_refid);
CREATE INDEX idx_group_2 ON dmz_group (c_orgid);
DROP TABLE IF EXISTS dmz_group_member;
CREATE TABLE dmz_group_member (
id bigserial NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_groupid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic NOT NULL,
UNIQUE (id)
);
CREATE INDEX idx_group_member_1 ON dmz_group_member (c_groupid,c_userid);
CREATE INDEX idx_group_member_2 ON dmz_group_member (c_orgid,c_groupid,c_userid);
DROP TABLE IF EXISTS dmz_org;
CREATE TABLE dmz_org (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_company varchar(500) COLLATE ucs_basic NOT NULL,
c_title varchar(500) COLLATE ucs_basic NOT NULL,
c_message varchar(500) COLLATE ucs_basic NOT NULL,
c_domain varchar(200) COLLATE ucs_basic NOT NULL DEFAULT '',
c_service varchar(200) COLLATE ucs_basic NOT NULL DEFAULT 'https://api.documize.com',
c_email varchar(500) COLLATE ucs_basic NOT NULL DEFAULT '',
c_anonaccess bool NOT NULL DEFAULT '0',
c_authprovider varchar(20) COLLATE ucs_basic NOT NULL DEFAULT 'documize',
c_authconfig json DEFAULT NULL,
c_maxtags int NOT NULL DEFAULT '3',
c_verified bool NOT NULL DEFAULT '0',
c_serial varchar(50) COLLATE ucs_basic NOT NULL DEFAULT '',
c_active bool NOT NULL DEFAULT '1',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (c_refid)
);
CREATE INDEX idx_org_1 ON dmz_org (id);
CREATE INDEX idx_org_2 ON dmz_org (c_domain);
DROP TABLE IF EXISTS dmz_permission;
CREATE TABLE dmz_permission (
id bigserial NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_who varchar(30) COLLATE ucs_basic NOT NULL,
c_whoid varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_action varchar(30) COLLATE ucs_basic NOT NULL,
c_scope varchar(30) COLLATE ucs_basic NOT NULL,
c_location varchar(100) COLLATE ucs_basic NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (id)
);
CREATE INDEX idx_permission_1 ON dmz_permission (c_orgid);
CREATE INDEX idx_permission_2 ON dmz_permission (c_orgid,c_who,c_whoid,c_location);
CREATE INDEX idx_permission_3 ON dmz_permission (c_orgid,c_who,c_whoid,c_location,c_action);
CREATE INDEX idx_permission_4 ON dmz_permission (c_orgid,c_location,c_refid);
CREATE INDEX idx_permission_5 ON dmz_permission (c_orgid,c_who,c_location,c_action);
DROP TABLE IF EXISTS dmz_pin;
CREATE TABLE dmz_pin (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic DEFAULT '',
c_spaceid varchar(20) COLLATE ucs_basic DEFAULT '',
c_docid varchar(20) COLLATE ucs_basic DEFAULT '',
c_sequence BIGINT NOT NULL DEFAULT '99',
c_name varchar(100) COLLATE ucs_basic NOT NULL DEFAULT '',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
CREATE INDEX idx_pin_1 ON dmz_pin (c_userid);
DROP TABLE IF EXISTS dmz_search;
CREATE TABLE dmz_search (
id bigserial NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_docid varchar(20) COLLATE ucs_basic NOT NULL,
c_itemid varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_itemtype varchar(10) COLLATE ucs_basic NOT NULL,
c_content text COLLATE ucs_basic,
c_token TSVECTOR,
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (id)
);
CREATE INDEX idx_search_1 ON dmz_search (c_orgid);
CREATE INDEX idx_search_2 ON dmz_search (c_docid);
CREATE INDEX idx_search_3 ON dmz_search USING GIN(c_token);
DROP TABLE IF EXISTS dmz_section;
CREATE TABLE dmz_section (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_docid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_contenttype varchar(20) COLLATE ucs_basic NOT NULL DEFAULT 'wysiwyg',
c_type varchar(10) COLLATE ucs_basic NOT NULL DEFAULT 'section',
c_templateid varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_level bigint NOT NULL,
c_sequence double precision NOT NULL,
c_name varchar(2000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_body text COLLATE ucs_basic,
c_revisions bigint NOT NULL,
c_status int NOT NULL DEFAULT '0',
c_relativeid varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (c_refid)
);
CREATE INDEX idx_section_1 ON dmz_section (id);
CREATE INDEX idx_section_2 ON dmz_section (c_orgid);
CREATE INDEX idx_section_3 ON dmz_section (c_docid);
DROP TABLE IF EXISTS dmz_section_meta;
CREATE TABLE dmz_section_meta (
id bigserial NOT NULL,
c_sectionid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_docid varchar(20) COLLATE ucs_basic NOT NULL,
c_rawbody BYTEA,
c_config json DEFAULT NULL,
c_external bool DEFAULT '0',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (c_sectionid)
);
CREATE INDEX idx_section_meta_1 ON dmz_section_meta (id);
CREATE INDEX idx_section_meta_2 ON dmz_section_meta (c_sectionid);
CREATE INDEX idx_section_meta_3 ON dmz_section_meta (c_orgid);
CREATE INDEX idx_section_meta_4 ON dmz_section_meta (c_docid);
DROP TABLE IF EXISTS dmz_section_revision;
CREATE TABLE dmz_section_revision (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_docid varchar(20) COLLATE ucs_basic NOT NULL,
c_ownerid varchar(20) COLLATE ucs_basic DEFAULT '',
c_sectionid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic NOT NULL,
c_contenttype varchar(20) COLLATE ucs_basic NOT NULL DEFAULT 'wysiwyg',
c_type varchar(10) COLLATE ucs_basic NOT NULL DEFAULT 'section',
c_name varchar(2000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_body text COLLATE ucs_basic,
c_rawbody BYTEA,
c_config json DEFAULT NULL,
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (c_refid)
);
CREATE INDEX idx_section_revision_1 ON dmz_section_revision (id);
CREATE INDEX idx_section_revision_2 ON dmz_section_revision (c_orgid);
CREATE INDEX idx_section_revision_3 ON dmz_section_revision (c_docid);
CREATE INDEX idx_section_revision_4 ON dmz_section_revision (c_sectionid);
DROP TABLE IF EXISTS dmz_section_template;
CREATE TABLE dmz_section_template (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_spaceid varchar(20) COLLATE ucs_basic DEFAULT '',
c_userid varchar(20) COLLATE ucs_basic DEFAULT '',
c_contenttype varchar(20) COLLATE ucs_basic NOT NULL DEFAULT 'wysiwyg',
c_type varchar(10) COLLATE ucs_basic NOT NULL DEFAULT 'section',
c_name varchar(2000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_body text COLLATE ucs_basic,
c_desc varchar(2000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_used bigint NOT NULL,
c_rawbody BYTEA,
c_config json DEFAULT NULL,
c_external bool DEFAULT '0',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
CREATE INDEX idx_section_template_1 ON dmz_section_template (c_refid);
CREATE INDEX idx_section_template_2 ON dmz_section_template (c_spaceid);
DROP TABLE IF EXISTS dmz_space;
CREATE TABLE dmz_space (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_name varchar(300) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_type int NOT NULL DEFAULT '1',
c_lifecycle int NOT NULL DEFAULT '1',
c_likes varchar(1000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (c_refid)
);
CREATE INDEX idx_space_1 ON dmz_space (id);
CREATE INDEX idx_space_2 ON dmz_space (c_userid);
CREATE INDEX idx_space_3 ON dmz_space (c_orgid);
DROP TABLE IF EXISTS dmz_user;
CREATE TABLE dmz_user (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_firstname varchar(500) COLLATE ucs_basic NOT NULL DEFAULT '',
c_lastname varchar(500) COLLATE ucs_basic NOT NULL DEFAULT '',
c_email varchar(500) COLLATE ucs_basic NOT NULL DEFAULT '',
c_initials varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_globaladmin bool NOT NULL DEFAULT '0',
c_password varchar(500) COLLATE ucs_basic NOT NULL DEFAULT '',
c_salt varchar(500) COLLATE ucs_basic NOT NULL DEFAULT '',
c_reset varchar(500) COLLATE ucs_basic NOT NULL DEFAULT '',
c_active bool NOT NULL DEFAULT '1',
c_lastversion varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (c_refid)
);
CREATE INDEX idx_user_1 ON dmz_user (id);
CREATE INDEX idx_user_2 ON dmz_user (c_email);
DROP TABLE IF EXISTS dmz_user_account;
CREATE TABLE dmz_user_account (
id bigserial NOT NULL,
c_refid varchar(20) COLLATE ucs_basic NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic NOT NULL,
c_editor bool NOT NULL DEFAULT '0',
c_admin bool NOT NULL DEFAULT '0',
c_users bool NOT NULL DEFAULT '1',
c_analytics bool NOT NULL DEFAULT '0',
c_active bool NOT NULL DEFAULT '1',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
c_revised timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (c_refid)
);
CREATE INDEX idx_user_account_1 ON dmz_user_account (id);
CREATE INDEX idx_user_account_2 ON dmz_user_account (c_userid);
CREATE INDEX idx_user_account_3 ON dmz_user_account (c_orgid);
DROP TABLE IF EXISTS dmz_user_activity;
CREATE TABLE dmz_user_activity (
id bigserial NOT NULL,
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic NOT NULL,
c_spaceid varchar(20) COLLATE ucs_basic NOT NULL,
c_docid varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_sectionid varchar(20) COLLATE ucs_basic NOT NULL DEFAULT '',
c_sourcetype int NOT NULL DEFAULT '0',
c_activitytype int NOT NULL DEFAULT '0',
c_metadata varchar(1000) COLLATE ucs_basic NOT NULL DEFAULT '',
c_created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
CREATE INDEX idx_user_activity_1 ON dmz_user_activity (c_orgid);
CREATE INDEX idx_user_activity_2 ON dmz_user_activity (c_userid);
CREATE INDEX idx_user_activity_3 ON dmz_user_activity (c_activitytype);
CREATE INDEX idx_user_activity_4 ON dmz_user_activity (c_orgid,c_docid,c_sourcetype);
CREATE INDEX idx_user_activity_5 ON dmz_user_activity (c_orgid,c_docid,c_userid,c_sourcetype);
DROP TABLE IF EXISTS dmz_user_config;
CREATE TABLE dmz_user_config (
c_orgid varchar(20) COLLATE ucs_basic NOT NULL,
c_userid varchar(20) COLLATE ucs_basic NOT NULL,
c_key varchar(200) COLLATE ucs_basic NOT NULL,
c_config json DEFAULT NULL,
UNIQUE (c_orgid,c_userid,c_key)
);
INSERT INTO dmz_config VALUES ('SMTP','{"userid": "","password": "","host": "","port": "","sender": ""}');
INSERT INTO dmz_config VALUES ('FILEPLUGINS', '[{"Comment": "Disable (or not) built-in html import (NOTE: no Plugin name)","Disabled": false,"API": "Convert","Actions": ["htm","html"]},{"Comment": "Disable (or not) built-in Documize API import used from SDK (NOTE: no Plugin name)","Disabled": false,"API": "Convert","Actions": ["documizeapi"]}]');
INSERT INTO dmz_config VALUES ('SECTION-TRELLO','{"appKey": ""}');
INSERT INTO dmz_config VALUES ('META','{"database": "0"}');

View file

@ -13,9 +13,7 @@ package database
import (
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/documize/community/core/api/plugins"
@ -23,14 +21,14 @@ import (
"github.com/documize/community/core/secrets"
"github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/server/web"
)
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
}
// Setup the tables in a blank database
@ -89,7 +87,7 @@ func (h *Handler) Setup(w http.ResponseWriter, r *http.Request) {
return
}
if err = Migrate(h.Runtime, false /* no tables exist yet */); err != nil {
if err = InstallUpgrade(h.Runtime, false); err != nil {
h.Runtime.Log.Error("database.Setup migrate", err)
return
}
@ -124,130 +122,124 @@ type onboardRequest struct {
// setupAccount prepares the database for a newly onboard customer.
// Once done, they can then login and use Documize.
func setupAccount(rt *env.Runtime, completion onboardRequest, serial string) (err error) {
tx, err := rt.Db.Beginx()
if err != nil {
rt.Log.Error("setup - failed to get transaction", err)
return
}
//accountTitle := "This is where you will find documentation for your all projects. You can customize this message from the settings screen."
salt := secrets.GenerateSalt()
password := secrets.GeneratePassword(completion.Password, salt)
// Allocate organization to the user.
orgID := uniqueid.Generate()
sql := fmt.Sprintf("insert into organization (refid, company, title, message, domain, email, serial) values (\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\")",
_, err = tx.Exec(RebindParams("INSERT INTO dmz_org (c_refid, c_company, c_title, c_message, c_domain, c_email, c_serial) VALUES (?, ?, ?, ?, ?, ?, ?)", rt.StoreProvider.Type()),
orgID, completion.Company, completion.CompanyLong, completion.Message, completion.URL, completion.Email, serial)
_, err = runSQL(rt, sql)
if err != nil {
rt.Log.Error("Failed to insert into organization", err)
rt.Log.Error("INSERT INTO dmz_org failed", err)
tx.Rollback()
return
}
// Create user.
userID := uniqueid.Generate()
sql = fmt.Sprintf("insert into user (refid, firstname, lastname, email, initials, salt, password, global) values (\"%s\",\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", 1)",
userID, completion.Firstname, completion.Lastname, completion.Email, stringutil.MakeInitials(completion.Firstname, completion.Lastname), salt, password)
_, err = runSQL(rt, sql)
_, err = tx.Exec(RebindParams("INSERT INTO dmz_user (c_refid, c_firstname, c_lastname, c_email, c_initials, c_salt, c_password, c_globaladmin) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", rt.StoreProvider.Type()),
userID, completion.Firstname, completion.Lastname, completion.Email, stringutil.MakeInitials(completion.Firstname, completion.Lastname), salt, password, true)
if err != nil {
rt.Log.Error("Failed with error", err)
return err
rt.Log.Error("INSERT INTO dmz_user failed", err)
tx.Rollback()
return
}
// Link user to organization.
accountID := uniqueid.Generate()
sql = fmt.Sprintf("insert into account (refid, userid, orgid, `admin`, editor, users, analytics) values (\"%s\", \"%s\", \"%s\", 1, 1, 1, 1)", accountID, userID, orgID)
_, err = runSQL(rt, sql)
_, err = tx.Exec(RebindParams("INSERT INTO dmz_user_account (c_refid, c_userid, c_orgid, c_admin, c_editor, c_users, c_analytics) VALUES (?, ?, ?, ?, ?, ?, ?)", rt.StoreProvider.Type()),
accountID, userID, orgID, true, true, true, true)
if err != nil {
rt.Log.Error("Failed with error", err)
return err
rt.Log.Error("INSERT INTO dmz_user_account failed", err)
tx.Rollback()
return
}
// create space
labelID := uniqueid.Generate()
sql = fmt.Sprintf("insert into label (refid, orgid, label, type, userid) values (\"%s\", \"%s\", \"My Project\", 2, \"%s\")", labelID, orgID, userID)
_, err = runSQL(rt, sql)
// Create space.
spaceID := uniqueid.Generate()
_, err = tx.Exec(RebindParams("INSERT INTO dmz_space (c_refid, c_orgid, c_userid, c_name, c_type) VALUES (?, ?, ?, ?, ?)", rt.StoreProvider.Type()),
spaceID, orgID, userID, "Welcome", 2)
if err != nil {
rt.Log.Error("insert into label failed", err)
rt.Log.Error("INSERT INTO dmz_space failed", err)
tx.Rollback()
return
}
// assign permissions to space
// Assign permissions to space.
perms := []string{"view", "manage", "own", "doc-add", "doc-edit", "doc-delete", "doc-move", "doc-copy", "doc-template", "doc-approve", "doc-version", "doc-lifecycle"}
for _, p := range perms {
sql = fmt.Sprintf("insert into permission (orgid, who, whoid, action, scope, location, refid) values (\"%s\", 'user', \"%s\", \"%s\", 'object', 'space', \"%s\")", orgID, userID, p, labelID)
_, err = runSQL(rt, sql)
_, err = tx.Exec(RebindParams("INSERT INTO dmz_permission (c_orgid, c_who, c_whoid, c_action, c_scope, c_location, c_refid) VALUES (?, ?, ?, ?, ?, ?, ?)", rt.StoreProvider.Type()),
orgID, "user", userID, p, "object", "space", spaceID)
if err != nil {
rt.Log.Error("insert into permission failed", err)
rt.Log.Error("INSERT INTO dmz_permission failed", err)
tx.Rollback()
return
}
}
// Create some user groups
// Create some user groups.
groupDevID := uniqueid.Generate()
sql = fmt.Sprintf("INSERT INTO role (refid, orgid, role, purpose) VALUES (\"%s\", \"%s\", \"Technology\", \"On-site and remote development teams\")", groupDevID, orgID)
_, err = runSQL(rt, sql)
_, err = tx.Exec(RebindParams("INSERT INTO dmz_group (c_refid, c_orgid, c_name, c_desc) VALUES (?, ?, ?, ?)", rt.StoreProvider.Type()),
groupDevID, orgID, "Technology", "On-site and remote development teams")
if err != nil {
rt.Log.Error("insert into role failed", err)
rt.Log.Error("INSERT INTO dmz_group failed", err)
tx.Rollback()
return
}
groupProjectID := uniqueid.Generate()
sql = fmt.Sprintf("INSERT INTO role (refid, orgid, role, purpose) VALUES (\"%s\", \"%s\", \"Project Management\", \"HQ project management\")", groupProjectID, orgID)
_, err = runSQL(rt, sql)
_, err = tx.Exec(RebindParams("INSERT INTO dmz_group (c_refid, c_orgid, c_name, c_desc) VALUES (?, ?, ?, ?)", rt.StoreProvider.Type()),
groupProjectID, orgID, "Project Management", "HQ PMO and Account Management departments")
if err != nil {
rt.Log.Error("insert into role failed", err)
rt.Log.Error("INSERT INTO dmz_group failed", err)
tx.Rollback()
return
}
groupBackofficeID := uniqueid.Generate()
sql = fmt.Sprintf("INSERT INTO role (refid, orgid, role, purpose) VALUES (\"%s\", \"%s\", \"Back Office\", \"Non-IT and PMO personnel\")", groupBackofficeID, orgID)
_, err = runSQL(rt, sql)
if err != nil {
rt.Log.Error("insert into role failed", err)
}
// Join some groups
sql = fmt.Sprintf("INSERT INTO rolemember (orgid, roleid, userid) VALUES (\"%s\", \"%s\", \"%s\")", orgID, groupDevID, userID)
_, err = runSQL(rt, sql)
if err != nil {
rt.Log.Error("insert into rolemember failed", err)
}
sql = fmt.Sprintf("INSERT INTO rolemember (orgid, roleid, userid) VALUES (\"%s\", \"%s\", \"%s\")", orgID, groupProjectID, userID)
_, err = runSQL(rt, sql)
if err != nil {
rt.Log.Error("insert into rolemember failed", err)
}
sql = fmt.Sprintf("INSERT INTO rolemember (orgid, roleid, userid) VALUES (\"%s\", \"%s\", \"%s\")", orgID, groupBackofficeID, userID)
_, err = runSQL(rt, sql)
if err != nil {
rt.Log.Error("insert into rolemember failed", err)
}
return
}
// runSQL creates a transaction per call
func runSQL(rt *env.Runtime, sql string) (id uint64, err error) {
if strings.TrimSpace(sql) == "" {
return 0, nil
}
tx, err := rt.Db.Beginx()
if err != nil {
rt.Log.Error("runSql - failed to get transaction", err)
return
}
result, err := tx.Exec(sql)
_, err = tx.Exec(RebindParams("INSERT INTO dmz_group (c_refid, c_orgid, c_name, c_desc) VALUES (?, ?, ?, ?)", rt.StoreProvider.Type()),
groupBackofficeID, orgID, "Back Office", "Finance and HR people")
if err != nil {
rt.Log.Error("INSERT INTO dmz_group failed", err)
tx.Rollback()
rt.Log.Error("runSql - unable to run sql", err)
return
}
// Join the user groups.
_, err = tx.Exec(RebindParams("INSERT INTO dmz_group_member (c_orgid, c_groupid, c_userid) VALUES (?, ?, ?)", rt.StoreProvider.Type()),
orgID, groupDevID, userID)
if err != nil {
rt.Log.Error("INSERT INTO dmz_group_member failed", err)
tx.Rollback()
return
}
_, err = tx.Exec(RebindParams("INSERT INTO dmz_group_member (c_orgid, c_groupid, c_userid) VALUES (?, ?, ?)", rt.StoreProvider.Type()),
orgID, groupProjectID, userID)
if err != nil {
rt.Log.Error("INSERT INTO dmz_group_member failed", err)
tx.Rollback()
return
}
_, err = tx.Exec(RebindParams("INSERT INTO dmz_group_member (c_orgid, c_groupid, c_userid) VALUES (?, ?, ?)", rt.StoreProvider.Type()),
orgID, groupBackofficeID, userID)
if err != nil {
rt.Log.Error("INSERT INTO dmz_group_member failed", err)
tx.Rollback()
return
}
// Finish up.
if err = tx.Commit(); err != nil {
rt.Log.Error("runSql - unable to commit sql", err)
rt.Log.Error("setup - unable to commit sql", err)
return
}
tempID, _ := result.LastInsertId()
id = uint64(tempID)
return
}

6
core/env/flags.go vendored
View file

@ -80,8 +80,8 @@ func ParseFlags() (f Flags) {
register(&port, "port", false, "http/https port number")
register(&forcePort2SSL, "forcesslport", false, "redirect given http port number to TLS")
register(&siteMode, "offline", false, "set to '1' for OFFLINE mode")
register(&dbType, "dbtype", false, "set to database type mysql|percona|mariadb")
register(&dbConn, "db", true, `'username:password@protocol(hostname:port)/databasename" for example "fred:bloggs@tcp(localhost:3306)/documize"`)
register(&dbType, "dbtype", true, "specify the database provider: mysql|percona|mariadb|postgresql")
register(&dbConn, "db", true, `'database specific connection string for example "user:password@tcp(localhost:3306)/dbname"`)
parse("db")
@ -92,7 +92,7 @@ func ParseFlags() (f Flags) {
f.SiteMode = siteMode
f.SSLCertFile = certFile
f.SSLKeyFile = keyFile
f.DBType = dbType
f.DBType = strings.ToLower(dbType)
return f
}

15
core/env/product.go vendored
View file

@ -18,13 +18,14 @@ import (
// ProdInfo describes a product
type ProdInfo struct {
Edition string
Title string
Version string
Major string
Minor string
Patch string
License License
Edition string
Title string
Version string
Major string
Minor string
Patch string
Revision int
License License
}
// License holds details of product license.

113
core/env/provider.go vendored Normal file
View file

@ -0,0 +1,113 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
// Package env provides runtime, server level setup and configuration
package env
// StoreType represents name of database system
type StoreType string
const (
// StoreTypeMySQL is MySQL
StoreTypeMySQL StoreType = "MySQL"
// StoreTypePercona is Percona
StoreTypePercona StoreType = "Percona"
// StoreTypeMariaDB is MariaDB
StoreTypeMariaDB StoreType = "MariaDB"
// StoreTypePostgreSQL is PostgreSQL
StoreTypePostgreSQL StoreType = "PostgreSQL"
// StoreTypeMSSQL is Microsoft SQL Server
StoreTypeMSSQL StoreType = "MSSQL"
)
// StoreProvider defines a database provider.
type StoreProvider interface {
// Name of provider
Type() StoreType
// TypeVariant returns flavor of database provider.
TypeVariant() StoreType
// SQL driver name used to open DB connection.
DriverName() string
// Database connection string parameters that must be present before connecting to DB.
Params() map[string]string
// Example holds storage provider specific connection string format.
// used in error messages
Example() string
// DatabaseName holds the SQL database name where Documize tables live.
DatabaseName() string
// Make connection string with default parameters.
MakeConnectionString() string
// QueryMeta is how to extract version number, collation, character set from database provider.
QueryMeta() string
// QueryStartLock locks database tables.
// QueryStartLock() string
// QueryFinishLock unlocks database tables.
// QueryFinishLock() string
// QueryInsertProcessID returns database specific query that will
// insert ID of this running process.
// QueryInsertProcessID() string
// QueryInsertProcessID returns database specific query that will
// delete ID of this running process.
// QueryDeleteProcessID() string
// QueryRecordVersionUpgrade returns database specific insert statement
// that records the database version number.
QueryRecordVersionUpgrade(version int) string
// QueryRecordVersionUpgrade returns database specific insert statement
// that records the database version number.
// For use on databases before The Great Schema Migration (v25, MySQL).
QueryRecordVersionUpgradeLegacy(version int) string
// QueryGetDatabaseVersion returns the schema version number.
QueryGetDatabaseVersion() string
// QueryGetDatabaseVersionLegacy returns the schema version number before The Great Schema Migration (v25, MySQL).
QueryGetDatabaseVersionLegacy() string
// QueryTableList returns a list tables in Documize database.
QueryTableList() string
// QueryDateInterval returns provider specific
// interval style date SQL.
QueryDateInterval(days int64) string
// JSONEmpty returns empty SQL JSON object.
// Typically used as 2nd parameter to COALESCE().
JSONEmpty() string
// JSONGetValue returns JSON attribute selection syntax.
// Typically used in SELECT <my_json_field> query.
JSONGetValue(column, attribute string) string
// VerfiyVersion checks to see if actual database meets
// minimum version requirements.
VerfiyVersion(dbVersion string) (versionOK bool, minVerRequired string)
// VerfiyCharacterCollation checks to see if actual database
// has correct character set and collation settings.
VerfiyCharacterCollation(charset, collation string) (charOK bool, requirements string)
}

34
core/env/runtime.go vendored
View file

@ -12,48 +12,38 @@
// Package env provides runtime, server level setup and configuration
package env
import "github.com/jmoiron/sqlx"
import (
"github.com/jmoiron/sqlx"
)
// Runtime provides access to database, logger and other server-level scoped objects.
// Use Context for per-request values.
type Runtime struct {
Flags Flags
Db *sqlx.DB
DbVariant DbVariant
Log Logger
Product ProdInfo
Flags Flags
Db *sqlx.DB
StoreProvider StoreProvider
Log Logger
Product ProdInfo
}
const (
// SiteModeNormal serves app
SiteModeNormal = ""
// SiteModeOffline serves offline.html
SiteModeOffline = "1"
// SiteModeSetup tells Ember to serve setup route
SiteModeSetup = "2"
// SiteModeBadDB redirects to db-error.html page
SiteModeBadDB = "3"
)
// DbVariant details SQL database variant
type DbVariant string
const (
// DbVariantMySQL is MySQL
DbVariantMySQL DbVariant = "MySQL"
// DBVariantPercona is Percona
DBVariantPercona DbVariant = "Percona"
// DBVariantMariaDB is MariaDB
DBVariantMariaDB DbVariant = "MariaDB"
// DBVariantMSSQL is Microsoft SQL Server
DBVariantMSSQL DbVariant = "MSSQL"
// DBVariantPostgreSQL is PostgreSQL
DBVariantPostgreSQL DbVariant = "PostgreSQL"
)
const (
// CommunityEdition is AGPL product variant
CommunityEdition = "Community"
// EnterpriseEdition is commercial licensed product variant
EnterpriseEdition = "Enterprise"
)

View file

@ -9,13 +9,29 @@
//
// https://documize.com
// Package uniqueid provides utility functions specific to the http-end-point component of Documize.
// Package uniqueid provides randomly generated string 16 characters long.
package uniqueid
import "github.com/documize/community/core/uniqueid/xid"
import (
"github.com/documize/community/core/uniqueid/xid"
"github.com/documize/community/core/uniqueid/xid16"
)
// Generate creates a randomly generated string suitable for use as part of an URI.
// It returns a string that is always 16 characters long.
func Generate() string {
return xid.New().String()
}
// Generate16 creates a randomly generated 16 character length string suitable for use as part of an URI.
// It returns a string that is always 16 characters long.
func Generate16() string {
return xid16.New().String()
}
// beqassjmvbajrivsc0eg
// beqat1bmvbajrivsc0f0
// beqat1bmvbajrivsc1ag
// beqat1bmvbajrivsc1g0
// beqat1bmvbajrivsc1ug

View file

@ -23,7 +23,7 @@ var m = make(map[string]struct{})
var mx sync.Mutex
func mm(t *testing.T, id string) {
if len(id) != 16 {
if len(id) != 20 {
t.Errorf("len(id)=%d", len(id))
}
mx.Lock()

19
core/uniqueid/xid/LICENSE Executable file
View file

@ -0,0 +1,19 @@
Copyright (c) 2015 Olivier Poitrey <rs@dailymotion.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

112
core/uniqueid/xid/README.md Executable file
View file

@ -0,0 +1,112 @@
# Globally Unique ID Generator
REPO: https://github.com/rs/xid
Package xid is a globally unique id generator library, ready to be used safely directly in your server code.
Xid is using Mongo Object ID algorithm to generate globally unique ids with a different serialization (base64) to make it shorter when transported as a string:
https://docs.mongodb.org/manual/reference/object-id/
- 4-byte value representing the seconds since the Unix epoch,
- 3-byte machine identifier,
- 2-byte process id, and
- 3-byte counter, starting with a random value.
The binary representation of the id is compatible with Mongo 12 bytes Object IDs.
The string representation is using base32 hex (w/o padding) for better space efficiency
when stored in that form (20 bytes). The hex variant of base32 is used to retain the
sortable property of the id.
Xid doesn't use base64 because case sensitivity and the 2 non alphanum chars may be an
issue when transported as a string between various systems. Base36 wasn't retained either
because 1/ it's not standard 2/ the resulting size is not predictable (not bit aligned)
and 3/ it would not remain sortable. To validate a base32 `xid`, expect a 20 chars long,
all lowercase sequence of `a` to `v` letters and `0` to `9` numbers (`[0-9a-v]{20}`).
UUIDs are 16 bytes (128 bits) and 36 chars as string representation. Twitter Snowflake
ids are 8 bytes (64 bits) but require machine/data-center configuration and/or central
generator servers. xid stands in between with 12 bytes (96 bits) and a more compact
URL-safe string representation (20 chars). No configuration or central generator server
is required so it can be used directly in server's code.
| Name | Binary Size | String Size | Features
|-------------|-------------|----------------|----------------
| [UUID] | 16 bytes | 36 chars | configuration free, not sortable
| [shortuuid] | 16 bytes | 22 chars | configuration free, not sortable
| [Snowflake] | 8 bytes | up to 20 chars | needs machin/DC configuration, needs central server, sortable
| [MongoID] | 12 bytes | 24 chars | configuration free, sortable
| xid | 12 bytes | 20 chars | configuration free, sortable
[UUID]: https://en.wikipedia.org/wiki/Universally_unique_identifier
[shortuuid]: https://github.com/stochastic-technologies/shortuuid
[Snowflake]: https://blog.twitter.com/2010/announcing-snowflake
[MongoID]: https://docs.mongodb.org/manual/reference/object-id/
Features:
- Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake
- Base32 hex encoded by default (20 chars when transported as printable string, still sortable)
- Non configured, you don't need set a unique machine and/or data center id
- K-ordered
- Embedded time with 1 second precision
- Unicity guaranteed for 16,777,216 (24 bits) unique ids per second and per host/process
- Lock-free (i.e.: unlike UUIDv1 and v2)
Best used with [zerolog](https://github.com/rs/zerolog)'s
[RequestIDHandler](https://godoc.org/github.com/rs/zerolog/hlog#RequestIDHandler).
Notes:
- Xid is dependent on the system time, a monotonic counter and so is not cryptographically secure. If unpredictability of IDs is important, you should not use Xids. It is worth noting that most of the other UUID like implementations are also not cryptographically secure. You shoud use libraries that rely on cryptographically secure sources (like /dev/urandom on unix, crypto/rand in golang), if you want a truly random ID generator.
References:
- http://www.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems
- https://en.wikipedia.org/wiki/Universally_unique_identifier
- https://blog.twitter.com/2010/announcing-snowflake
- Python port by [Graham Abbott](https://github.com/graham): https://github.com/graham/python_xid
- Scala port by [Egor Kolotaev](https://github.com/kolotaev): https://github.com/kolotaev/ride
## Install
go get github.com/rs/xid
## Usage
```go
guid := xid.New()
println(guid.String())
// Output: 9m4e2mr0ui3e8a215n4g
```
Get `xid` embedded info:
```go
guid.Machine()
guid.Pid()
guid.Time()
guid.Counter()
```
## Benchmark
Benchmark against Go [Maxim Bublis](https://github.com/satori)'s [UUID](https://github.com/satori/go.uuid).
```
BenchmarkXID 20000000 91.1 ns/op 32 B/op 1 allocs/op
BenchmarkXID-2 20000000 55.9 ns/op 32 B/op 1 allocs/op
BenchmarkXID-4 50000000 32.3 ns/op 32 B/op 1 allocs/op
BenchmarkUUIDv1 10000000 204 ns/op 48 B/op 1 allocs/op
BenchmarkUUIDv1-2 10000000 160 ns/op 48 B/op 1 allocs/op
BenchmarkUUIDv1-4 10000000 195 ns/op 48 B/op 1 allocs/op
BenchmarkUUIDv4 1000000 1503 ns/op 64 B/op 2 allocs/op
BenchmarkUUIDv4-2 1000000 1427 ns/op 64 B/op 2 allocs/op
BenchmarkUUIDv4-4 1000000 1452 ns/op 64 B/op 2 allocs/op
```
Note: UUIDv1 requires a global lock, hence the performence degrading as we add more CPUs.
## Licenses
All source code is licensed under the [MIT License](https://raw.github.com/rs/xid/master/LICENSE).

View file

@ -0,0 +1,9 @@
// +build darwin
package xid
import "syscall"
func readPlatformMachineID() (string, error) {
return syscall.Sysctl("kern.uuid")
}

View file

@ -0,0 +1,9 @@
// +build !darwin,!linux,!freebsd,!windows
package xid
import "errors"
func readPlatformMachineID() (string, error) {
return "", errors.New("not implemented")
}

View file

@ -0,0 +1,9 @@
// +build freebsd
package xid
import "syscall"
func readPlatformMachineID() (string, error) {
return syscall.Sysctl("kern.hostuuid")
}

View file

@ -0,0 +1,10 @@
// +build linux
package xid
import "io/ioutil"
func readPlatformMachineID() (string, error) {
b, err := ioutil.ReadFile("/sys/class/dmi/id/product_uuid")
return string(b), err
}

View file

@ -0,0 +1,38 @@
// +build windows
package xid
import (
"fmt"
"syscall"
"unsafe"
)
func readPlatformMachineID() (string, error) {
// source: https://github.com/shirou/gopsutil/blob/master/host/host_syscall.go
var h syscall.Handle
err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, syscall.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, syscall.KEY_READ|syscall.KEY_WOW64_64KEY, &h)
if err != nil {
return "", err
}
defer syscall.RegCloseKey(h)
const syscallRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16
const uuidLen = 36
var regBuf [syscallRegBufLen]uint16
bufLen := uint32(syscallRegBufLen)
var valType uint32
err = syscall.RegQueryValueEx(h, syscall.StringToUTF16Ptr(`MachineGuid`), nil, &valType, (*byte)(unsafe.Pointer(&regBuf[0])), &bufLen)
if err != nil {
return "", err
}
hostID := syscall.UTF16ToString(regBuf[:])
hostIDLen := len(hostID)
if hostIDLen != uuidLen {
return "", fmt.Errorf("HostID incorrect: %q\n", hostID)
}
return hostID, nil
}

365
core/uniqueid/xid/id.go Executable file
View file

@ -0,0 +1,365 @@
// Package xid is a globally unique id generator suited for web scale
//
// Xid is using Mongo Object ID algorithm to generate globally unique ids:
// https://docs.mongodb.org/manual/reference/object-id/
//
// - 4-byte value representing the seconds since the Unix epoch,
// - 3-byte machine identifier,
// - 2-byte process id, and
// - 3-byte counter, starting with a random value.
//
// The binary representation of the id is compatible with Mongo 12 bytes Object IDs.
// The string representation is using base32 hex (w/o padding) for better space efficiency
// when stored in that form (20 bytes). The hex variant of base32 is used to retain the
// sortable property of the id.
//
// Xid doesn't use base64 because case sensitivity and the 2 non alphanum chars may be an
// issue when transported as a string between various systems. Base36 wasn't retained either
// because 1/ it's not standard 2/ the resulting size is not predictable (not bit aligned)
// and 3/ it would not remain sortable. To validate a base32 `xid`, expect a 20 chars long,
// all lowercase sequence of `a` to `v` letters and `0` to `9` numbers (`[0-9a-v]{20}`).
//
// UUID is 16 bytes (128 bits), snowflake is 8 bytes (64 bits), xid stands in between
// with 12 bytes with a more compact string representation ready for the web and no
// required configuration or central generation server.
//
// Features:
//
// - Size: 12 bytes (96 bits), smaller than UUID, larger than snowflake
// - Base32 hex encoded by default (16 bytes storage when transported as printable string)
// - Non configured, you don't need set a unique machine and/or data center id
// - K-ordered
// - Embedded time with 1 second precision
// - Unicity guaranteed for 16,777,216 (24 bits) unique ids per second and per host/process
//
// Best used with xlog's RequestIDHandler (https://godoc.org/github.com/rs/xlog#RequestIDHandler).
//
// References:
//
// - http://www.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems
// - https://en.wikipedia.org/wiki/Universally_unique_identifier
// - https://blog.twitter.com/2010/announcing-snowflake
package xid
import (
"bytes"
"crypto/md5"
"crypto/rand"
"database/sql/driver"
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"io/ioutil"
"os"
"sort"
"sync/atomic"
"time"
)
// Code inspired from mgo/bson ObjectId
// ID represents a unique request id
type ID [rawLen]byte
const (
encodedLen = 20 // string encoded len
rawLen = 12 // binary raw len
// encoding stores a custom version of the base32 encoding with lower case
// letters.
encoding = "0123456789abcdefghijklmnopqrstuv"
)
var (
// ErrInvalidID is returned when trying to unmarshal an invalid ID
ErrInvalidID = errors.New("xid: invalid ID")
// objectIDCounter is atomically incremented when generating a new ObjectId
// using NewObjectId() function. It's used as a counter part of an id.
// This id is initialized with a random value.
objectIDCounter = randInt()
// machineId stores machine id generated once and used in subsequent calls
// to NewObjectId function.
machineID = readMachineID()
// pid stores the current process id
pid = os.Getpid()
nilID ID
// dec is the decoding map for base32 encoding
dec [256]byte
)
func init() {
for i := 0; i < len(dec); i++ {
dec[i] = 0xFF
}
for i := 0; i < len(encoding); i++ {
dec[encoding[i]] = byte(i)
}
// If /proc/self/cpuset exists and is not /, we can assume that we are in a
// form of container and use the content of cpuset xor-ed with the PID in
// order get a reasonable machine global unique PID.
b, err := ioutil.ReadFile("/proc/self/cpuset")
if err == nil && len(b) > 1 {
pid ^= int(crc32.ChecksumIEEE(b))
}
}
// readMachineId generates machine id and puts it into the machineId global
// variable. If this function fails to get the hostname, it will cause
// a runtime error.
func readMachineID() []byte {
id := make([]byte, 3)
hid, err := readPlatformMachineID()
if err != nil || len(hid) == 0 {
hid, err = os.Hostname()
}
if err == nil && len(hid) != 0 {
hw := md5.New()
hw.Write([]byte(hid))
copy(id, hw.Sum(nil))
} else {
// Fallback to rand number if machine id can't be gathered
if _, randErr := rand.Reader.Read(id); randErr != nil {
panic(fmt.Errorf("xid: cannot get hostname nor generate a random number: %v; %v", err, randErr))
}
}
return id
}
// randInt generates a random uint32
func randInt() uint32 {
b := make([]byte, 3)
if _, err := rand.Reader.Read(b); err != nil {
panic(fmt.Errorf("xid: cannot generate random number: %v;", err))
}
return uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2])
}
// New generates a globally unique ID
func New() ID {
return NewWithTime(time.Now())
}
// NewWithTime generates a globally unique ID with the passed in time
func NewWithTime(t time.Time) ID {
var id ID
// Timestamp, 4 bytes, big endian
binary.BigEndian.PutUint32(id[:], uint32(t.Unix()))
// Machine, first 3 bytes of md5(hostname)
id[4] = machineID[0]
id[5] = machineID[1]
id[6] = machineID[2]
// Pid, 2 bytes, specs don't specify endianness, but we use big endian.
id[7] = byte(pid >> 8)
id[8] = byte(pid)
// Increment, 3 bytes, big endian
i := atomic.AddUint32(&objectIDCounter, 1)
id[9] = byte(i >> 16)
id[10] = byte(i >> 8)
id[11] = byte(i)
return id
}
// FromString reads an ID from its string representation
func FromString(id string) (ID, error) {
i := &ID{}
err := i.UnmarshalText([]byte(id))
return *i, err
}
// String returns a base32 hex lowercased with no padding representation of the id (char set is 0-9, a-v).
func (id ID) String() string {
text := make([]byte, encodedLen)
encode(text, id[:])
return string(text)
}
// MarshalText implements encoding/text TextMarshaler interface
func (id ID) MarshalText() ([]byte, error) {
text := make([]byte, encodedLen)
encode(text, id[:])
return text, nil
}
// MarshalJSON implements encoding/json Marshaler interface
func (id ID) MarshalJSON() ([]byte, error) {
if id.IsNil() {
return []byte("null"), nil
}
text, err := id.MarshalText()
return []byte(`"` + string(text) + `"`), err
}
// encode by unrolling the stdlib base32 algorithm + removing all safe checks
func encode(dst, id []byte) {
dst[0] = encoding[id[0]>>3]
dst[1] = encoding[(id[1]>>6)&0x1F|(id[0]<<2)&0x1F]
dst[2] = encoding[(id[1]>>1)&0x1F]
dst[3] = encoding[(id[2]>>4)&0x1F|(id[1]<<4)&0x1F]
dst[4] = encoding[id[3]>>7|(id[2]<<1)&0x1F]
dst[5] = encoding[(id[3]>>2)&0x1F]
dst[6] = encoding[id[4]>>5|(id[3]<<3)&0x1F]
dst[7] = encoding[id[4]&0x1F]
dst[8] = encoding[id[5]>>3]
dst[9] = encoding[(id[6]>>6)&0x1F|(id[5]<<2)&0x1F]
dst[10] = encoding[(id[6]>>1)&0x1F]
dst[11] = encoding[(id[7]>>4)&0x1F|(id[6]<<4)&0x1F]
dst[12] = encoding[id[8]>>7|(id[7]<<1)&0x1F]
dst[13] = encoding[(id[8]>>2)&0x1F]
dst[14] = encoding[(id[9]>>5)|(id[8]<<3)&0x1F]
dst[15] = encoding[id[9]&0x1F]
dst[16] = encoding[id[10]>>3]
dst[17] = encoding[(id[11]>>6)&0x1F|(id[10]<<2)&0x1F]
dst[18] = encoding[(id[11]>>1)&0x1F]
dst[19] = encoding[(id[11]<<4)&0x1F]
}
// UnmarshalText implements encoding/text TextUnmarshaler interface
func (id *ID) UnmarshalText(text []byte) error {
if len(text) != encodedLen {
return ErrInvalidID
}
for _, c := range text {
if dec[c] == 0xFF {
return ErrInvalidID
}
}
decode(id, text)
return nil
}
// UnmarshalJSON implements encoding/json Unmarshaler interface
func (id *ID) UnmarshalJSON(b []byte) error {
s := string(b)
if s == "null" {
*id = nilID
return nil
}
return id.UnmarshalText(b[1 : len(b)-1])
}
// decode by unrolling the stdlib base32 algorithm + removing all safe checks
func decode(id *ID, src []byte) {
id[0] = dec[src[0]]<<3 | dec[src[1]]>>2
id[1] = dec[src[1]]<<6 | dec[src[2]]<<1 | dec[src[3]]>>4
id[2] = dec[src[3]]<<4 | dec[src[4]]>>1
id[3] = dec[src[4]]<<7 | dec[src[5]]<<2 | dec[src[6]]>>3
id[4] = dec[src[6]]<<5 | dec[src[7]]
id[5] = dec[src[8]]<<3 | dec[src[9]]>>2
id[6] = dec[src[9]]<<6 | dec[src[10]]<<1 | dec[src[11]]>>4
id[7] = dec[src[11]]<<4 | dec[src[12]]>>1
id[8] = dec[src[12]]<<7 | dec[src[13]]<<2 | dec[src[14]]>>3
id[9] = dec[src[14]]<<5 | dec[src[15]]
id[10] = dec[src[16]]<<3 | dec[src[17]]>>2
id[11] = dec[src[17]]<<6 | dec[src[18]]<<1 | dec[src[19]]>>4
}
// Time returns the timestamp part of the id.
// It's a runtime error to call this method with an invalid id.
func (id ID) Time() time.Time {
// First 4 bytes of ObjectId is 32-bit big-endian seconds from epoch.
secs := int64(binary.BigEndian.Uint32(id[0:4]))
return time.Unix(secs, 0)
}
// Machine returns the 3-byte machine id part of the id.
// It's a runtime error to call this method with an invalid id.
func (id ID) Machine() []byte {
return id[4:7]
}
// Pid returns the process id part of the id.
// It's a runtime error to call this method with an invalid id.
func (id ID) Pid() uint16 {
return binary.BigEndian.Uint16(id[7:9])
}
// Counter returns the incrementing value part of the id.
// It's a runtime error to call this method with an invalid id.
func (id ID) Counter() int32 {
b := id[9:12]
// Counter is stored as big-endian 3-byte value
return int32(uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2]))
}
// Value implements the driver.Valuer interface.
func (id ID) Value() (driver.Value, error) {
if id.IsNil() {
return nil, nil
}
b, err := id.MarshalText()
return string(b), err
}
// Scan implements the sql.Scanner interface.
func (id *ID) Scan(value interface{}) (err error) {
switch val := value.(type) {
case string:
return id.UnmarshalText([]byte(val))
case []byte:
return id.UnmarshalText(val)
case nil:
*id = nilID
return nil
default:
return fmt.Errorf("xid: scanning unsupported type: %T", value)
}
}
// IsNil Returns true if this is a "nil" ID
func (id ID) IsNil() bool {
return id == nilID
}
// NilID returns a zero value for `xid.ID`.
func NilID() ID {
return nilID
}
// Bytes returns the byte array representation of `ID`
func (id ID) Bytes() []byte {
return id[:]
}
// FromBytes convert the byte array representation of `ID` back to `ID`
func FromBytes(b []byte) (ID, error) {
var id ID
if len(b) != rawLen {
return id, ErrInvalidID
}
copy(id[:], b)
return id, nil
}
// Compare returns an integer comparing two IDs. It behaves just like `bytes.Compare`.
// The result will be 0 if two IDs are identical, -1 if current id is less than the other one,
// and 1 if current id is greater than the other.
func (id ID) Compare(other ID) int {
return bytes.Compare(id[:], other[:])
}
type sorter []ID
func (s sorter) Len() int {
return len(s)
}
func (s sorter) Less(i, j int) bool {
return s[i].Compare(s[j]) < 0
}
func (s sorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Sort sorts an array of IDs inplace.
// It works by wrapping `[]ID` and use `sort.Sort`.
func Sort(ids []ID) {
sort.Sort(sorter(ids))
}

396
core/uniqueid/xid/id_test.go Executable file
View file

@ -0,0 +1,396 @@
package xid
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"
"testing"
"time"
)
type IDParts struct {
id ID
timestamp int64
machine []byte
pid uint16
counter int32
}
var IDs = []IDParts{
IDParts{
ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9},
1300816219,
[]byte{0x60, 0xf4, 0x86},
0xe428,
4271561,
},
IDParts{
ID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
0,
[]byte{0x00, 0x00, 0x00},
0x0000,
0,
},
IDParts{
ID{0x00, 0x00, 0x00, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0x00, 0x00, 0x01},
0,
[]byte{0xaa, 0xbb, 0xcc},
0xddee,
1,
},
}
func TestIDPartsExtraction(t *testing.T) {
for i, v := range IDs {
t.Run(fmt.Sprintf("Test%d", i), func(t *testing.T) {
if got, want := v.id.Time(), time.Unix(v.timestamp, 0); got != want {
t.Errorf("Time() = %v, want %v", got, want)
}
if got, want := v.id.Machine(), v.machine; !bytes.Equal(got, want) {
t.Errorf("Machine() = %v, want %v", got, want)
}
if got, want := v.id.Pid(), v.pid; got != want {
t.Errorf("Pid() = %v, want %v", got, want)
}
if got, want := v.id.Counter(), v.counter; got != want {
t.Errorf("Counter() = %v, want %v", got, want)
}
})
}
}
func TestNew(t *testing.T) {
// Generate 10 ids
ids := make([]ID, 10)
for i := 0; i < 10; i++ {
ids[i] = New()
}
for i := 1; i < 10; i++ {
prevID := ids[i-1]
id := ids[i]
// Test for uniqueness among all other 9 generated ids
for j, tid := range ids {
if j != i {
if id.Compare(tid) == 0 {
t.Errorf("generated ID is not unique (%d/%d)", i, j)
}
}
}
// Check that timestamp was incremented and is within 30 seconds of the previous one
secs := id.Time().Sub(prevID.Time()).Seconds()
if secs < 0 || secs > 30 {
t.Error("wrong timestamp in generated ID")
}
// Check that machine ids are the same
if !bytes.Equal(id.Machine(), prevID.Machine()) {
t.Error("machine ID not equal")
}
// Check that pids are the same
if id.Pid() != prevID.Pid() {
t.Error("pid not equal")
}
// Test for proper increment
if got, want := int(id.Counter()-prevID.Counter()), 1; got != want {
t.Errorf("wrong increment in generated ID, delta=%v, want %v", got, want)
}
}
}
func TestIDString(t *testing.T) {
id := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9}
if got, want := id.String(), "9m4e2mr0ui3e8a215n4g"; got != want {
t.Errorf("String() = %v, want %v", got, want)
}
}
func TestFromString(t *testing.T) {
got, err := FromString("9m4e2mr0ui3e8a215n4g")
if err != nil {
t.Fatal(err)
}
want := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9}
if got != want {
t.Errorf("FromString() = %v, want %v", got, want)
}
}
func TestFromStringInvalid(t *testing.T) {
_, err := FromString("invalid")
if err != ErrInvalidID {
t.Errorf("FromString(invalid) err=%v, want %v", err, ErrInvalidID)
}
}
type jsonType struct {
ID *ID
Str string
}
func TestIDJSONMarshaling(t *testing.T) {
id := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9}
v := jsonType{ID: &id, Str: "test"}
data, err := json.Marshal(&v)
if err != nil {
t.Fatal(err)
}
if got, want := string(data), `{"ID":"9m4e2mr0ui3e8a215n4g","Str":"test"}`; got != want {
t.Errorf("json.Marshal() = %v, want %v", got, want)
}
}
func TestIDJSONUnmarshaling(t *testing.T) {
data := []byte(`{"ID":"9m4e2mr0ui3e8a215n4g","Str":"test"}`)
v := jsonType{}
err := json.Unmarshal(data, &v)
if err != nil {
t.Fatal(err)
}
want := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9}
if got := *v.ID; got.Compare(want) != 0 {
t.Errorf("json.Unmarshal() = %v, want %v", got, want)
}
}
func TestIDJSONUnmarshalingError(t *testing.T) {
v := jsonType{}
err := json.Unmarshal([]byte(`{"ID":"9M4E2MR0UI3E8A215N4G"}`), &v)
if err != ErrInvalidID {
t.Errorf("json.Unmarshal() err=%v, want %v", err, ErrInvalidID)
}
err = json.Unmarshal([]byte(`{"ID":"TYjhW2D0huQoQS"}`), &v)
if err != ErrInvalidID {
t.Errorf("json.Unmarshal() err=%v, want %v", err, ErrInvalidID)
}
err = json.Unmarshal([]byte(`{"ID":"TYjhW2D0huQoQS3kdk"}`), &v)
if err != ErrInvalidID {
t.Errorf("json.Unmarshal() err=%v, want %v", err, ErrInvalidID)
}
}
func TestIDDriverValue(t *testing.T) {
id := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9}
got, err := id.Value()
if err != nil {
t.Fatal(err)
}
if want := "9m4e2mr0ui3e8a215n4g"; got != want {
t.Errorf("Value() = %v, want %v", got, want)
}
}
func TestIDDriverScan(t *testing.T) {
got := ID{}
err := got.Scan("9m4e2mr0ui3e8a215n4g")
if err != nil {
t.Fatal(err)
}
want := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9}
if got.Compare(want) != 0 {
t.Errorf("Scan() = %v, want %v", got, want)
}
}
func TestIDDriverScanError(t *testing.T) {
id := ID{}
if got, want := id.Scan(0), errors.New("xid: scanning unsupported type: int"); !reflect.DeepEqual(got, want) {
t.Errorf("Scan() err=%v, want %v", got, want)
}
if got, want := id.Scan("0"), ErrInvalidID; got != want {
t.Errorf("Scan() err=%v, want %v", got, want)
}
}
func TestIDDriverScanByteFromDatabase(t *testing.T) {
got := ID{}
bs := []byte("9m4e2mr0ui3e8a215n4g")
err := got.Scan(bs)
if err != nil {
t.Fatal(err)
}
want := ID{0x4d, 0x88, 0xe1, 0x5b, 0x60, 0xf4, 0x86, 0xe4, 0x28, 0x41, 0x2d, 0xc9}
if got.Compare(want) != 0 {
t.Errorf("Scan() = %v, want %v", got, want)
}
}
func BenchmarkNew(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = New()
}
})
}
func BenchmarkNewString(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = New().String()
}
})
}
func BenchmarkFromString(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, _ = FromString("9m4e2mr0ui3e8a215n4g")
}
})
}
// func BenchmarkUUIDv1(b *testing.B) {
// b.RunParallel(func(pb *testing.PB) {
// for pb.Next() {
// _ = uuid.NewV1().String()
// }
// })
// }
// func BenchmarkUUIDv4(b *testing.B) {
// b.RunParallel(func(pb *testing.PB) {
// for pb.Next() {
// _ = uuid.NewV4().String()
// }
// })
// }
func TestID_IsNil(t *testing.T) {
tests := []struct {
name string
id ID
want bool
}{
{
name: "ID not nil",
id: New(),
want: false,
},
{
name: "Nil ID",
id: ID{},
want: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
if got, want := tt.id.IsNil(), tt.want; got != want {
t.Errorf("IsNil() = %v, want %v", got, want)
}
})
}
}
func TestNilID(t *testing.T) {
got := ID{}
if want := NilID(); !reflect.DeepEqual(got, want) {
t.Error("NilID() not equal ID{}")
}
}
func TestNilID_IsNil(t *testing.T) {
if !NilID().IsNil() {
t.Error("NilID().IsNil() is not true")
}
}
func TestFromBytes_Invariant(t *testing.T) {
want := New()
got, err := FromBytes(want.Bytes())
if err != nil {
t.Fatal(err)
}
if got.Compare(want) != 0 {
t.Error("FromBytes(id.Bytes()) != id")
}
}
func TestFromBytes_InvalidBytes(t *testing.T) {
cases := []struct {
length int
shouldFail bool
}{
{11, true},
{12, false},
{13, true},
}
for _, c := range cases {
b := make([]byte, c.length, c.length)
_, err := FromBytes(b)
if got, want := err != nil, c.shouldFail; got != want {
t.Errorf("FromBytes() error got %v, want %v", got, want)
}
}
}
func TestID_Compare(t *testing.T) {
pairs := []struct {
left ID
right ID
expected int
}{
{IDs[1].id, IDs[0].id, -1},
{ID{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, IDs[2].id, -1},
{IDs[0].id, IDs[0].id, 0},
}
for _, p := range pairs {
if p.expected != p.left.Compare(p.right) {
t.Errorf("%s Compare to %s should return %d", p.left, p.right, p.expected)
}
if -1*p.expected != p.right.Compare(p.left) {
t.Errorf("%s Compare to %s should return %d", p.right, p.left, -1*p.expected)
}
}
}
var IDList = []ID{IDs[0].id, IDs[1].id, IDs[2].id}
func TestSorter_Len(t *testing.T) {
if got, want := sorter([]ID{}).Len(), 0; got != want {
t.Errorf("Len() %v, want %v", got, want)
}
if got, want := sorter(IDList).Len(), 3; got != want {
t.Errorf("Len() %v, want %v", got, want)
}
}
func TestSorter_Less(t *testing.T) {
sorter := sorter(IDList)
if !sorter.Less(1, 0) {
t.Errorf("Less(1, 0) not true")
}
if sorter.Less(2, 1) {
t.Errorf("Less(2, 1) true")
}
if sorter.Less(0, 0) {
t.Errorf("Less(0, 0) true")
}
}
func TestSorter_Swap(t *testing.T) {
ids := make([]ID, 0)
ids = append(ids, IDList...)
sorter := sorter(ids)
sorter.Swap(0, 1)
if got, want := ids[0], IDList[1]; !reflect.DeepEqual(got, want) {
t.Error("ids[0] != IDList[1]")
}
if got, want := ids[1], IDList[0]; !reflect.DeepEqual(got, want) {
t.Error("ids[1] != IDList[0]")
}
sorter.Swap(2, 2)
if got, want := ids[2], IDList[2]; !reflect.DeepEqual(got, want) {
t.Error("ids[2], IDList[2]")
}
}
func TestSort(t *testing.T) {
ids := make([]ID, 0)
ids = append(ids, IDList...)
Sort(ids)
if got, want := ids, []ID{IDList[1], IDList[2], IDList[0]}; !reflect.DeepEqual(got, want) {
t.Fail()
}
}

View file

@ -32,7 +32,7 @@
// - http://www.slideshare.net/davegardnerisme/unique-id-generation-in-distributed-systems
// - https://en.wikipedia.org/wiki/Universally_unique_identifier
// - https://blog.twitter.com/2010/announcing-snowflake
package xid
package xid16
import (
"crypto/md5"

View file

@ -1,148 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package mysql
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/account"
"github.com/pkg/errors"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
}
// Add inserts the given record into the datbase account table.
func (s Scope) Add(ctx domain.RequestContext, account account.Account) (err error) {
account.Created = time.Now().UTC()
account.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec("INSERT INTO account (refid, orgid, userid, `admin`, editor, users, analytics, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
account.RefID, account.OrgID, account.UserID, account.Admin, account.Editor, account.Users, account.Analytics, account.Active, account.Created, account.Revised)
if err != nil {
err = errors.Wrap(err, "unable to execute insert for account")
}
return
}
// GetUserAccount returns the database account record corresponding to the given userID, using the client's current organizaion.
func (s Scope) GetUserAccount(ctx domain.RequestContext, userID string) (account account.Account, err error) {
err = s.Runtime.Db.Get(&account, `
SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.users, a.analytics, a.active, a.created, a.revised,
b.company, b.title, b.message, b.domain
FROM account a, organization b
WHERE b.refid=a.orgid AND a.orgid=? AND a.userid=?`, ctx.OrgID, userID)
if err != sql.ErrNoRows && err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute select for account by user %s", userID))
}
return
}
// GetUserAccounts returns a slice of database account records, for all organizations that the userID is a member of, in organization title order.
func (s Scope) GetUserAccounts(ctx domain.RequestContext, userID string) (t []account.Account, err error) {
err = s.Runtime.Db.Select(&t, `
SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.users, a.analytics, a.active, a.created, a.revised,
b.company, b.title, b.message, b.domain
FROM account a, organization b
WHERE a.userid=? AND a.orgid=b.refid AND a.active=1 ORDER BY b.title`, userID)
if err != sql.ErrNoRows && err != nil {
err = errors.Wrap(err, fmt.Sprintf("Unable to execute select account for user %s", userID))
}
return
}
// GetAccountsByOrg returns a slice of database account records, for all users in the client's organization.
func (s Scope) GetAccountsByOrg(ctx domain.RequestContext) (t []account.Account, err error) {
err = s.Runtime.Db.Select(&t,
`SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.users, a.analytics, a.active, a.created, a.revised,
b.company, b.title, b.message, b.domain
FROM account a, organization b
WHERE a.orgid=b.refid AND a.orgid=? AND a.active=1`, ctx.OrgID)
if err != sql.ErrNoRows && err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute select account for org %s", ctx.OrgID))
}
return
}
// CountOrgAccounts returns the numnber of active user accounts for specified organization.
func (s Scope) CountOrgAccounts(ctx domain.RequestContext) (c int) {
row := s.Runtime.Db.QueryRow("SELECT count(*) FROM account WHERE orgid=? AND active=1", ctx.OrgID)
err := row.Scan(&c)
if err == sql.ErrNoRows {
return 0
}
if err != nil {
err = errors.Wrap(err, "count org accounts")
return 0
}
return
}
// UpdateAccount updates the database record for the given account to the given values.
func (s Scope) UpdateAccount(ctx domain.RequestContext, account account.Account) (err error) {
account.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec("UPDATE account SET userid=:userid, `admin`=:admin, editor=:editor, users=:users, analytics=:analytics, active=:active, revised=:revised WHERE orgid=:orgid AND refid=:refid", &account)
if err != sql.ErrNoRows && err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute update for account %s", account.RefID))
}
return
}
// HasOrgAccount returns if the given orgID has valid userID.
func (s Scope) HasOrgAccount(ctx domain.RequestContext, orgID, userID string) bool {
row := s.Runtime.Db.QueryRow("SELECT count(*) FROM account WHERE orgid=? and userid=?", orgID, userID)
var count int
err := row.Scan(&count)
if err == sql.ErrNoRows {
return false
}
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "HasOrgAccount")
return false
}
if count == 0 {
return false
}
return true
}
// DeleteAccount deletes the database record in the account table for user ID.
func (s Scope) DeleteAccount(ctx domain.RequestContext, ID string) (rows int64, err error) {
b := mysql.BaseQuery{}
return b.DeleteConstrained(ctx.Transaction, "account", ctx.OrgID, ID)
}

157
domain/account/store.go Normal file
View file

@ -0,0 +1,157 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package account
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/account"
"github.com/pkg/errors"
)
// Store provides data access to account information.
type Store struct {
store.Context
store.AccountStorer
}
// Add inserts the given record into the datbase account table.
func (s Store) Add(ctx domain.RequestContext, account account.Account) (err error) {
account.Created = time.Now().UTC()
account.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_user_account (c_refid, c_orgid, c_userid, c_admin, c_editor, c_users, c_analytics, c_active, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
account.RefID, account.OrgID, account.UserID, account.Admin, account.Editor, account.Users, account.Analytics, account.Active, account.Created, account.Revised)
if err != nil {
err = errors.Wrap(err, "unable to execute insert for account")
}
return
}
// GetUserAccount returns the database account record corresponding to the given userID, using the client's current organizaion.
func (s Store) GetUserAccount(ctx domain.RequestContext, userID string) (account account.Account, err error) {
err = s.Runtime.Db.Get(&account, s.Bind(`
SELECT a.id, a.c_refid AS refid, a.c_orgid AS orgid, a.c_userid AS userid,
a.c_editor AS editor, a.c_admin AS admin, a.c_users AS users, a.c_analytics AS analytics,
a.c_active AS active, a.c_created AS created, a.c_revised AS revised,
b.c_company AS company, b.c_title AS title, b.c_message AS message, b.c_domain as domain
FROM dmz_user_account a, dmz_org b
WHERE b.c_refid=a.c_orgid AND a.c_orgid=? AND a.c_userid=?`),
ctx.OrgID, userID)
if err != sql.ErrNoRows && err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute select for account by user %s", userID))
}
return
}
// GetUserAccounts returns a slice of database account records, for all organizations that the userID is a member of, in organization title order.
func (s Store) GetUserAccounts(ctx domain.RequestContext, userID string) (t []account.Account, err error) {
err = s.Runtime.Db.Select(&t, s.Bind(`
SELECT a.id, a.c_refid AS refid, a.c_orgid AS orgid, a.c_userid AS userid,
a.c_editor AS editor, a.c_admin AS admin, a.c_users AS users, a.c_analytics AS analytics,
a.c_active AS active, a.c_created AS created, a.c_revised AS revised,
b.c_company AS company, b.c_title AS title, b.c_message AS message, b.c_domain as domain
FROM dmz_user_account a, dmz_org b
WHERE a.c_userid=? AND a.c_orgid=b.c_refid AND a.c_active=true
ORDER BY b.c_title`),
userID)
if err != sql.ErrNoRows && err != nil {
err = errors.Wrap(err, fmt.Sprintf("Unable to execute select account for user %s", userID))
}
return
}
// GetAccountsByOrg returns a slice of database account records, for all users in the client's organization.
func (s Store) GetAccountsByOrg(ctx domain.RequestContext) (t []account.Account, err error) {
err = s.Runtime.Db.Select(&t, s.Bind(`
SELECT a.id, a.c_refid AS refid, a.c_orgid AS orgid, a.c_userid AS userid,
a.c_editor AS editor, a.c_admin AS admin, a.c_users AS users, a.c_analytics AS analytics,
a.c_active AS active, a.c_created AS created, a.c_revised AS revised,
b.c_company AS company, b.c_title AS title, b.c_message AS message, b.c_domain as domain
FROM dmz_user_account a, dmz_org b
WHERE a.c_orgid=b.c_refid AND a.c_orgid=? AND a.c_active=true`),
ctx.OrgID)
if err != sql.ErrNoRows && err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute select account for org %s", ctx.OrgID))
}
return
}
// CountOrgAccounts returns the numnber of active user accounts for specified organization.
func (s Store) CountOrgAccounts(ctx domain.RequestContext) (c int) {
row := s.Runtime.Db.QueryRow(s.Bind("SELECT count(*) FROM dmz_user_account WHERE c_orgid=? AND c_active=true"), ctx.OrgID)
err := row.Scan(&c)
if err == sql.ErrNoRows {
return 0
}
if err != nil {
err = errors.Wrap(err, "count org accounts")
return 0
}
return
}
// UpdateAccount updates the database record for the given account to the given values.
func (s Store) UpdateAccount(ctx domain.RequestContext, account account.Account) (err error) {
account.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec(s.Bind(`
UPDATE dmz_user_account SET
c_userid=:userid, c_admin=:admin, c_editor=:editor, c_users=:users, c_analytics=:analytics,
c_active=:active, c_revised=:revised WHERE c_orgid=:orgid AND c_refid=:refid`), &account)
if err != sql.ErrNoRows && err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute update for account %s", account.RefID))
}
return
}
// HasOrgAccount returns if the given orgID has valid userID.
func (s Store) HasOrgAccount(ctx domain.RequestContext, orgID, userID string) bool {
row := s.Runtime.Db.QueryRow(s.Bind("SELECT count(*) FROM dmz_user_account WHERE c_orgid=? and c_userid=?"), orgID, userID)
var count int
err := row.Scan(&count)
if err == sql.ErrNoRows {
return false
}
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "HasOrgAccount")
return false
}
if count == 0 {
return false
}
return true
}
// DeleteAccount deletes the database record in the account table for user ID.
func (s Store) DeleteAccount(ctx domain.RequestContext, ID string) (rows int64, err error) {
return s.DeleteConstrained(ctx.Transaction, "dmz_user_account", ctx.OrgID, ID)
}

View file

@ -1,82 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package mysql
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/activity"
"github.com/pkg/errors"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
}
// RecordUserActivity logs user initiated data changes.
func (s Scope) RecordUserActivity(ctx domain.RequestContext, activity activity.UserActivity) (err error) {
activity.OrgID = ctx.OrgID
activity.UserID = ctx.UserID
activity.Created = time.Now().UTC()
_, err = ctx.Transaction.Exec("INSERT INTO useractivity (orgid, userid, labelid, documentid, pageid, sourcetype, activitytype, metadata, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
activity.OrgID, activity.UserID, activity.LabelID, activity.DocumentID, activity.PageID, activity.SourceType, activity.ActivityType, activity.Metadata, activity.Created)
if err != nil {
err = errors.Wrap(err, "execute record user activity")
}
return
}
// GetDocumentActivity returns the metadata for a specified document.
func (s Scope) GetDocumentActivity(ctx domain.RequestContext, id string) (a []activity.DocumentActivity, err error) {
qry := `SELECT a.id, DATE(a.created) as created, a.orgid, IFNULL(a.userid, '') AS userid, a.labelid, a.documentid, a.pageid, a.activitytype, a.metadata,
IFNULL(u.firstname, 'Anonymous') AS firstname, IFNULL(u.lastname, 'Viewer') AS lastname,
IFNULL(p.title, '') as pagetitle
FROM useractivity a
LEFT JOIN user u ON a.userid=u.refid
LEFT JOIN page p ON a.pageid=p.refid
WHERE a.orgid=? AND a.documentid=?
AND a.userid != '0' AND a.userid != ''
ORDER BY a.created DESC`
err = s.Runtime.Db.Select(&a, qry, ctx.OrgID, id)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "select document user activity")
}
if len(a) == 0 {
a = []activity.DocumentActivity{}
}
return
}
// DeleteDocumentChangeActivity removes all entries for document changes (add, remove, update).
func (s Scope) DeleteDocumentChangeActivity(ctx domain.RequestContext, documentID string) (rows int64, err error) {
b := mysql.BaseQuery{}
rows, err = b.DeleteWhere(ctx.Transaction,
fmt.Sprintf("DELETE FROM useractivity WHERE orgid='%s' AND documentid='%s' AND (activitytype=1 OR activitytype=2 OR activitytype=3 OR activitytype=4 OR activitytype=7)", ctx.OrgID, documentID))
return
}

84
domain/activity/store.go Normal file
View file

@ -0,0 +1,84 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package activity
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/activity"
"github.com/pkg/errors"
)
// Store provides data access to user activity information.
type Store struct {
store.Context
store.ActivityStorer
}
// RecordUserActivity logs user initiated data changes.
func (s Store) RecordUserActivity(ctx domain.RequestContext, activity activity.UserActivity) (err error) {
activity.OrgID = ctx.OrgID
activity.UserID = ctx.UserID
activity.Created = time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_user_activity (c_orgid, c_userid, c_spaceid, c_docid, c_sectionid, c_sourcetype, c_activitytype, c_metadata, c_created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"),
activity.OrgID, activity.UserID, activity.SpaceID, activity.DocumentID, activity.SectionID, activity.SourceType, activity.ActivityType, activity.Metadata, activity.Created)
if err != nil {
err = errors.Wrap(err, "execute record user activity")
}
return
}
// GetDocumentActivity returns the metadata for a specified document.
func (s Store) GetDocumentActivity(ctx domain.RequestContext, id string) (a []activity.DocumentActivity, err error) {
qry := s.Bind(`SELECT a.id, DATE(a.c_created) AS created, a.c_orgid AS orgid,
COALESCE(a.c_userid, '') AS userid, a.c_spaceid AS spaceid,
a.c_docid AS documentid, a.c_sectionid AS sectionid, a.c_activitytype AS activitytype,
a.c_metadata AS metadata,
COALESCE(u.c_firstname, 'Anonymous') AS firstname, COALESCE(u.c_lastname, 'Viewer') AS lastname,
COALESCE(p.c_name, '') AS sectionname
FROM dmz_user_activity a
LEFT JOIN dmz_user u ON a.c_userid=u.c_refid
LEFT JOIN dmz_section p ON a.c_sectionid=p.c_refid
WHERE a.c_orgid=? AND a.c_docid=?
AND a.c_userid != '0' AND a.c_userid != ''
ORDER BY a.c_created DESC`)
err = s.Runtime.Db.Select(&a, qry, ctx.OrgID, id)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "select document user activity")
}
if len(a) == 0 {
a = []activity.DocumentActivity{}
}
return
}
// DeleteDocumentChangeActivity removes all entries for document changes (add, remove, update).
func (s Store) DeleteDocumentChangeActivity(ctx domain.RequestContext, documentID string) (rows int64, err error) {
rows, err = s.DeleteWhere(ctx.Transaction,
fmt.Sprintf("DELETE FROM dmz_user_activity WHERE c_orgid='%s' AND c_docid='%s' AND (c_activitytype=1 OR c_activitytype=2 OR c_activitytype=3 OR c_activitytype=4 OR c_activitytype=7)", ctx.OrgID, documentID))
return
}

View file

@ -28,6 +28,7 @@ import (
"github.com/documize/community/domain/organization"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/workflow"
@ -37,7 +38,7 @@ import (
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
Indexer indexer.Indexer
}

View file

@ -1,87 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package attachment
import (
"strings"
"time"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/pkg/errors"
"github.com/documize/community/core/env"
"github.com/documize/community/model/attachment"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
}
// Add inserts the given record into the database attachement table.
func (s Scope) Add(ctx domain.RequestContext, a attachment.Attachment) (err error) {
a.OrgID = ctx.OrgID
a.Created = time.Now().UTC()
a.Revised = time.Now().UTC()
bits := strings.Split(a.Filename, ".")
a.Extension = bits[len(bits)-1]
_, err = ctx.Transaction.Exec("INSERT INTO attachment (refid, orgid, documentid, job, fileid, filename, data, extension, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
a.RefID, a.OrgID, a.DocumentID, a.Job, a.FileID, a.Filename, a.Data, a.Extension, a.Created, a.Revised)
if err != nil {
err = errors.Wrap(err, "execute insert attachment")
}
return
}
// GetAttachment returns the database attachment record specified by the parameters.
func (s Scope) GetAttachment(ctx domain.RequestContext, orgID, attachmentID string) (a attachment.Attachment, err error) {
err = s.Runtime.Db.Get(&a, "SELECT id, refid, orgid, documentid, job, fileid, filename, data, extension, created, revised FROM attachment WHERE orgid=? and refid=?",
orgID, attachmentID)
if err != nil {
err = errors.Wrap(err, "execute select attachment")
}
return
}
// GetAttachments returns a slice containing the attachement records (excluding their data) for document docID, ordered by filename.
func (s Scope) GetAttachments(ctx domain.RequestContext, docID string) (a []attachment.Attachment, err error) {
err = s.Runtime.Db.Select(&a, "SELECT id, refid, orgid, documentid, job, fileid, filename, extension, created, revised FROM attachment WHERE orgid=? and documentid=? order by filename", ctx.OrgID, docID)
if err != nil {
err = errors.Wrap(err, "execute select attachments")
}
return
}
// GetAttachmentsWithData returns a slice containing the attachement records (including their data) for document docID, ordered by filename.
func (s Scope) GetAttachmentsWithData(ctx domain.RequestContext, docID string) (a []attachment.Attachment, err error) {
err = s.Runtime.Db.Select(&a, "SELECT id, refid, orgid, documentid, job, fileid, filename, extension, data, created, revised FROM attachment WHERE orgid=? and documentid=? order by filename", ctx.OrgID, docID)
if err != nil {
err = errors.Wrap(err, "execute select attachments with data")
}
return
}
// Delete deletes the id record from the database attachment table.
func (s Scope) Delete(ctx domain.RequestContext, id string) (rows int64, err error) {
b := mysql.BaseQuery{}
return b.DeleteConstrained(ctx.Transaction, "attachment", ctx.OrgID, id)
}

118
domain/attachment/store.go Normal file
View file

@ -0,0 +1,118 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package attachment
import (
"database/sql"
"strings"
"time"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/attachment"
"github.com/pkg/errors"
)
// Store provides data access to document/section attachments information.
type Store struct {
store.Context
store.AttachmentStorer
}
// Add inserts the given record into the database attachment table.
func (s Store) Add(ctx domain.RequestContext, a attachment.Attachment) (err error) {
a.OrgID = ctx.OrgID
a.Created = time.Now().UTC()
a.Revised = time.Now().UTC()
bits := strings.Split(a.Filename, ".")
a.Extension = bits[len(bits)-1]
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_doc_attachment (c_refid, c_orgid, c_docid, c_job, c_fileid, c_filename, c_data, c_extension, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
a.RefID, a.OrgID, a.DocumentID, a.Job, a.FileID, a.Filename, a.Data, a.Extension, a.Created, a.Revised)
if err != nil {
err = errors.Wrap(err, "execute insert attachment")
}
return
}
// GetAttachment returns the database attachment record specified by the parameters.
func (s Store) GetAttachment(ctx domain.RequestContext, orgID, attachmentID string) (a attachment.Attachment, err error) {
err = s.Runtime.Db.Get(&a, s.Bind(`
SELECT id, c_refid AS refid,
c_orgid AS orgid, c_docid AS documentid, c_job AS job, c_fileid AS fileid,
c_filename AS filename, c_data AS data, c_extension AS extension,
c_created AS created, c_revised AS revised
FROM dmz_doc_attachment
WHERE c_orgid=? and c_refid=?`),
orgID, attachmentID)
if err != nil {
err = errors.Wrap(err, "execute select attachment")
}
return
}
// GetAttachments returns a slice containing the attachment records (excluding their data) for document docID, ordered by filename.
func (s Store) GetAttachments(ctx domain.RequestContext, docID string) (a []attachment.Attachment, err error) {
err = s.Runtime.Db.Select(&a, s.Bind(`
SELECT id, c_refid AS refid,
c_orgid AS orgid, c_docid AS documentid, c_job AS job, c_fileid AS fileid,
c_filename AS filename, c_extension AS extension,
c_created AS created, c_revised AS revised
FROM dmz_doc_attachment
WHERE c_orgid=? AND c_docid=?
ORDER BY c_filename`),
ctx.OrgID, docID)
if err == sql.ErrNoRows {
err = nil
a = []attachment.Attachment{}
}
if err != nil {
err = errors.Wrap(err, "execute select attachments")
return
}
return
}
// GetAttachmentsWithData returns a slice containing the attachment records (including their data) for document docID, ordered by filename.
func (s Store) GetAttachmentsWithData(ctx domain.RequestContext, docID string) (a []attachment.Attachment, err error) {
err = s.Runtime.Db.Select(&a, s.Bind(`
SELECT id, c_refid AS refid,
c_orgid AS orgid, c_docid AS documentid, c_job AS job, c_fileid AS fileid,
c_filename AS filename, c_data AS data, c_extension AS extension,
c_created AS created, c_revised AS revised
FROM dmz_doc_attachment
WHERE c_orgid=? and c_docid=?
ORDER BY c_filename`),
ctx.OrgID, docID)
if err == sql.ErrNoRows {
err = nil
a = []attachment.Attachment{}
}
if err != nil {
err = errors.Wrap(err, "execute select attachments with data")
}
return
}
// Delete deletes the id record from the database attachment table.
func (s Store) Delete(ctx domain.RequestContext, id string) (rows int64, err error) {
return s.DeleteConstrained(ctx.Transaction, "dmz_doc_attachment", ctx.OrgID, id)
}

View file

@ -15,18 +15,19 @@ package audit
import (
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/audit"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
// Store provides data access to audit log information.
type Store struct {
store.Context
store.AuditStorer
}
// Record adds event entry for specified user using own DB TX.
func (s Scope) Record(ctx domain.RequestContext, t audit.EventType) {
func (s Store) Record(ctx domain.RequestContext, t audit.EventType) {
e := audit.AppEvent{}
e.OrgID = ctx.OrgID
e.UserID = ctx.UserID
@ -40,7 +41,7 @@ func (s Scope) Record(ctx domain.RequestContext, t audit.EventType) {
return
}
_, err = tx.Exec("INSERT INTO userevent (orgid, userid, eventtype, ip, created) VALUES (?, ?, ?, ?, ?)",
_, err = tx.Exec(s.Bind("INSERT INTO dmz_audit_log (c_orgid, c_userid, c_eventtype, c_ip, c_created) VALUES (?, ?, ?, ?, ?)"),
e.OrgID, e.UserID, e.Type, e.IP, e.Created)
if err != nil {

View file

@ -17,13 +17,14 @@ import (
"github.com/documize/community/core/env"
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
usr "github.com/documize/community/domain/user"
"github.com/documize/community/model/account"
"github.com/documize/community/model/user"
)
// AddExternalUser method to setup user account in Documize using Keycloak/LDAP provided user data.
func AddExternalUser(ctx domain.RequestContext, rt *env.Runtime, store *domain.Store, u user.User, addSpace bool) (nu user.User, err error) {
func AddExternalUser(ctx domain.RequestContext, rt *env.Runtime, store *store.Store, u user.User, addSpace bool) (nu user.User, err error) {
// only create account if not dupe
addUser := true
addAccount := true

View file

@ -23,6 +23,7 @@ import (
"github.com/documize/community/domain"
"github.com/documize/community/domain/organization"
"github.com/documize/community/domain/section/provider"
"github.com/documize/community/domain/store"
"github.com/documize/community/domain/user"
"github.com/documize/community/model/auth"
"github.com/documize/community/model/org"
@ -31,7 +32,7 @@ import (
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
}
// Login user based up HTTP Authorization header.
@ -82,6 +83,7 @@ func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
}
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error("unable to fetch user", err)
return
}
if len(u.Reset) > 0 || len(u.Password) == 0 {
@ -190,7 +192,7 @@ func (h *Handler) ValidateToken(w http.ResponseWriter, r *http.Request) {
rc.OrgName = org.Title
rc.Administrator = false
rc.Editor = false
rc.Global = false
rc.GlobalAdmin = false
rc.AppURL = r.Host
rc.Subdomain = organization.GetSubdomainFromHost(r)
rc.SSL = r.TLS != nil
@ -210,7 +212,7 @@ func (h *Handler) ValidateToken(w http.ResponseWriter, r *http.Request) {
rc.Administrator = u.Admin
rc.Editor = u.Editor
rc.Global = u.Global
rc.GlobalAdmin = u.GlobalAdmin
response.WriteJSON(w, u)
}

View file

@ -27,6 +27,7 @@ import (
"github.com/documize/community/core/stringutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/auth"
"github.com/documize/community/domain/store"
usr "github.com/documize/community/domain/user"
ath "github.com/documize/community/model/auth"
"github.com/documize/community/model/user"
@ -35,7 +36,7 @@ import (
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
}
// Sync gets list of Keycloak users and inserts new users into Documize

View file

@ -26,6 +26,7 @@ import (
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/auth"
"github.com/documize/community/domain/store"
usr "github.com/documize/community/domain/user"
ath "github.com/documize/community/model/auth"
lm "github.com/documize/community/model/auth"
@ -35,7 +36,7 @@ import (
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
}
// Preview connects to LDAP using paylaod and returns first 50 users.

View file

@ -212,11 +212,6 @@ func extractUser(c lm.LDAPConfig, e *ld.Entry) (u lm.LDAPUser) {
u.RemoteID = e.GetAttributeValue(c.AttributeUserRDN)
u.CN = e.GetAttributeValue("cn")
// Make name elements from DisplayName if we can.
if (len(u.Firstname) == 0 || len(u.Firstname) == 0) &&
len(e.GetAttributeValue(c.AttributeUserDisplayName)) > 0 {
}
if len(u.Firstname) == 0 {
u.Firstname = "LDAP"
}
@ -251,13 +246,13 @@ func convertUsers(c lm.LDAPConfig, lu []lm.LDAPUser) (du []user.User) {
// ConvertUser turns LDAP user into Documize user.
func convertUser(c lm.LDAPConfig, lu lm.LDAPUser) (du user.User) {
du = user.User{}
du.Editor = c.DefaultPermissionAddSpace
du.Active = true
du.Email = lu.Email
du.ViewUsers = false
du.Analytics = false
du.Admin = false
du.Global = false
du.GlobalAdmin = false
du.Editor = c.DefaultPermissionAddSpace
du.Email = lu.Email
du.Firstname = lu.Firstname
du.Lastname = lu.Lastname
du.Initials = stringutil.MakeInitials(lu.Firstname, lu.Lastname)

186
domain/backup/endpoint.go Normal file
View file

@ -0,0 +1,186 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package backup
import (
"archive/zip"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"github.com/documize/community/core/env"
"github.com/documize/community/core/response"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/store"
)
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *store.Store
Indexer indexer.Indexer
}
// Backup generates binary file of all instance settings and contents.
// The content is pulled directly from the database and marshalled to JSON.
// A zip file is then sent to the caller.
func (h *Handler) Backup(w http.ResponseWriter, r *http.Request) {
method := "system.backup"
ctx := domain.GetRequestContext(r)
if !ctx.Administrator {
response.WriteForbiddenError(w)
h.Runtime.Log.Info(fmt.Sprintf("Non-admin attempted system backup operation (user ID: %s)", ctx.UserID))
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
h.Runtime.Log.Error(method, err)
return
}
spec := backupSpec{}
err = json.Unmarshal(body, &spec)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
h.Runtime.Log.Error(method, err)
return
}
// data, err := backup(ctx, *h.Store, spec)
// if err != nil {
// response.WriteServerError(w, method, err)
// h.Runtime.Log.Error(method, err)
// return
// }
// Filename is current timestamp
fn := fmt.Sprintf("dmz-backup-%s.zip", uniqueid.Generate())
ziptest(fn)
bb, err := ioutil.ReadFile(fn)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
w.Header().Set("Content-Type", "application/zip")
w.Header().Set("Content-Disposition", `attachment; filename="`+fn+`" ; `+`filename*="`+fn+`"`)
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(bb)))
w.Header().Set("x-documize-filename", fn)
x, err := w.Write(bb)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
w.WriteHeader(http.StatusOK)
h.Runtime.Log.Info(fmt.Sprintf("Backup completed for %s by %s, size %d", ctx.OrgID, ctx.UserID, x))
}
type backupSpec struct {
}
func backup(ctx domain.RequestContext, s store.Store, spec backupSpec) (file []byte, err error) {
buf := new(bytes.Buffer)
zw := zip.NewWriter(buf)
// Add some files to the archive.
var files = []struct {
Name, Body string
}{
{"readme.txt", "This archive contains some text files."},
{"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
{"todo.txt", "Get animal handling licence.\nWrite more examples."},
}
for _, file := range files {
f, err := zw.Create(file.Name)
if err != nil {
return nil, err
}
_, err = f.Write([]byte(file.Body))
if err != nil {
return nil, err
}
}
// Make sure to check the error on Close.
err = zw.Close()
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func ziptest(filename string) {
// Create a file to write the archive buffer to
// Could also use an in memory buffer.
outFile, err := os.Create(filename)
if err != nil {
fmt.Println(err)
}
defer outFile.Close()
// Create a zip writer on top of the file writer
zipWriter := zip.NewWriter(outFile)
// Add files to archive
// We use some hard coded data to demonstrate,
// but you could iterate through all the files
// in a directory and pass the name and contents
// of each file, or you can take data from your
// program and write it write in to the archive
// without
var filesToArchive = []struct {
Name, Body string
}{
{"test.txt", "String contents of file"},
{"test2.txt", "\x61\x62\x63\n"},
}
// Create and write files to the archive, which in turn
// are getting written to the underlying writer to the
// .zip file we created at the beginning
for _, file := range filesToArchive {
fileWriter, err := zipWriter.Create(file.Name)
if err != nil {
fmt.Println(err)
}
_, err = fileWriter.Write([]byte(file.Body))
if err != nil {
fmt.Println(err)
}
}
// Clean up
err = zipWriter.Close()
if err != nil {
fmt.Println(err)
}
}

View file

@ -23,6 +23,7 @@ import (
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/permission"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/block"
)
@ -30,7 +31,7 @@ import (
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
}
// Add inserts new reusable content block into database.
@ -54,10 +55,11 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
err = json.Unmarshal(body, &b)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
h.Runtime.Log.Error(method, err)
return
}
if !permission.CanUploadDocument(ctx, *h.Store, b.LabelID) {
if !permission.CanUploadDocument(ctx, *h.Store, b.SpaceID) {
response.WriteForbiddenError(w)
return
}
@ -67,6 +69,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
@ -74,6 +77,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
@ -84,6 +88,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
b, err = h.Store.Block.Get(ctx, b.RefID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
@ -104,6 +109,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
b, err := h.Store.Block.Get(ctx, blockID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
@ -132,6 +138,7 @@ func (h *Handler) GetBySpace(w http.ResponseWriter, r *http.Request) {
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
@ -165,7 +172,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
b.RefID = blockID
if !permission.CanUploadDocument(ctx, *h.Store, b.LabelID) {
if !permission.CanUploadDocument(ctx, *h.Store, b.SpaceID) {
response.WriteForbiddenError(w)
return
}
@ -173,6 +180,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
@ -180,6 +188,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
@ -212,6 +221,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
@ -219,6 +229,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}

View file

@ -1,123 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package mysql
import (
"database/sql"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/block"
"github.com/pkg/errors"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
}
// Add saves reusable content block.
func (s Scope) Add(ctx domain.RequestContext, b block.Block) (err error) {
b.OrgID = ctx.OrgID
b.UserID = ctx.UserID
b.Created = time.Now().UTC()
b.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec("INSERT INTO block (refid, orgid, labelid, userid, contenttype, pagetype, title, body, excerpt, rawbody, config, externalsource, used, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
b.RefID, b.OrgID, b.LabelID, b.UserID, b.ContentType, b.PageType, b.Title, b.Body, b.Excerpt, b.RawBody, b.Config, b.ExternalSource, b.Used, b.Created, b.Revised)
if err != nil {
err = errors.Wrap(err, "execute insert block")
}
return
}
// Get returns requested reusable content block.
func (s Scope) Get(ctx domain.RequestContext, id string) (b block.Block, err error) {
err = s.Runtime.Db.Get(&b, "SELECT a.id, a.refid, a.orgid, a.labelid, a.userid, a.contenttype, a.pagetype, a.title, a.body, a.excerpt, a.rawbody, a.config, a.externalsource, a.used, a.created, a.revised, b.firstname, b.lastname FROM block a LEFT JOIN user b ON a.userid = b.refid WHERE a.orgid=? AND a.refid=?",
ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "execute select block")
}
return
}
// GetBySpace returns all reusable content scoped to given space.
func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (b []block.Block, err error) {
err = s.Runtime.Db.Select(&b, "SELECT a.id, a.refid, a.orgid, a.labelid, a.userid, a.contenttype, a.pagetype, a.title, a.body, a.excerpt, a.rawbody, a.config, a.externalsource, a.used, a.created, a.revised, b.firstname, b.lastname FROM block a LEFT JOIN user b ON a.userid = b.refid WHERE a.orgid=? AND a.labelid=? ORDER BY a.title", ctx.OrgID, spaceID)
if err != nil {
err = errors.Wrap(err, "select space blocks")
}
return
}
// IncrementUsage increments usage counter for content block.
func (s Scope) IncrementUsage(ctx domain.RequestContext, id string) (err error) {
_, err = ctx.Transaction.Exec("UPDATE block SET used=used+1, revised=? WHERE orgid=? AND refid=?", time.Now().UTC(), ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "execute increment block usage")
}
return
}
// DecrementUsage decrements usage counter for content block.
func (s Scope) DecrementUsage(ctx domain.RequestContext, id string) (err error) {
_, err = ctx.Transaction.Exec("UPDATE block SET used=used-1, revised=? WHERE orgid=? AND refid=?", time.Now().UTC(), ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "execute decrement block usage")
}
return
}
// RemoveReference clears page.blockid for given blockID.
func (s Scope) RemoveReference(ctx domain.RequestContext, id string) (err error) {
_, err = ctx.Transaction.Exec("UPDATE page SET blockid='', revised=? WHERE orgid=? AND blockid=?", time.Now().UTC(), ctx.OrgID, id)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "execute remove block ref")
}
return
}
// Update updates existing reusable content block item.
func (s Scope) Update(ctx domain.RequestContext, b block.Block) (err error) {
b.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec("UPDATE block SET title=:title, body=:body, excerpt=:excerpt, rawbody=:rawbody, config=:config, revised=:revised WHERE orgid=:orgid AND refid=:refid", b)
if err != nil {
err = errors.Wrap(err, "execute update block")
}
return
}
// Delete removes reusable content block from database.
func (s Scope) Delete(ctx domain.RequestContext, id string) (rows int64, err error) {
b := mysql.BaseQuery{}
return b.DeleteConstrained(ctx.Transaction, "block", ctx.OrgID, id)
}

152
domain/block/store.go Normal file
View file

@ -0,0 +1,152 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package block
import (
"database/sql"
"time"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/block"
"github.com/pkg/errors"
)
// Store provides data access to section template information.
type Store struct {
store.Context
store.BlockStorer
}
// Add saves reusable content block.
func (s Store) Add(ctx domain.RequestContext, b block.Block) (err error) {
b.OrgID = ctx.OrgID
b.UserID = ctx.UserID
b.Created = time.Now().UTC()
b.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_section_template (c_refid, c_orgid, c_spaceid, c_userid, c_contenttype, c_type, c_name, c_body, c_desc, c_rawbody, c_config, c_external, c_used, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
b.RefID, b.OrgID, b.SpaceID, b.UserID, b.ContentType, b.Type, b.Name, b.Body, b.Excerpt, b.RawBody, b.Config, b.ExternalSource, b.Used, b.Created, b.Revised)
if err != nil {
err = errors.Wrap(err, "execute insert block")
}
return
}
// Get returns requested reusable content block.
func (s Store) Get(ctx domain.RequestContext, id string) (b block.Block, err error) {
err = s.Runtime.Db.Get(&b, s.Bind(`
SELECT a.id, a.c_refid as refid,
a.c_orgid as orgid,
a.c_spaceid AS spaceid, a.c_userid AS userid, a.c_contenttype AS contenttype, a.c_type AS type,
a.c_name AS name, a.c_body AS body, a.c_desc AS excerpt, a.c_rawbody AS rawbody,
a.c_config AS config, a.c_external AS externalsource, a.c_used AS used,
a.c_created AS created, a.c_revised AS revised,
b.c_firstname AS firstname, b.c_lastname AS lastname
FROM dmz_section_template a LEFT JOIN dmz_user b ON a.c_userid = b.c_refid
WHERE a.c_orgid=? AND a.c_refid=?`),
ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "execute select block")
}
return
}
// GetBySpace returns all reusable content scoped to given space.
func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (b []block.Block, err error) {
err = s.Runtime.Db.Select(&b, s.Bind(`
SELECT a.id, a.c_refid as refid,
a.c_orgid as orgid,
a.c_spaceid AS spaceid, a.c_userid AS userid, a.c_contenttype AS contenttype, a.c_type AS type,
a.c_name AS name, a.c_body AS body, a.c_desc AS excerpt, a.c_rawbody AS rawbody,
a.c_config AS config, a.c_external AS externalsource, a.c_used AS used,
a.c_created AS created, a.c_revised AS revised,
b.c_firstname AS firstname, b.c_lastname AS lastname
FROM dmz_section_template a LEFT JOIN dmz_user b ON a.c_userid = b.c_refid
WHERE a.c_orgid=? AND a.c_spaceid=?
ORDER BY a.c_name`),
ctx.OrgID, spaceID)
if err != nil {
err = errors.Wrap(err, "select space blocks")
}
return
}
// IncrementUsage increments usage counter for content block.
func (s Store) IncrementUsage(ctx domain.RequestContext, id string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_section_template SET
c_used=c_used+1, c_revised=? WHERE c_orgid=? AND c_refid=?`),
time.Now().UTC(), ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "execute increment block usage")
}
return
}
// DecrementUsage decrements usage counter for content block.
func (s Store) DecrementUsage(ctx domain.RequestContext, id string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_section_template SET
c_used=c_used-1, c_revised=? WHERE c_orgid=? AND c_refid=?`),
time.Now().UTC(), ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "execute decrement block usage")
}
return
}
// RemoveReference clears page.blockid for given blockID.
func (s Store) RemoveReference(ctx domain.RequestContext, id string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_section SET
c_templateid='', c_revised=?
WHERE c_orgid=? AND c_templateid=?`),
time.Now().UTC(), ctx.OrgID, id)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "execute remove block ref")
}
return
}
// Update updates existing reusable content block item.
func (s Store) Update(ctx domain.RequestContext, b block.Block) (err error) {
b.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec(s.Bind(`UPDATE dmz_section_template SET
c_name=:name, c_body=:body, c_desc=:excerpt, c_rawbody=:rawbody,
c_config=:config, c_revised=:revised
WHERE c_orgid=:orgid AND c_refid=:refid`),
b)
if err != nil {
err = errors.Wrap(err, "execute update block")
}
return
}
// Delete removes reusable content block from database.
func (s Store) Delete(ctx domain.RequestContext, id string) (rows int64, err error) {
return s.DeleteConstrained(ctx.Transaction, "dmz_section_template", ctx.OrgID, id)
}

View file

@ -26,6 +26,7 @@ import (
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/permission"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/category"
pm "github.com/documize/community/model/permission"
@ -34,7 +35,7 @@ import (
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
}
// Add saves space category.
@ -74,9 +75,9 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
}
// Category max length 30.
cat.Category = strings.TrimSpace(cat.Category)
if len(cat.Category) > 30 {
cat.Category = cat.Category[:30]
cat.Name = strings.TrimSpace(cat.Name)
if len(cat.Name) > 30 {
cat.Name = cat.Name[:30]
}
err = h.Store.Category.Add(ctx, cat)
@ -164,6 +165,7 @@ func (h *Handler) GetAll(w http.ResponseWriter, r *http.Request) {
cat, err := h.Store.Category.GetAllBySpace(ctx, spaceID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
@ -200,7 +202,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
cat.OrgID = ctx.OrgID
cat.RefID = categoryID
ok := permission.HasPermission(ctx, *h.Store, cat.LabelID, pm.SpaceManage, pm.SpaceOwner)
ok := permission.HasPermission(ctx, *h.Store, cat.SpaceID, pm.SpaceManage, pm.SpaceOwner)
if !ok || !ctx.Authenticated {
response.WriteForbiddenError(w)
return
@ -249,10 +251,11 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
cat, err := h.Store.Category.Get(ctx, catID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ok := permission.HasPermission(ctx, *h.Store, cat.LabelID, pm.SpaceManage, pm.SpaceOwner)
ok := permission.HasPermission(ctx, *h.Store, cat.SpaceID, pm.SpaceManage, pm.SpaceOwner)
if !ok || !ctx.Authenticated {
response.WriteForbiddenError(w)
return
@ -318,8 +321,8 @@ func (h *Handler) GetSummary(w http.ResponseWriter, r *http.Request) {
s, err := h.Store.Category.GetSpaceCategorySummary(ctx, spaceID)
if err != nil {
h.Runtime.Log.Error("get space category summary failed", err)
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
@ -358,7 +361,7 @@ func (h *Handler) SetDocumentCategoryMembership(w http.ResponseWriter, r *http.R
return
}
if !permission.HasPermission(ctx, *h.Store, cats[0].LabelID, pm.DocumentAdd, pm.DocumentEdit) {
if !permission.HasPermission(ctx, *h.Store, cats[0].SpaceID, pm.DocumentAdd, pm.DocumentEdit) {
response.WriteForbiddenError(w)
return
}
@ -413,7 +416,7 @@ func (h *Handler) GetDocumentCategoryMembership(w http.ResponseWriter, r *http.R
return
}
if !permission.HasPermission(ctx, *h.Store, doc.LabelID, pm.SpaceView, pm.DocumentAdd, pm.DocumentEdit) {
if !permission.HasPermission(ctx, *h.Store, doc.SpaceID, pm.SpaceView, pm.DocumentAdd, pm.DocumentEdit) {
response.WriteForbiddenError(w)
return
}

View file

@ -1,316 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
// Package mysql handles data persistence for both category definition
// and and document/category association.
package mysql
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/category"
"github.com/pkg/errors"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
}
// Add inserts the given record into the category table.
func (s Scope) Add(ctx domain.RequestContext, c category.Category) (err error) {
c.Created = time.Now().UTC()
c.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec("INSERT INTO category (refid, orgid, labelid, category, created, revised) VALUES (?, ?, ?, ?, ?, ?)",
c.RefID, c.OrgID, c.LabelID, c.Category, c.Created, c.Revised)
if err != nil {
err = errors.Wrap(err, "unable to execute insert category")
}
return
}
// GetBySpace returns space categories accessible by user.
// Context is used to for user ID.
func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (c []category.Category, err error) {
err = s.Runtime.Db.Select(&c, `
SELECT id, refid, orgid, labelid, category, created, revised FROM category
WHERE orgid=? AND labelid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='category' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='category' UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid
WHERE p.orgid=? AND p.who='role' AND p.location='category' AND (r.userid=? OR r.userid='0')
))
ORDER BY category`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select categories for space %s", spaceID))
}
return
}
// GetAllBySpace returns all space categories.
func (s Scope) GetAllBySpace(ctx domain.RequestContext, spaceID string) (c []category.Category, err error) {
c = []category.Category{}
err = s.Runtime.Db.Select(&c, `
SELECT id, refid, orgid, labelid, category, created, revised FROM category
WHERE orgid=? AND labelid=?
AND labelid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid
WHERE p.orgid=? AND p.who='role' AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0')
))
ORDER BY category`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select all categories for space %s", spaceID))
}
return
}
// GetByOrg returns all categories accessible by user for their org.
func (s Scope) GetByOrg(ctx domain.RequestContext, userID string) (c []category.Category, err error) {
err = s.Runtime.Db.Select(&c, `
SELECT id, refid, orgid, labelid, category, created, revised FROM category
WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='category' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='category' UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid
WHERE p.orgid=? AND p.who='role' AND p.location='category' AND (r.userid=? OR r.userid='0')
))
ORDER BY category`, ctx.OrgID, ctx.OrgID, ctx.OrgID, userID, ctx.OrgID, userID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select categories for org %s", ctx.OrgID))
}
return
}
// Update saves category name change.
func (s Scope) Update(ctx domain.RequestContext, c category.Category) (err error) {
c.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec("UPDATE category SET category=:category, revised=:revised WHERE orgid=:orgid AND refid=:refid", c)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute update for category %s", c.RefID))
}
return
}
// Get returns specified category
func (s Scope) Get(ctx domain.RequestContext, id string) (c category.Category, err error) {
err = s.Runtime.Db.Get(&c, "SELECT id, refid, orgid, labelid, category, created, revised FROM category WHERE orgid=? AND refid=?",
ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to get category %s", id))
}
return
}
// Delete removes category from the store.
func (s Scope) Delete(ctx domain.RequestContext, id string) (rows int64, err error) {
b := mysql.BaseQuery{}
return b.DeleteConstrained(ctx.Transaction, "category", ctx.OrgID, id)
}
// AssociateDocument inserts category membership record into the category member table.
func (s Scope) AssociateDocument(ctx domain.RequestContext, m category.Member) (err error) {
m.Created = time.Now().UTC()
m.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec("INSERT INTO categorymember (refid, orgid, categoryid, labelid, documentid, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?)",
m.RefID, m.OrgID, m.CategoryID, m.LabelID, m.DocumentID, m.Created, m.Revised)
if err != nil {
err = errors.Wrap(err, "unable to execute insert categorymember")
}
return
}
// DisassociateDocument removes document associatation from category.
func (s Scope) DisassociateDocument(ctx domain.RequestContext, categoryID, documentID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM categorymember WHERE orgid='%s' AND categoryid='%s' AND documentid='%s'",
ctx.OrgID, categoryID, documentID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// RemoveCategoryMembership removes all category associations from the store.
func (s Scope) RemoveCategoryMembership(ctx domain.RequestContext, categoryID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM categorymember WHERE orgid='%s' AND categoryid='%s'",
ctx.OrgID, categoryID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// RemoveSpaceCategoryMemberships removes all category associations from the store for the space.
func (s Scope) RemoveSpaceCategoryMemberships(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM categorymember WHERE orgid='%s' AND labelid='%s'",
ctx.OrgID, spaceID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// RemoveDocumentCategories removes all document category associations from the store.
func (s Scope) RemoveDocumentCategories(ctx domain.RequestContext, documentID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM categorymember WHERE orgid='%s' AND documentid='%s'",
ctx.OrgID, documentID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteBySpace removes all category and category associations for given space.
func (s Scope) DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
b := mysql.BaseQuery{}
s1 := fmt.Sprintf("DELETE FROM categorymember WHERE orgid='%s' AND labelid='%s'", ctx.OrgID, spaceID)
b.DeleteWhere(ctx.Transaction, s1)
s2 := fmt.Sprintf("DELETE FROM category WHERE orgid='%s' AND labelid='%s'", ctx.OrgID, spaceID)
return b.DeleteWhere(ctx.Transaction, s2)
}
// GetSpaceCategorySummary returns number of documents and users for space categories.
func (s Scope) GetSpaceCategorySummary(ctx domain.RequestContext, spaceID string) (c []category.SummaryModel, err error) {
c = []category.SummaryModel{}
err = s.Runtime.Db.Select(&c, `
SELECT 'documents' as type, categoryid, COUNT(*) AS count
FROM categorymember
WHERE orgid=? AND labelid=?
AND documentid IN (
SELECT refid FROM document
WHERE orgid=? AND labelid=?
AND lifecycle!=2 AND template=0 AND groupid=''
UNION ALL
SELECT d.refid
FROM (
SELECT groupid, MIN(versionorder) AS latestversion
FROM document
WHERE orgid=? AND labelid=? AND lifecycle!=2 AND groupid!='' AND template=0
GROUP BY groupid
) AS x INNER JOIN document AS d ON d.groupid=x.groupid AND d.versionorder=x.latestversion
)
GROUP BY categoryid, type
UNION ALL
SELECT 'users' as type, refid AS categoryid, count(*) AS count
FROM permission
WHERE orgid=? AND location='category'
AND refid IN (SELECT refid FROM category WHERE orgid=? AND labelid=?)
GROUP BY refid, type`,
ctx.OrgID, spaceID,
ctx.OrgID, spaceID, ctx.OrgID, spaceID,
ctx.OrgID, ctx.OrgID, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("select category summary for space %s", spaceID))
}
return
}
// GetDocumentCategoryMembership returns all space categories associated with given document.
func (s Scope) GetDocumentCategoryMembership(ctx domain.RequestContext, documentID string) (c []category.Category, err error) {
c = []category.Category{}
err = s.Runtime.Db.Select(&c, `
SELECT id, refid, orgid, labelid, category, created, revised FROM category
WHERE orgid=? AND refid IN (SELECT categoryid FROM categorymember WHERE orgid=? AND documentid=?)`, ctx.OrgID, ctx.OrgID, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select categories for document %s", documentID))
}
return
}
// GetSpaceCategoryMembership returns category/document associations within space.
func (s Scope) GetSpaceCategoryMembership(ctx domain.RequestContext, spaceID string) (c []category.Member, err error) {
err = s.Runtime.Db.Select(&c, `
SELECT id, refid, orgid, labelid, categoryid, documentid, created, revised FROM categorymember
WHERE orgid=? AND labelid=?
AND labelid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space'
AND p.action='view' AND (r.userid=? OR r.userid='0')
))
ORDER BY documentid`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("select all category/document membership for space %s", spaceID))
}
return
}
// GetOrgCategoryMembership returns category/document associations within organization.
func (s Scope) GetOrgCategoryMembership(ctx domain.RequestContext, userID string) (c []category.Member, err error) {
err = s.Runtime.Db.Select(&c, `
SELECT id, refid, orgid, labelid, categoryid, documentid, created, revised FROM categorymember
WHERE orgid=?
AND labelid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space'
AND p.action='view' AND (r.userid=? OR r.userid='0')
))
ORDER BY documentid`, ctx.OrgID, ctx.OrgID, ctx.OrgID, userID, ctx.OrgID, userID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("select all category/document membership for organization %s", ctx.OrgID))
}
return
}

323
domain/category/store.go Normal file
View file

@ -0,0 +1,323 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package category
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/category"
"github.com/pkg/errors"
)
// Store provides data access to space category information.
type Store struct {
store.Context
store.CategoryStorer
}
// Add inserts the given record into the category table.
func (s Store) Add(ctx domain.RequestContext, c category.Category) (err error) {
c.Created = time.Now().UTC()
c.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_category (c_refid, c_orgid, c_spaceid, c_name, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?)"),
c.RefID, c.OrgID, c.SpaceID, c.Name, c.Created, c.Revised)
if err != nil {
err = errors.Wrap(err, "unable to execute insert category")
}
return
}
// GetBySpace returns space categories accessible by user.
// Context is used to for user ID.
func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (c []category.Category, err error) {
err = s.Runtime.Db.Select(&c, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised
FROM dmz_category
WHERE c_orgid=? AND c_spaceid=? AND c_refid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='category' AND c_refid IN
(SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='category'
UNION ALL
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid
WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='category' AND (r.c_userid=? OR r.c_userid='0')
))
ORDER BY name`),
ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select categories for space %s", spaceID))
}
return
}
// GetAllBySpace returns all space categories.
func (s Store) GetAllBySpace(ctx domain.RequestContext, spaceID string) (c []category.Category, err error) {
c = []category.Category{}
err = s.Runtime.Db.Select(&c, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised
FROM dmz_category
WHERE c_orgid=? AND c_spaceid=? AND c_spaceid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
UNION ALL
SELECT p.c_refid FROM dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid
WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='space' AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
))
ORDER BY c_name`),
ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select all categories for space %s", spaceID))
}
return
}
// GetByOrg returns all categories accessible by user for their org.
func (s Store) GetByOrg(ctx domain.RequestContext, userID string) (c []category.Category, err error) {
err = s.Runtime.Db.Select(&c, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised
FROM dmz_category
WHERE c_orgid=? AND c_refid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='category' AND c_refid IN (
SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='category'
UNION ALL
SELECT p.c_refid FROM dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid
WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='category' AND (r.c_userid=? OR r.c_userid='0')
))
ORDER BY c_name`),
ctx.OrgID, ctx.OrgID, ctx.OrgID, userID, ctx.OrgID, userID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select categories for org %s", ctx.OrgID))
}
return
}
// Update saves category name change.
func (s Store) Update(ctx domain.RequestContext, c category.Category) (err error) {
c.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec(s.Bind("UPDATE dmz_category SET c_name=:name, c_revised=:revised WHERE c_orgid=:orgid AND c_refid=:refid"), c)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute update for category %s", c.RefID))
}
return
}
// Get returns specified category
func (s Store) Get(ctx domain.RequestContext, id string) (c category.Category, err error) {
err = s.Runtime.Db.Get(&c, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised
FROM dmz_category
WHERE c_orgid=? AND c_refid=?`),
ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to get category %s", id))
}
return
}
// Delete removes category from the store.
func (s Store) Delete(ctx domain.RequestContext, id string) (rows int64, err error) {
return s.DeleteConstrained(ctx.Transaction, "dmz_category", ctx.OrgID, id)
}
// AssociateDocument inserts category membership record into the category member table.
func (s Store) AssociateDocument(ctx domain.RequestContext, m category.Member) (err error) {
m.Created = time.Now().UTC()
m.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_category_member (c_refid, c_orgid, c_categoryid, c_spaceid, c_docid, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?)"),
m.RefID, m.OrgID, m.CategoryID, m.SpaceID, m.DocumentID, m.Created, m.Revised)
if err != nil {
err = errors.Wrap(err, "unable to execute insert categorymember")
}
return
}
// DisassociateDocument removes document associatation from category.
func (s Store) DisassociateDocument(ctx domain.RequestContext, categoryID, documentID string) (rows int64, err error) {
sql := fmt.Sprintf("DELETE FROM dmz_category_member WHERE c_orgid='%s' AND c_categoryid='%s' AND c_docid='%s'",
ctx.OrgID, categoryID, documentID)
return s.DeleteWhere(ctx.Transaction, sql)
}
// RemoveCategoryMembership removes all category associations from the store.
func (s Store) RemoveCategoryMembership(ctx domain.RequestContext, categoryID string) (rows int64, err error) {
sql := fmt.Sprintf("DELETE FROM dmz_category_member WHERE c_orgid='%s' AND c_categoryid='%s'",
ctx.OrgID, categoryID)
return s.DeleteWhere(ctx.Transaction, sql)
}
// RemoveSpaceCategoryMemberships removes all category associations from the store for the space.
func (s Store) RemoveSpaceCategoryMemberships(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
sql := fmt.Sprintf("DELETE FROM dmz_category_member WHERE c_orgid='%s' AND c_spaceid='%s'",
ctx.OrgID, spaceID)
return s.DeleteWhere(ctx.Transaction, sql)
}
// RemoveDocumentCategories removes all document category associations from the store.
func (s Store) RemoveDocumentCategories(ctx domain.RequestContext, documentID string) (rows int64, err error) {
sql := fmt.Sprintf("DELETE FROM dmz_category_member WHERE c_orgid='%s' AND c_docid='%s'",
ctx.OrgID, documentID)
return s.DeleteWhere(ctx.Transaction, sql)
}
// DeleteBySpace removes all category and category associations for given space.
func (s Store) DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
s1 := fmt.Sprintf("DELETE FROM dmz_category_member WHERE c_orgid='%s' AND c_spaceid='%s'", ctx.OrgID, spaceID)
s.DeleteWhere(ctx.Transaction, s1)
s2 := fmt.Sprintf("DELETE FROM dmz_category WHERE c_orgid='%s' AND c_spaceid='%s'", ctx.OrgID, spaceID)
return s.DeleteWhere(ctx.Transaction, s2)
}
// GetSpaceCategorySummary returns number of documents and users for space categories.
func (s Store) GetSpaceCategorySummary(ctx domain.RequestContext, spaceID string) (c []category.SummaryModel, err error) {
c = []category.SummaryModel{}
err = s.Runtime.Db.Select(&c, s.Bind(`
SELECT 'documents' AS type, c_categoryid AS categoryid, COUNT(*) AS count
FROM dmz_category_member
WHERE c_orgid=? AND c_spaceid=?
AND c_docid IN (
SELECT c_refid
FROM dmz_doc
WHERE c_orgid=? AND c_spaceid=? AND c_lifecycle!=2 AND c_template=false AND c_groupid=''
UNION ALL
SELECT d.c_refid
FROM (
SELECT c_groupid, MIN(c_versionorder) AS latestversion
FROM dmz_doc
WHERE c_orgid=? AND c_spaceid=? AND c_lifecycle!=2 AND c_groupid!='' AND c_template=false
GROUP BY c_groupid
) AS x INNER JOIN dmz_doc AS d ON d.c_groupid=x.c_groupid AND d.c_versionorder=x.latestversion
)
GROUP BY c_categoryid, type
UNION ALL
SELECT 'users' AS type, c_refid AS categoryid, count(*) AS count
FROM dmz_permission
WHERE c_orgid=? AND c_location='category' AND c_refid IN
(SELECT c_refid FROM dmz_category WHERE c_orgid=? AND c_spaceid=?)
GROUP BY c_refid, type`),
ctx.OrgID, spaceID,
ctx.OrgID, spaceID, ctx.OrgID, spaceID,
ctx.OrgID, ctx.OrgID, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("select category summary for space %s", spaceID))
}
return
}
// GetDocumentCategoryMembership returns all space categories associated with given document.
func (s Store) GetDocumentCategoryMembership(ctx domain.RequestContext, documentID string) (c []category.Category, err error) {
c = []category.Category{}
err = s.Runtime.Db.Select(&c, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_name AS name, c_created AS created, c_revised AS revised
FROM dmz_category
WHERE c_orgid=? AND c_refid IN (SELECT c_categoryid FROM dmz_category_member WHERE c_orgid=? AND c_docid=?)`),
ctx.OrgID, ctx.OrgID, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select categories for document %s", documentID))
}
return
}
// GetSpaceCategoryMembership returns category/document associations within space.
func (s Store) GetSpaceCategoryMembership(ctx domain.RequestContext, spaceID string) (c []category.Member, err error) {
err = s.Runtime.Db.Select(&c, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_categoryid AS categoryid, c_docid AS documentid, c_created AS created, c_revised AS revised
FROM dmz_category_member
WHERE c_orgid=? AND c_spaceid=? AND c_spaceid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
UNION ALL
SELECT p.c_refid FROM dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid
WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='space'
AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
))
ORDER BY documentid`),
ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("select all category/document membership for space %s", spaceID))
}
return
}
// GetOrgCategoryMembership returns category/document associations within organization.
func (s Store) GetOrgCategoryMembership(ctx domain.RequestContext, userID string) (c []category.Member, err error) {
err = s.Runtime.Db.Select(&c, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_categoryid AS categoryid, c_docid AS documentid, c_created AS created, c_revised AS revised
FROM dmz_category_member
WHERE c_orgid=? AND c_spaceid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid IN
(SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
UNION ALL
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='space'
AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
))
ORDER BY documentid`),
ctx.OrgID, ctx.OrgID, ctx.OrgID, userID, ctx.OrgID, userID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("select all category/document membership for organization %s", ctx.OrgID))
}
return
}

View file

@ -42,7 +42,7 @@ type RequestContext struct {
Analytics bool
Active bool
Editor bool
Global bool
GlobalAdmin bool
ViewUsers bool
}

View file

@ -26,9 +26,10 @@ import (
"github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/conversion/store"
ls "github.com/documize/community/domain/conversion/store"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/activity"
"github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit"
@ -43,7 +44,7 @@ import (
var storageProvider StorageProvider
func init() {
storageProvider = new(store.LocalStorageProvider)
storageProvider = new(ls.LocalStorageProvider)
}
func (h *Handler) upload(w http.ResponseWriter, r *http.Request) (string, string, string) {
@ -166,12 +167,12 @@ func (h *Handler) convert(w http.ResponseWriter, r *http.Request, job, folderID
response.WriteJSON(w, nd)
}
func processDocument(ctx domain.RequestContext, r *env.Runtime, store *domain.Store, indexer indexer.Indexer, filename, job string, sp space.Space, fileResult *api.DocumentConversionResponse) (newDocument doc.Document, err error) {
func processDocument(ctx domain.RequestContext, r *env.Runtime, store *store.Store, indexer indexer.Indexer, filename, job string, sp space.Space, fileResult *api.DocumentConversionResponse) (newDocument doc.Document, err error) {
// Convert into database objects
document := convertFileResult(filename, fileResult)
document.Job = job
document.OrgID = ctx.OrgID
document.LabelID = sp.RefID
document.SpaceID = sp.RefID
document.UserID = ctx.UserID
documentID := uniqueid.Generate()
document.RefID = documentID
@ -193,16 +194,16 @@ func processDocument(ctx domain.RequestContext, r *env.Runtime, store *domain.St
p.OrgID = ctx.OrgID
p.DocumentID = documentID
p.Level = v.Level
p.Title = v.Title
p.Name = v.Title
p.Body = string(v.Body)
p.Sequence = float64(k+1) * 1024.0 // need to start above 0 to allow insertion before the first item
pageID := uniqueid.Generate()
p.RefID = pageID
p.ContentType = "wysiwyg"
p.PageType = "section"
p.Type = "section"
meta := page.Meta{}
meta.PageID = pageID
meta.SectionID = pageID
meta.RawBody = p.Body
meta.Config = "{}"
@ -245,7 +246,7 @@ func processDocument(ctx domain.RequestContext, r *env.Runtime, store *domain.St
}
store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: newDocument.LabelID,
SpaceID: newDocument.SpaceID,
DocumentID: newDocument.RefID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeCreated})
@ -278,13 +279,13 @@ func convertFileResult(filename string, fileResult *api.DocumentConversionRespon
document = doc.Document{}
document.RefID = ""
document.OrgID = ""
document.LabelID = ""
document.SpaceID = ""
document.Job = ""
document.Location = filename
if fileResult != nil {
if len(fileResult.Pages) > 0 {
document.Title = fileResult.Pages[0].Title
document.Name = fileResult.Pages[0].Title
document.Slug = stringutil.MakeSlug(fileResult.Pages[0].Title)
}
document.Excerpt = fileResult.Excerpt

View file

@ -16,14 +16,14 @@ import (
api "github.com/documize/community/core/convapi"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/store"
)
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
Indexer indexer.Indexer
}

View file

@ -14,6 +14,7 @@ package document
import (
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/category"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/page"
@ -64,7 +65,7 @@ func FilterCategoryProtected(docs []doc.Document, cats []category.Category, memb
}
// CopyDocument clones an existing document
func CopyDocument(ctx domain.RequestContext, s domain.Store, documentID string) (newDocumentID string, err error) {
func CopyDocument(ctx domain.RequestContext, s store.Store, documentID string) (newDocumentID string, err error) {
doc, err := s.Document.Get(ctx, documentID)
if err != nil {
err = errors.Wrap(err, "unable to fetch existing document")
@ -100,7 +101,7 @@ func CopyDocument(ctx domain.RequestContext, s domain.Store, documentID string)
pageID := uniqueid.Generate()
p.RefID = pageID
meta.PageID = pageID
meta.SectionID = pageID
meta.DocumentID = newDocumentID
m := page.NewPage{}

View file

@ -29,6 +29,7 @@ import (
"github.com/documize/community/domain/organization"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/activity"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/doc"
@ -43,7 +44,7 @@ import (
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
Indexer indexer.Indexer
}
@ -70,7 +71,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
return
}
if !permission.CanViewSpaceDocument(ctx, *h.Store, document.LabelID) {
if !permission.CanViewSpaceDocument(ctx, *h.Store, document.SpaceID) {
response.WriteForbiddenError(w)
return
}
@ -85,7 +86,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
}
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: document.LabelID,
SpaceID: document.SpaceID,
DocumentID: document.RefID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeRead})
@ -93,6 +94,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
if err != nil {
ctx.Transaction.Rollback()
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit()
@ -166,7 +168,7 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
}
// Sort by title.
sort.Sort(doc.ByTitle(documents))
sort.Sort(doc.ByName(documents))
// Remove documents that cannot be seen due to lack of
// category view/access permission.
@ -231,9 +233,9 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
if oldDoc.LabelID != d.LabelID {
if oldDoc.SpaceID != d.SpaceID {
h.Store.Category.RemoveDocumentCategories(ctx, d.RefID)
err = h.Store.Document.MoveActivity(ctx, documentID, oldDoc.LabelID, d.LabelID)
err = h.Store.Document.MoveActivity(ctx, documentID, oldDoc.SpaceID, d.SpaceID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -268,7 +270,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
// Record document being marked as archived.
if d.Lifecycle == workflow.LifecycleArchived {
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: d.LabelID,
SpaceID: d.SpaceID,
DocumentID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeArchived})
@ -277,7 +279,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
// Record document being marked as draft.
if d.Lifecycle == workflow.LifecycleDraft {
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: d.LabelID,
SpaceID: d.SpaceID,
DocumentID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeDraft})
@ -286,7 +288,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
// Record document being marked as live.
if d.Lifecycle == workflow.LifecycleLive {
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: d.LabelID,
SpaceID: d.SpaceID,
DocumentID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypePublished})
@ -340,7 +342,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
// If approval workflow then only approvers can delete page
if doc.Protection == workflow.ProtectionReview {
approvers, err := permission.GetUsersWithDocumentPermission(ctx, *h.Store, doc.LabelID, doc.RefID, pm.DocumentApprove)
approvers, err := permission.GetUsersWithDocumentPermission(ctx, *h.Store, doc.SpaceID, doc.RefID, pm.DocumentApprove)
if err != nil {
response.WriteForbiddenError(w)
h.Runtime.Log.Error(method, err)
@ -389,7 +391,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
// Draft actions are not logged
if doc.Lifecycle == workflow.LifecycleLive {
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: doc.LabelID,
SpaceID: doc.SpaceID,
DocumentID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeDeleted})
@ -458,7 +460,7 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
}
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: "",
SpaceID: "",
DocumentID: "",
Metadata: options.Keywords,
SourceType: activity.SourceTypeSearch,
@ -504,7 +506,7 @@ func (h *Handler) recordSearchActivity(ctx domain.RequestContext, q []search.Que
if _, isExisting := prev[q[i].DocumentID]; !isExisting {
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: q[i].SpaceID,
SpaceID: q[i].SpaceID,
DocumentID: q[i].DocumentID,
Metadata: keywords,
SourceType: activity.SourceTypeSearch,
@ -545,7 +547,7 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
return
}
if !permission.CanViewSpaceDocument(ctx, *h.Store, document.LabelID) {
if !permission.CanViewSpaceDocument(ctx, *h.Store, document.SpaceID) {
response.WriteForbiddenError(w)
return
}
@ -557,9 +559,10 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
}
// permissions
perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, document.LabelID)
perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, document.SpaceID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(perms) == 0 {
@ -570,6 +573,7 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
roles, err := h.Store.Permission.GetUserDocumentPermissions(ctx, document.RefID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(roles) == 0 {
@ -629,7 +633,7 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
if document.Lifecycle == workflow.LifecycleLive {
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: document.LabelID,
SpaceID: document.SpaceID,
DocumentID: document.RefID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeRead})

View file

@ -19,6 +19,7 @@ import (
"github.com/documize/community/domain"
"github.com/documize/community/domain/permission"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/page"
pm "github.com/documize/community/model/permission"
@ -39,7 +40,7 @@ type exportTOC struct {
}
// BuildExport generates self-enclosed HTML for content specified.
func BuildExport(ctx domain.RequestContext, s domain.Store, spec exportSpec) (html string, err error) {
func BuildExport(ctx domain.RequestContext, s store.Store, spec exportSpec) (html string, err error) {
export := strings.Builder{}
content := strings.Builder{}
toc := []exportTOC{}
@ -51,6 +52,8 @@ func BuildExport(ctx domain.RequestContext, s domain.Store, spec exportSpec) (ht
if e == nil {
content.WriteString(c)
toc = append(toc, t...)
} else {
fmt.Println("export.space", err)
}
}
@ -59,6 +62,8 @@ func BuildExport(ctx domain.RequestContext, s domain.Store, spec exportSpec) (ht
if e == nil {
content.WriteString(c)
toc = append(toc, t...)
} else {
fmt.Println("export.category", err)
}
case "document":
@ -66,6 +71,8 @@ func BuildExport(ctx domain.RequestContext, s domain.Store, spec exportSpec) (ht
if e == nil {
content.WriteString(c)
toc = append(toc, t...)
} else {
fmt.Println("export.document", err)
}
}
@ -114,7 +121,7 @@ func BuildExport(ctx domain.RequestContext, s domain.Store, spec exportSpec) (ht
}
// exportSpace returns documents exported.
func exportSpace(ctx domain.RequestContext, s domain.Store, spaceID string) (toc []exportTOC, export string, err error) {
func exportSpace(ctx domain.RequestContext, s store.Store, spaceID string) (toc []exportTOC, export string, err error) {
// Permission check.
if !permission.CanViewSpace(ctx, s, spaceID) {
return toc, "", nil
@ -153,7 +160,7 @@ func exportSpace(ctx domain.RequestContext, s domain.Store, spaceID string) (toc
for _, d := range docs {
docHTML, e := processDocument(ctx, s, d.RefID)
if e == nil && len(docHTML) > 0 {
toc = append(toc, exportTOC{ID: d.RefID, Entry: d.Title})
toc = append(toc, exportTOC{ID: d.RefID, Entry: d.Name})
b.WriteString(docHTML)
} else {
return toc, b.String(), err
@ -164,7 +171,7 @@ func exportSpace(ctx domain.RequestContext, s domain.Store, spaceID string) (toc
}
// exportCategory returns documents exported for selected categories.
func exportCategory(ctx domain.RequestContext, s domain.Store, spaceID string, category []string) (toc []exportTOC, export string, err error) {
func exportCategory(ctx domain.RequestContext, s store.Store, spaceID string, category []string) (toc []exportTOC, export string, err error) {
// Permission check.
if !permission.CanViewSpace(ctx, s, spaceID) {
return toc, "", nil
@ -221,7 +228,7 @@ func exportCategory(ctx domain.RequestContext, s domain.Store, spaceID string, c
for _, d := range exportDocs {
docHTML, e := processDocument(ctx, s, d.RefID)
if e == nil && len(docHTML) > 0 {
toc = append(toc, exportTOC{ID: d.RefID, Entry: d.Title})
toc = append(toc, exportTOC{ID: d.RefID, Entry: d.Name})
b.WriteString(docHTML)
} else {
return toc, b.String(), err
@ -232,7 +239,7 @@ func exportCategory(ctx domain.RequestContext, s domain.Store, spaceID string, c
}
// exportDocument returns documents for export.
func exportDocument(ctx domain.RequestContext, s domain.Store, spaceID string, document []string) (toc []exportTOC, export string, err error) {
func exportDocument(ctx domain.RequestContext, s store.Store, spaceID string, document []string) (toc []exportTOC, export string, err error) {
// Permission check.
if !permission.CanViewSpace(ctx, s, spaceID) {
return toc, "", nil
@ -274,7 +281,7 @@ func exportDocument(ctx domain.RequestContext, s domain.Store, spaceID string, d
if permission.CanViewDocument(ctx, s, d.RefID) {
docHTML, e := processDocument(ctx, s, d.RefID)
if e == nil && len(docHTML) > 0 {
toc = append(toc, exportTOC{ID: d.RefID, Entry: d.Title})
toc = append(toc, exportTOC{ID: d.RefID, Entry: d.Name})
b.WriteString(docHTML)
} else {
return toc, b.String(), err
@ -288,7 +295,7 @@ func exportDocument(ctx domain.RequestContext, s domain.Store, spaceID string, d
}
// processDocument writes out document as HTML content
func processDocument(ctx domain.RequestContext, s domain.Store, documentID string) (export string, err error) {
func processDocument(ctx domain.RequestContext, s store.Store, documentID string) (export string, err error) {
b := strings.Builder{}
// Permission check.
@ -325,7 +332,7 @@ func processDocument(ctx domain.RequestContext, s domain.Store, documentID strin
// Put out document name.
b.WriteString(fmt.Sprintf("<div class='export-doc-header' id='%s'>", doc.RefID))
b.WriteString("<div class='export-doc-title'>")
b.WriteString(doc.Title)
b.WriteString(doc.Name)
b.WriteString("</div>")
b.WriteString("<div class='export-doc-excerpt'>")
b.WriteString(doc.Excerpt)
@ -338,7 +345,7 @@ func processDocument(ctx domain.RequestContext, s domain.Store, documentID strin
b.WriteString(`<div class="document-structure">`)
b.WriteString(`<div class="page-header">`)
b.WriteString(fmt.Sprintf("<span class='page-number'>%s</span>", page.Numbering))
b.WriteString(fmt.Sprintf("<span class='page-title'>%s</span>", page.Title))
b.WriteString(fmt.Sprintf("<span class='page-title'>%s</span>", page.Name))
b.WriteString("</div>")
b.WriteString("</div>")

View file

@ -1,369 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package mysql
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/doc"
"github.com/pkg/errors"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
}
// Add inserts the given document record into the document table and audits that it has been done.
func (s Scope) Add(ctx domain.RequestContext, d doc.Document) (err error) {
d.OrgID = ctx.OrgID
d.Created = time.Now().UTC()
d.Revised = d.Created // put same time in both fields
_, err = ctx.Transaction.Exec(`
INSERT INTO document (refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, lifecycle, versioned, versionid, versionorder, groupid, created, revised)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
d.RefID, d.OrgID, d.LabelID, d.UserID, d.Job, d.Location, d.Title, d.Excerpt, d.Slug, d.Tags,
d.Template, d.Protection, d.Approval, d.Lifecycle, d.Versioned, d.VersionID, d.VersionOrder, d.GroupID, d.Created, d.Revised)
if err != nil {
err = errors.Wrap(err, "execute insert document")
}
return
}
// Get fetches the document record with the given id fromt the document table and audits that it has been got.
func (s Scope) Get(ctx domain.RequestContext, id string) (document doc.Document, err error) {
err = s.Runtime.Db.Get(&document, `
SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template,
protection, approval, lifecycle, versioned, versionid, versionorder, groupid, created, revised
FROM document
WHERE orgid=? and refid=?`,
ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "execute select document")
}
return
}
// DocumentMeta returns the metadata for a specified document.
func (s Scope) DocumentMeta(ctx domain.RequestContext, id string) (meta doc.DocumentMeta, err error) {
sqlViewers := `SELECT MAX(a.created) as created,
IFNULL(a.userid, '') AS userid, IFNULL(u.firstname, 'Anonymous') AS firstname, IFNULL(u.lastname, 'Viewer') AS lastname
FROM audit a LEFT JOIN user u ON a.userid=u.refid
WHERE a.orgid=? AND a.documentid=?
AND a.userid != '0' AND a.userid != ''
AND action='get-document'
GROUP BY a.userid ORDER BY MAX(a.created) DESC`
err = s.Runtime.Db.Select(&meta.Viewers, sqlViewers, ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("select document viewers %s", id))
return
}
sqlEdits := `SELECT a.created,
IFNULL(a.action, '') AS action, IFNULL(a.userid, '') AS userid, IFNULL(u.firstname, 'Anonymous') AS firstname, IFNULL(u.lastname, 'Viewer') AS lastname, IFNULL(a.pageid, '') AS pageid
FROM audit a LEFT JOIN user u ON a.userid=u.refid
WHERE a.orgid=? AND a.documentid=? AND a.userid != '0' AND a.userid != ''
AND (a.action='update-page' OR a.action='add-page' OR a.action='remove-page')
ORDER BY a.created DESC;`
err = s.Runtime.Db.Select(&meta.Editors, sqlEdits, ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("select document editors %s", id))
return
}
return
}
// GetBySpace returns a slice containing the documents for a given space.
//
// No attempt is made to hide documents that are protected by category
// permissions hence caller must filter as required.
//
// All versions of a document are returned, hence caller must
// decide what to do with them.
func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error) {
documents = []doc.Document{}
err = s.Runtime.Db.Select(&documents, `
SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template,
protection, approval, lifecycle, versioned, versionid, versionorder, groupid, created, revised
FROM document
WHERE orgid=? AND template=0 AND labelid IN (
SELECT refid FROM label WHERE orgid=? AND refid IN
(SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid=? AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=?
AND p.who='role' AND p.location='space' AND p.refid=? AND p.action='view' AND (r.userid=? OR r.userid='0')
))
)
ORDER BY title, versionorder`, ctx.OrgID, ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, spaceID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "select documents by space")
}
return
}
// TemplatesBySpace returns a slice containing the documents available as templates for given space.
func (s Scope) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents,
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template,
protection, approval, lifecycle, versioned, versionid, versionorder, groupid, created, revised
FROM document
WHERE orgid=? AND labelid=? AND template=1 AND lifecycle=1
AND labelid IN
(
SELECT refid FROM label WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0')
))
)
ORDER BY title`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
documents = []doc.Document{}
}
if err != nil {
err = errors.Wrap(err, "select space document templates")
}
return
}
// PublicDocuments returns a slice of SitemapDocument records
// linking to documents in public spaces.
// These documents can then be seen by search crawlers.
func (s Scope) PublicDocuments(ctx domain.RequestContext, orgID string) (documents []doc.SitemapDocument, err error) {
err = s.Runtime.Db.Select(&documents,
`SELECT d.refid as documentid, d.title as document, d.revised as revised, l.refid as folderid, l.label as folder
FROM document d LEFT JOIN label l ON l.refid=d.labelid
WHERE d.orgid=?
AND l.type=1
AND d.lifecycle=1
AND d.template=0`, orgID)
if err == sql.ErrNoRows {
err = nil
documents = []doc.SitemapDocument{}
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute GetPublicDocuments for org %s%s", orgID))
}
return
}
// Update changes the given document record to the new values, updates search information and audits the action.
func (s Scope) Update(ctx domain.RequestContext, document doc.Document) (err error) {
document.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec(`
UPDATE document
SET
labelid=:labelid, userid=:userid, job=:job, location=:location, title=:title, excerpt=:excerpt, slug=:slug, tags=:tags, template=:template,
protection=:protection, approval=:approval, lifecycle=:lifecycle, versioned=:versioned, versionid=:versionid, versionorder=:versionorder, groupid=:groupid, revised=:revised
WHERE orgid=:orgid AND refid=:refid`,
&document)
if err != nil {
err = errors.Wrap(err, "document.store.Update")
}
return
}
// UpdateGroup applies same values to all documents
// with the same group ID.
func (s Scope) UpdateGroup(ctx domain.RequestContext, d doc.Document) (err error) {
_, err = ctx.Transaction.Exec(`UPDATE document SET title=?, excerpt=? WHERE orgid=? AND groupid=?`,
d.Title, d.Excerpt, ctx.OrgID, d.GroupID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "document.store.UpdateTitle")
}
return
}
// ChangeDocumentSpace assigns the specified space to the document.
func (s Scope) ChangeDocumentSpace(ctx domain.RequestContext, document, space string) (err error) {
revised := time.Now().UTC()
_, err = ctx.Transaction.Exec("UPDATE document SET labelid=?, revised=? WHERE orgid=? AND refid=?",
space, revised, ctx.OrgID, document)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute change document space %s", document))
}
return
}
// MoveDocumentSpace changes the space for client's organization's documents which have space "id", to "move".
func (s Scope) MoveDocumentSpace(ctx domain.RequestContext, id, move string) (err error) {
_, err = ctx.Transaction.Exec("UPDATE document SET labelid=? WHERE orgid=? AND labelid=?",
move, ctx.OrgID, id)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute document space move %s", id))
}
return
}
// MoveActivity changes the space for all document activity records.
func (s Scope) MoveActivity(ctx domain.RequestContext, documentID, oldSpaceID, newSpaceID string) (err error) {
_, err = ctx.Transaction.Exec("UPDATE useractivity SET labelid=? WHERE orgid=? AND labelid=? AND documentid=?",
newSpaceID, ctx.OrgID, oldSpaceID, documentID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute document activity move %s", documentID))
}
return
}
// Delete removes the specified document.
// Remove document pages, revisions, attachments, updates the search subsystem.
func (s Scope) Delete(ctx domain.RequestContext, documentID string) (rows int64, err error) {
b := mysql.BaseQuery{}
rows, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM page WHERE documentid=\"%s\" AND orgid=\"%s\"", documentID, ctx.OrgID))
if err != nil {
return
}
_, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM revision WHERE documentid=\"%s\" AND orgid=\"%s\"", documentID, ctx.OrgID))
if err != nil {
return
}
_, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM attachment WHERE documentid=\"%s\" AND orgid=\"%s\"", documentID, ctx.OrgID))
if err != nil {
return
}
_, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM categorymember WHERE documentid=\"%s\" AND orgid=\"%s\"", documentID, ctx.OrgID))
if err != nil {
return
}
_, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM vote WHERE documentid=\"%s\" AND orgid=\"%s\"", documentID, ctx.OrgID))
if err != nil {
return
}
return b.DeleteConstrained(ctx.Transaction, "document", ctx.OrgID, documentID)
}
// DeleteBySpace removes all documents for given space.
// Remove document pages, revisions, attachments, updates the search subsystem.
func (s Scope) DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
b := mysql.BaseQuery{}
rows, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM page WHERE documentid IN (SELECT refid FROM document WHERE labelid=\"%s\" AND orgid=\"%s\")", spaceID, ctx.OrgID))
if err != nil {
return
}
_, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM revision WHERE documentid IN (SELECT refid FROM document WHERE labelid=\"%s\" AND orgid=\"%s\")", spaceID, ctx.OrgID))
if err != nil {
return
}
_, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM attachment WHERE documentid IN (SELECT refid FROM document WHERE labelid=\"%s\" AND orgid=\"%s\")", spaceID, ctx.OrgID))
if err != nil {
return
}
_, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM vote WHERE documentid IN (SELECT refid FROM document WHERE labelid=\"%s\" AND orgid=\"%s\")", spaceID, ctx.OrgID))
if err != nil {
return
}
return b.DeleteConstrained(ctx.Transaction, "document", ctx.OrgID, spaceID)
}
// GetVersions returns a slice containing the documents for a given space.
//
// No attempt is made to hide documents that are protected by category
// permissions hence caller must filter as required.
//
// All versions of a document are returned, hence caller must
// decide what to do with them.
func (s Scope) GetVersions(ctx domain.RequestContext, groupID string) (v []doc.Version, err error) {
v = []doc.Version{}
err = s.Runtime.Db.Select(&v, `
SELECT versionid, refid as documentid
FROM document
WHERE orgid=? AND groupid=?
ORDER BY versionorder`, ctx.OrgID, groupID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "document.store.GetVersions")
}
return
}
// Vote records document content vote.
// Any existing vote by the user is replaced.
func (s Scope) Vote(ctx domain.RequestContext, refID, orgID, documentID, userID string, vote int) (err error) {
b := mysql.BaseQuery{}
_, err = b.DeleteWhere(ctx.Transaction,
fmt.Sprintf("DELETE FROM vote WHERE orgid=\"%s\" AND documentid=\"%s\" AND voter=\"%s\"",
orgID, documentID, userID))
if err != nil {
s.Runtime.Log.Error("store.Vote", err)
}
_, err = ctx.Transaction.Exec(`INSERT INTO vote (refid, orgid, documentid, voter, vote) VALUES (?, ?, ?, ?, ?)`,
refID, orgID, documentID, userID, vote)
if err != nil {
err = errors.Wrap(err, "execute insert vote")
}
return
}

348
domain/document/store.go Normal file
View file

@ -0,0 +1,348 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package document
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/doc"
"github.com/pkg/errors"
)
// Store provides data access to space category information.
type Store struct {
store.Context
store.DocumentStorer
}
// Add inserts the given document record into the document table and audits that it has been done.
func (s Store) Add(ctx domain.RequestContext, d doc.Document) (err error) {
d.OrgID = ctx.OrgID
d.Created = time.Now().UTC()
d.Revised = d.Created // put same time in both fields
_, err = ctx.Transaction.Exec(s.Bind(`
INSERT INTO dmz_doc (c_refid, c_orgid, c_spaceid, c_userid, c_job, c_location, c_name, c_desc, c_slug, c_tags,
c_template, c_protection, c_approval, c_lifecycle, c_versioned, c_versionid, c_versionorder, c_groupid, c_created, c_revised)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
d.RefID, d.OrgID, d.SpaceID, d.UserID, d.Job, d.Location, d.Name, d.Excerpt, d.Slug, d.Tags,
d.Template, d.Protection, d.Approval, d.Lifecycle, d.Versioned, d.VersionID, d.VersionOrder, d.GroupID, d.Created, d.Revised)
if err != nil {
err = errors.Wrap(err, "execute insert document")
}
return
}
// Get fetches the document record with the given id fromt the document table and audits that it has been got.
func (s Store) Get(ctx domain.RequestContext, id string) (document doc.Document, err error) {
err = s.Runtime.Db.Get(&document, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_userid AS userid,
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
FROM dmz_doc
WHERE c_orgid=? AND c_refid=?`),
ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "execute select document")
}
return
}
// GetBySpace returns a slice containing the documents for a given space.
//
// No attempt is made to hide documents that are protected by category
// permissions hence caller must filter as required.
//
// All versions of a document are returned, hence caller must
// decide what to do with them.
func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error) {
documents = []doc.Document{}
err = s.Runtime.Db.Select(&documents, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_userid AS userid,
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
FROM dmz_doc
WHERE c_orgid=? AND c_template=false AND c_spaceid IN
(SELECT c_refid FROM dmz_space WHERE c_orgid=? AND c_refid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid=? AND c_refid IN
(SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
UNION ALL
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=?
AND p.c_who='role' AND p.c_location='space' AND p.c_refid=? AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
)
)
)
ORDER BY c_name, c_versionorder`),
ctx.OrgID, ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, spaceID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "select documents by space")
}
return
}
// TemplatesBySpace returns a slice containing the documents available as templates for given space.
func (s Store) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_userid AS userid,
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
FROM dmz_doc
WHERE c_orgid=? AND c_spaceid=? AND c_template=true AND c_lifecycle=1
AND c_spaceid IN
(SELECT c_refid FROM dmz_space WHERE c_orgid=? AND c_refid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
UNION ALL
SELECT p.c_refid FROM dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=? AND p.c_who='role' AND p.c_location='space' AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0'))
)
)
ORDER BY c_name`),
ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
documents = []doc.Document{}
}
if err != nil {
err = errors.Wrap(err, "select space document templates")
}
return
}
// PublicDocuments returns a slice of SitemapDocument records
// linking to documents in public spaces.
// These documents can then be seen by search crawlers.
func (s Store) PublicDocuments(ctx domain.RequestContext, orgID string) (documents []doc.SitemapDocument, err error) {
err = s.Runtime.Db.Select(&documents, s.Bind(`
SELECT d.c_refid AS documentid, d.c_name AS document, d.c_revised as revised, l.c_refid AS spaceid, l.c_name AS space
FROM dmz_doc d
LEFT JOIN dmz_space l ON l.c_refid=d.c_spaceid
WHERE d.c_orgid=? AND l.c_type=1 AND d.c_lifecycle=1 AND d.c_template=false`),
orgID)
if err == sql.ErrNoRows {
err = nil
documents = []doc.SitemapDocument{}
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute GetPublicDocuments for org %s", orgID))
}
return
}
/*
FROM document d LEFT JOIN label l ON l.refid=d.labelid
*/
// Update changes the given document record to the new values, updates search information and audits the action.
func (s Store) Update(ctx domain.RequestContext, document doc.Document) (err error) {
document.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec(s.Bind(`
UPDATE dmz_doc SET
c_spaceid=:spaceid, c_userid=:userid, c_job=:job, c_location=:location, c_name=:name,
c_desc=:excerpt, c_slug=:slug, c_tags=:tags, c_template=:template,
c_protection=:protection, c_approval=:approval, c_lifecycle=:lifecycle,
c_versioned=:versioned, c_versionid=:versionid, c_versionorder=:versionorder,
c_groupid=:groupid, c_revised=:revised
WHERE c_orgid=:orgid AND c_refid=:refid`),
&document)
if err != nil {
err = errors.Wrap(err, "document.store.Update")
}
return
}
// UpdateGroup applies same values to all documents with the same group ID.
func (s Store) UpdateGroup(ctx domain.RequestContext, d doc.Document) (err error) {
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_doc SET c_name=?, c_desc=? WHERE c_orgid=? AND c_groupid=?`),
d.Name, d.Excerpt, ctx.OrgID, d.GroupID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "document.store.UpdateGroup")
}
return
}
// ChangeDocumentSpace assigns the specified space to the document.
func (s Store) ChangeDocumentSpace(ctx domain.RequestContext, document, space string) (err error) {
revised := time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind("UPDATE dmz_doc SET c_spaceid=?, c_revised=? WHERE c_orgid=? AND c_refid=?"),
space, revised, ctx.OrgID, document)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute change document space %s", document))
}
return
}
// MoveDocumentSpace changes the space for client's organization's documents which have space "id", to "move".
func (s Store) MoveDocumentSpace(ctx domain.RequestContext, id, move string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind("UPDATE dmz_doc SET c_spaceid=? WHERE c_orgid=? AND c_spaceid=?"),
move, ctx.OrgID, id)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute document space move %s", id))
}
return
}
// MoveActivity changes the space for all document activity records.
func (s Store) MoveActivity(ctx domain.RequestContext, documentID, oldSpaceID, newSpaceID string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind("UPDATE dmz_user_activity SET c_spaceid=? WHERE c_orgid=? AND c_spaceid=? AND c_docid=?"),
newSpaceID, ctx.OrgID, oldSpaceID, documentID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute document activity move %s", documentID))
}
return
}
// Delete removes the specified document.
// Remove document pages, revisions, attachments, updates the search subsystem.
func (s Store) Delete(ctx domain.RequestContext, documentID string) (rows int64, err error) {
rows, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_section WHERE c_docid='%s' AND c_orgid='%s'", documentID, ctx.OrgID))
if err != nil {
return
}
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_section_revision WHERE c_docid='%s' AND c_orgid='%s'", documentID, ctx.OrgID))
if err != nil {
return
}
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_attachment WHERE c_docid='%s' AND c_orgid='%s'", documentID, ctx.OrgID))
if err != nil {
return
}
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_category_member WHERE c_docid='%s' AND c_orgid='%s'", documentID, ctx.OrgID))
if err != nil {
return
}
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_vote WHERE c_docid='%s' AND c_orgid='%s'", documentID, ctx.OrgID))
if err != nil {
return
}
return s.DeleteConstrained(ctx.Transaction, "dmz_doc", ctx.OrgID, documentID)
}
// DeleteBySpace removes all documents for given space.
// Remove document pages, revisions, attachments, updates the search subsystem.
func (s Store) DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
rows, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_section WHERE c_docid IN (SELECT c_refid FROM dmz_doc WHERE c_spaceid='%s' AND c_orgid='%s')", spaceID, ctx.OrgID))
if err != nil {
return
}
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_section_revision WHERE c_docid IN (SELECT c_refid FROM dmz_doc WHERE c_spaceid='%s' AND c_orgid='%s')", spaceID, ctx.OrgID))
if err != nil {
return
}
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_attachment WHERE c_docid IN (SELECT c_refid FROM dmz_doc WHERE c_spaceid='%s' AND c_orgid='%s')", spaceID, ctx.OrgID))
if err != nil {
return
}
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_vote WHERE c_docid IN (SELECT c_refid FROM dmz_doc WHERE c_spaceid='%s' AND c_orgid='%s')", spaceID, ctx.OrgID))
if err != nil {
return
}
return s.DeleteConstrained(ctx.Transaction, "dmz_doc", ctx.OrgID, spaceID)
}
// GetVersions returns a slice containing the documents for a given space.
//
// No attempt is made to hide documents that are protected by category
// permissions hence caller must filter as required.
//
// All versions of a document are returned, hence caller must
// decide what to do with them.
func (s Store) GetVersions(ctx domain.RequestContext, groupID string) (v []doc.Version, err error) {
v = []doc.Version{}
err = s.Runtime.Db.Select(&v, s.Bind(`
SELECT c_versionid AS versionid, c_refid As documentid
FROM dmz_doc
WHERE c_orgid=? AND c_groupid=?
ORDER BY c_versionorder`),
ctx.OrgID, groupID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "document.store.GetVersions")
}
return
}
// Vote records document content vote.
// Any existing vote by the user is replaced.
func (s Store) Vote(ctx domain.RequestContext, refID, orgID, documentID, userID string, vote int) (err error) {
_, err = s.DeleteWhere(ctx.Transaction,
fmt.Sprintf("DELETE FROM dmz_doc_vote WHERE c_orgid='%s' AND c_docid='%s' AND c_voter='%s'",
orgID, documentID, userID))
if err != nil {
s.Runtime.Log.Error("store.Vote", err)
}
_, err = ctx.Transaction.Exec(s.Bind(`INSERT INTO dmz_doc_vote (c_refid, c_orgid, c_docid, c_voter, c_vote) VALUES (?, ?, ?, ?, ?)`),
refID, orgID, documentID, userID, vote)
if err != nil {
err = errors.Wrap(err, "execute insert vote")
}
return
}

View file

@ -21,6 +21,7 @@ import (
"github.com/documize/community/core/response"
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/group"
)
@ -28,7 +29,7 @@ import (
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
}
// Add saves new user group.
@ -192,6 +193,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
g, err := h.Store.Group.Get(ctx, groupID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}

View file

@ -1,168 +0,0 @@
// Copyright 2018 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package mysql
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/group"
"github.com/pkg/errors"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
}
// Add inserts new user group into store.
func (s Scope) Add(ctx domain.RequestContext, g group.Group) (err error) {
g.Created = time.Now().UTC()
g.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec("INSERT INTO role (refid, orgid, role, purpose, created, revised) VALUES (?, ?, ?, ?, ?, ?)",
g.RefID, g.OrgID, g.Name, g.Purpose, g.Created, g.Revised)
if err != nil {
err = errors.Wrap(err, "insert group")
}
return
}
// Get returns requested group.
func (s Scope) Get(ctx domain.RequestContext, refID string) (g group.Group, err error) {
err = s.Runtime.Db.Get(&g,
`SELECT id, refid, orgid, role as name, purpose, created, revised FROM role WHERE orgid=? AND refid=?`,
ctx.OrgID, refID)
if err != nil {
err = errors.Wrap(err, "select group")
}
return
}
// GetAll returns all user groups for current orgID.
func (s Scope) GetAll(ctx domain.RequestContext) (groups []group.Group, err error) {
groups = []group.Group{}
err = s.Runtime.Db.Select(&groups,
`SELECT a.id, a.refid, a.orgid, a.role as name, a.purpose, a.created, a.revised, COUNT(b.roleid) AS members
FROM role a
LEFT JOIN rolemember b ON a.refid=b.roleid
WHERE a.orgid=?
GROUP BY a.id, a.refid, a.orgid, a.role, a.purpose, a.created, a.revised
ORDER BY a.role`,
ctx.OrgID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "select groups")
}
return
}
// Update group name and description.
func (s Scope) Update(ctx domain.RequestContext, g group.Group) (err error) {
g.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec("UPDATE role SET role=?, purpose=?, revised=? WHERE orgid=? AND refid=?",
g.Name, g.Purpose, g.Revised, ctx.OrgID, g.RefID)
if err != nil {
err = errors.Wrap(err, "update group")
}
return
}
// Delete removes group from store.
func (s Scope) Delete(ctx domain.RequestContext, refID string) (rows int64, err error) {
b := mysql.BaseQuery{}
b.DeleteConstrained(ctx.Transaction, "role", ctx.OrgID, refID)
return b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM rolemember WHERE orgid=\"%s\" AND roleid=\"%s\"", ctx.OrgID, refID))
}
// GetGroupMembers returns all user associated with given group.
func (s Scope) GetGroupMembers(ctx domain.RequestContext, groupID string) (members []group.Member, err error) {
members = []group.Member{}
err = s.Runtime.Db.Select(&members,
`SELECT a.id, a.orgid, a.roleid, a.userid,
IFNULL(b.firstname, '') as firstname, IFNULL(b.lastname, '') as lastname
FROM rolemember a
LEFT JOIN user b ON b.refid=a.userid
WHERE a.orgid=? AND a.roleid=?
ORDER BY b.firstname, b.lastname`,
ctx.OrgID, groupID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "select members")
}
return
}
// JoinGroup adds user to group.
func (s Scope) JoinGroup(ctx domain.RequestContext, groupID, userID string) (err error) {
_, err = ctx.Transaction.Exec("INSERT INTO rolemember (orgid, roleid, userid) VALUES (?, ?, ?)", ctx.OrgID, groupID, userID)
if err != nil {
err = errors.Wrap(err, "insert group member")
}
return
}
// LeaveGroup removes user from group.
func (s Scope) LeaveGroup(ctx domain.RequestContext, groupID, userID string) (err error) {
b := mysql.BaseQuery{}
_, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM rolemember WHERE orgid=\"%s\" AND roleid=\"%s\" AND userid=\"%s\"", ctx.OrgID, groupID, userID))
if err != nil {
err = errors.Wrap(err, "clear group member")
}
return
}
// GetMembers returns members for every group.
// Useful when you need to bulk fetch membership records
// for subsequent processing.
func (s Scope) GetMembers(ctx domain.RequestContext) (r []group.Record, err error) {
r = []group.Record{}
err = s.Runtime.Db.Select(&r,
`SELECT a.id, a.orgid, a.roleid, a.userid, b.role as name, b.purpose
FROM rolemember a, role b
WHERE a.orgid=? AND a.roleid=b.refid
ORDER BY a.userid`,
ctx.OrgID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "select group members")
}
return
}

181
domain/group/store.go Normal file
View file

@ -0,0 +1,181 @@
// Copyright 2018 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package group
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/group"
"github.com/pkg/errors"
)
// Store provides data access to space category information.
type Store struct {
store.Context
store.DocumentStorer
}
// Add inserts new user group into store.
func (s Store) Add(ctx domain.RequestContext, g group.Group) (err error) {
g.Created = time.Now().UTC()
g.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_group (c_refid, c_orgid, c_name, c_desc, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?)"),
g.RefID, g.OrgID, g.Name, g.Purpose, g.Created, g.Revised)
if err != nil {
err = errors.Wrap(err, "insert group")
}
return
}
// Get returns requested group.
func (s Store) Get(ctx domain.RequestContext, refID string) (g group.Group, err error) {
err = s.Runtime.Db.Get(&g, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_name AS name, c_desc AS purpose, c_created AS created, c_revised AS revised
FROM dmz_group
WHERE c_orgid=? AND c_refid=?`),
ctx.OrgID, refID)
if err != nil {
err = errors.Wrap(err, "select group")
}
return
}
// GetAll returns all user groups for current orgID.
func (s Store) GetAll(ctx domain.RequestContext) (groups []group.Group, err error) {
groups = []group.Group{}
err = s.Runtime.Db.Select(&groups, s.Bind(`
SELECT a.id, a.c_refid AS refid, a.c_orgid AS orgid, a.c_name AS name, a.c_desc AS purpose, a.c_created AS created, a.c_revised AS revised,
COUNT(b.c_groupid) AS members
FROM dmz_group a
LEFT JOIN dmz_group_member b ON a.c_refid=b.c_groupid
WHERE a.c_orgid=?
GROUP BY a.id, a.c_refid, a.c_orgid, a.c_name, a.c_desc, a.c_created, a.c_revised
ORDER BY a.c_name`),
ctx.OrgID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "select groups")
}
return
}
// Update group name and description.
func (s Store) Update(ctx domain.RequestContext, g group.Group) (err error) {
g.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_group SET
c_name=?, c_desc=?, c_revised=?
WHERE c_orgid=? AND c_refid=?`),
g.Name, g.Purpose, g.Revised, ctx.OrgID, g.RefID)
if err != nil {
err = errors.Wrap(err, "update group")
}
return
}
// Delete removes group from store.
func (s Store) Delete(ctx domain.RequestContext, refID string) (rows int64, err error) {
_, err = s.DeleteConstrained(ctx.Transaction, "dmz_group", ctx.OrgID, refID)
if err != nil {
return
}
return s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_group_member WHERE c_orgid='%s' AND c_groupid='%s'", ctx.OrgID, refID))
}
// GetGroupMembers returns all user associated with given group.
func (s Store) GetGroupMembers(ctx domain.RequestContext, groupID string) (members []group.Member, err error) {
members = []group.Member{}
err = s.Runtime.Db.Select(&members, s.Bind(`
SELECT a.id, a.c_orgid AS orgid, a.c_groupid AS groupid, a.c_userid AS userid,
COALESCE(b.c_firstname, '') as firstname, COALESCE(b.c_lastname, '') as lastname
FROM dmz_group_member a
LEFT JOIN dmz_user b ON b.c_refid=a.c_userid
WHERE a.c_orgid=? AND a.c_groupid=?
ORDER BY b.c_firstname, b.c_lastname`),
ctx.OrgID, groupID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "select members")
}
return
}
// JoinGroup adds user to group.
func (s Store) JoinGroup(ctx domain.RequestContext, groupID, userID string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_group_member (c_orgid, c_groupid, c_userid) VALUES (?, ?, ?)"),
ctx.OrgID, groupID, userID)
if err != nil {
err = errors.Wrap(err, "insert group member")
}
return
}
// LeaveGroup removes user from group.
func (s Store) LeaveGroup(ctx domain.RequestContext, groupID, userID string) (err error) {
_, err = s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_group_member WHERE c_orgid='%s' AND c_groupid='%s' AND c_userid='%s'",
ctx.OrgID, groupID, userID))
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "clear group member")
}
return
}
// GetMembers returns members for every group.
// Useful when you need to bulk fetch membership records
// for subsequent processing.
func (s Store) GetMembers(ctx domain.RequestContext) (r []group.Record, err error) {
r = []group.Record{}
err = s.Runtime.Db.Select(&r, s.Bind(`
SELECT a.id, a.c_orgid AS orgid, a.c_groupid AS groupid, a.c_userid AS userid,
b.c_name As name, b.c_desc AS purpose
FROM dmz_group_member a, dmz_group b
WHERE a.c_orgid=? AND a.c_groupid=b.c_refid
ORDER BY a.c_userid`),
ctx.OrgID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "select group members")
}
return
}

View file

@ -22,6 +22,7 @@ import (
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/permission"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/attachment"
"github.com/documize/community/model/link"
"github.com/documize/community/model/page"
@ -30,7 +31,7 @@ import (
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
}
// GetLinkCandidates returns references to documents/sections/attachments.
@ -80,11 +81,11 @@ func (h *Handler) GetLinkCandidates(w http.ResponseWriter, r *http.Request) {
if p.RefID != pageID {
c := link.Candidate{
RefID: uniqueid.Generate(),
FolderID: folderID,
SpaceID: folderID,
DocumentID: documentID,
TargetID: p.RefID,
LinkType: p.PageType,
Title: p.Title,
LinkType: p.Type,
Title: p.Name,
}
pc = append(pc, c)
}
@ -108,7 +109,7 @@ func (h *Handler) GetLinkCandidates(w http.ResponseWriter, r *http.Request) {
for _, f := range files {
c := link.Candidate{
RefID: uniqueid.Generate(),
FolderID: folderID,
SpaceID: folderID,
DocumentID: documentID,
TargetID: f.RefID,
LinkType: "file",

View file

@ -60,7 +60,7 @@ func getLink(t html.Token) (ok bool, link link.Link) {
case "data-link-id":
link.RefID = strings.TrimSpace(a.Val)
case "data-link-space-id":
link.FolderID = strings.TrimSpace(a.Val)
link.SpaceID = strings.TrimSpace(a.Val)
case "data-link-target-document-id":
link.TargetDocumentID = strings.TrimSpace(a.Val)
case "data-link-target-id":

View file

@ -1,281 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package mysql
import (
"database/sql"
"fmt"
"strings"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/link"
"github.com/pkg/errors"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
}
// Add inserts wiki-link into the store.
// These links exist when content references another document or content.
func (s Scope) Add(ctx domain.RequestContext, l link.Link) (err error) {
l.Created = time.Now().UTC()
l.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec("INSERT INTO link (refid, orgid, folderid, userid, sourcedocumentid, sourcepageid, targetdocumentid, targetid, externalid, linktype, orphan, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
l.RefID, l.OrgID, l.FolderID, l.UserID, l.SourceDocumentID, l.SourcePageID, l.TargetDocumentID, l.TargetID, l.ExternalID, l.LinkType, l.Orphan, l.Created, l.Revised)
if err != nil {
err = errors.Wrap(err, "execute link insert")
}
return
}
// GetDocumentOutboundLinks returns outbound links for specified document.
func (s Scope) GetDocumentOutboundLinks(ctx domain.RequestContext, documentID string) (links []link.Link, err error) {
err = s.Runtime.Db.Select(&links,
`select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.externalid, l.linktype, l.orphan, l.created, l.revised
FROM link l
WHERE l.orgid=? AND l.sourcedocumentid=?`,
ctx.OrgID,
documentID)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "select document oubound links")
return
}
if len(links) == 0 {
links = []link.Link{}
}
return
}
// GetPageLinks returns outbound links for specified page in document.
func (s Scope) GetPageLinks(ctx domain.RequestContext, documentID, pageID string) (links []link.Link, err error) {
err = s.Runtime.Db.Select(&links,
`select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.externalid, l.linktype, l.orphan, l.created, l.revised
FROM link l
WHERE l.orgid=? AND l.sourcedocumentid=? AND l.sourcepageid=?`,
ctx.OrgID,
documentID,
pageID)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "get page links")
return
}
if len(links) == 0 {
links = []link.Link{}
}
return
}
// MarkOrphanDocumentLink marks all link records referencing specified document.
func (s Scope) MarkOrphanDocumentLink(ctx domain.RequestContext, documentID string) (err error) {
revised := time.Now().UTC()
_, err = ctx.Transaction.Exec("UPDATE link SET orphan=1, revised=? WHERE linktype='document' AND orgid=? AND targetdocumentid=?",
revised, ctx.OrgID, documentID)
if err != nil {
err = errors.Wrap(err, "mark link as orphan")
}
return
}
// MarkOrphanPageLink marks all link records referencing specified page.
func (s Scope) MarkOrphanPageLink(ctx domain.RequestContext, pageID string) (err error) {
revised := time.Now().UTC()
_, err = ctx.Transaction.Exec("UPDATE link SET orphan=1, revised=? WHERE linktype='section' AND orgid=? AND targetid=?", revised, ctx.OrgID, pageID)
if err != nil {
err = errors.Wrap(err, "mark orphan page link")
}
return
}
// MarkOrphanAttachmentLink marks all link records referencing specified attachment.
func (s Scope) MarkOrphanAttachmentLink(ctx domain.RequestContext, attachmentID string) (err error) {
revised := time.Now().UTC()
_, err = ctx.Transaction.Exec("UPDATE link SET orphan=1, revised=? WHERE linktype='file' AND orgid=? AND targetid=?",
revised, ctx.OrgID, attachmentID)
if err != nil {
err = errors.Wrap(err, "mark orphan attachment link")
}
return
}
// DeleteSourcePageLinks removes saved links for given source.
func (s Scope) DeleteSourcePageLinks(ctx domain.RequestContext, pageID string) (rows int64, err error) {
b := mysql.BaseQuery{}
return b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM link WHERE orgid=\"%s\" AND sourcepageid=\"%s\"", ctx.OrgID, pageID))
}
// DeleteSourceDocumentLinks removes saved links for given document.
func (s Scope) DeleteSourceDocumentLinks(ctx domain.RequestContext, documentID string) (rows int64, err error) {
b := mysql.BaseQuery{}
return b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM link WHERE orgid=\"%s\" AND sourcedocumentid=\"%s\"", ctx.OrgID, documentID))
}
// DeleteLink removes saved link from the store.
func (s Scope) DeleteLink(ctx domain.RequestContext, id string) (rows int64, err error) {
b := mysql.BaseQuery{}
return b.DeleteConstrained(ctx.Transaction, "link", ctx.OrgID, id)
}
// SearchCandidates returns matching documents, sections and attachments using keywords.
func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (docs []link.Candidate,
pages []link.Candidate, attachments []link.Candidate, err error) {
// find matching documents
temp := []link.Candidate{}
keywords = strings.TrimSpace(strings.ToLower(keywords))
likeQuery := "LOWER(title) LIKE '%" + keywords + "%'"
err = s.Runtime.Db.Select(&temp, `
SELECT d.refid as documentid, d. labelid as folderid, d.title, l.label as context
FROM document d LEFT JOIN label l ON d.labelid=l.refid WHERE l.orgid=? AND `+likeQuery+`
AND d.labelid IN
(
SELECT refid FROM label WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role'
AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0')
))
)
ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err != nil {
err = errors.Wrap(err, "execute search links 1")
return
}
for _, r := range temp {
c := link.Candidate{
RefID: uniqueid.Generate(),
FolderID: r.FolderID,
DocumentID: r.DocumentID,
TargetID: r.DocumentID,
LinkType: "document",
Title: r.Title,
Context: r.Context,
}
docs = append(docs, c)
}
// find matching sections
likeQuery = "LOWER(p.title) LIKE '%" + keywords + "%'"
temp = []link.Candidate{}
err = s.Runtime.Db.Select(&temp,
`SELECT p.refid as targetid, p.documentid as documentid, p.title as title, p.pagetype as linktype, d.title as context, d.labelid as folderid
FROM page p LEFT JOIN document d ON d.refid=p.documentid WHERE p.orgid=? AND `+likeQuery+`
AND d.labelid IN
(
SELECT refid FROM label WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role'
AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0')
))
)
ORDER BY p.title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err != nil {
err = errors.Wrap(err, "execute search links 2")
return
}
for _, r := range temp {
c := link.Candidate{
RefID: uniqueid.Generate(),
FolderID: r.FolderID,
DocumentID: r.DocumentID,
TargetID: r.TargetID,
LinkType: r.LinkType,
Title: r.Title,
Context: r.Context,
}
pages = append(pages, c)
}
// find matching attachments
likeQuery = "LOWER(a.filename) LIKE '%" + keywords + "%'"
temp = []link.Candidate{}
err = s.Runtime.Db.Select(&temp,
`SELECT a.refid as targetid, a.documentid as documentid, a.filename as title, a.extension as context, d.labelid as folderid
FROM attachment a LEFT JOIN document d ON d.refid=a.documentid WHERE a.orgid=? AND `+likeQuery+`
AND d.labelid IN
(
SELECT refid FROM label WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role'
AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0')
))
)
ORDER BY a.filename`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err != nil {
err = errors.Wrap(err, "execute search links 3")
return
}
for _, r := range temp {
c := link.Candidate{
RefID: uniqueid.Generate(),
FolderID: r.FolderID,
DocumentID: r.DocumentID,
TargetID: r.TargetID,
LinkType: "file",
Title: r.Title,
Context: r.Context,
}
attachments = append(attachments, c)
}
if len(docs) == 0 {
docs = []link.Candidate{}
}
if len(pages) == 0 {
pages = []link.Candidate{}
}
if len(attachments) == 0 {
attachments = []link.Candidate{}
}
return
}

278
domain/link/store.go Normal file
View file

@ -0,0 +1,278 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package link
import (
"database/sql"
"fmt"
"strings"
"time"
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/link"
"github.com/pkg/errors"
)
// Store provides data access to space category information.
type Store struct {
store.Context
store.LinkStorer
}
// Add inserts wiki-link into the store.
// These links exist when content references another document or content.
func (s Store) Add(ctx domain.RequestContext, l link.Link) (err error) {
l.Created = time.Now().UTC()
l.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_doc_link (c_refid, c_orgid, c_spaceid, c_userid, c_sourcedocid, c_sourcesectionid, c_targetdocid, c_targetid, c_externalid, c_type, c_orphan, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
l.RefID, l.OrgID, l.SpaceID, l.UserID, l.SourceDocumentID, l.SourceSectionID, l.TargetDocumentID, l.TargetID, l.ExternalID, l.LinkType, l.Orphan, l.Created, l.Revised)
if err != nil {
err = errors.Wrap(err, "execute link insert")
}
return
}
// GetDocumentOutboundLinks returns outbound links for specified document.
func (s Store) GetDocumentOutboundLinks(ctx domain.RequestContext, documentID string) (links []link.Link, err error) {
err = s.Runtime.Db.Select(&links, s.Bind(`
select c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_userid AS userid,
c_sourcedocid AS sourcedocumentid, c_sourcesectionid AS sourcesectionid,
c_targetdocid AS targetdocumentid, c_targetid AS targetid, c_externalid AS externalid,
c_type as linktype, c_orphan As orphan, c_created AS created, c_revised AS revised
FROM dmz_doc_link
WHERE c_orgid=? AND c_sourcedocid=?`),
ctx.OrgID, documentID)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "select document oubound links")
return
}
if len(links) == 0 {
links = []link.Link{}
}
return
}
// GetPageLinks returns outbound links for specified page in document.
func (s Store) GetPageLinks(ctx domain.RequestContext, documentID, pageID string) (links []link.Link, err error) {
err = s.Runtime.Db.Select(&links, s.Bind(`
select c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_userid AS userid,
c_sourcedocid AS sourcedocumentid, c_sourcesectionid AS sourcesectionid,
c_targetdocid AS targetdocumentid, c_targetid AS targetid, c_externalid AS externalid,
c_type as linktype, c_orphan As orphan, c_created AS created, c_revised AS revised
FROM dmz_doc_link
WHERE c_orgid=? AND c_sourcedocid=? AND c_sourcesectionid=?`),
ctx.OrgID, documentID, pageID)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "get page links")
return
}
if len(links) == 0 {
links = []link.Link{}
}
return
}
// MarkOrphanDocumentLink marks all link records referencing specified document.
func (s Store) MarkOrphanDocumentLink(ctx domain.RequestContext, documentID string) (err error) {
revised := time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_doc_link SET
c_orphan=true, c_revised=?
WHERE c_type='document' AND c_orgid=? AND c_targetdocid=?`),
revised, ctx.OrgID, documentID)
if err != nil {
err = errors.Wrap(err, "mark link as orphan")
}
return
}
// MarkOrphanPageLink marks all link records referencing specified page.
func (s Store) MarkOrphanPageLink(ctx domain.RequestContext, pageID string) (err error) {
revised := time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_doc_link SET
c_orphan=true, c_revised=?
WHERE c_type='section' AND c_orgid=? AND c_targetid=?`),
revised, ctx.OrgID, pageID)
if err != nil {
err = errors.Wrap(err, "mark orphan page link")
}
return
}
// MarkOrphanAttachmentLink marks all link records referencing specified attachment.
func (s Store) MarkOrphanAttachmentLink(ctx domain.RequestContext, attachmentID string) (err error) {
revised := time.Now().UTC()
_, err = ctx.Transaction.Exec(s.Bind(`UPDATE dmz_doc_link SET
c_orphan=true, c_revised=?
WHERE c_type='file' AND c_orgid=? AND c_targetid=?`),
revised, ctx.OrgID, attachmentID)
if err != nil {
err = errors.Wrap(err, "mark orphan attachment link")
}
return
}
// DeleteSourcePageLinks removes saved links for given source.
func (s Store) DeleteSourcePageLinks(ctx domain.RequestContext, pageID string) (rows int64, err error) {
return s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_link WHERE c_orgid='%s' AND c_sourcesectionid='%s'", ctx.OrgID, pageID))
}
// DeleteSourceDocumentLinks removes saved links for given document.
func (s Store) DeleteSourceDocumentLinks(ctx domain.RequestContext, documentID string) (rows int64, err error) {
return s.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM dmz_doc_link WHERE c_orgid='%s' AND c_sourcedocid='%s'", ctx.OrgID, documentID))
}
// DeleteLink removes saved link from the store.
func (s Store) DeleteLink(ctx domain.RequestContext, id string) (rows int64, err error) {
return s.DeleteConstrained(ctx.Transaction, "dmz_doc_link", ctx.OrgID, id)
}
// SearchCandidates returns matching documents, sections and attachments using keywords.
func (s Store) SearchCandidates(ctx domain.RequestContext, keywords string) (docs []link.Candidate,
pages []link.Candidate, attachments []link.Candidate, err error) {
// find matching documents
temp := []link.Candidate{}
keywords = strings.TrimSpace(strings.ToLower(keywords))
likeQuery := "LOWER(d.c_name) LIKE '%" + keywords + "%'"
err = s.Runtime.Db.Select(&temp, s.Bind(`
SELECT d.c_refid AS documentid, d.c_spaceid AS spaceid, d.c_name AS title, l.c_name AS context
FROM dmz_doc d LEFT JOIN dmz_space l ON d.c_spaceid=l.c_refid
WHERE l.c_orgid=? AND `+likeQuery+` AND d.c_spaceid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid IN
(SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
UNION ALL
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=? AND p.c_who='role'
AND p.c_location='space' AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
)
)
ORDER BY title`),
ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err != nil {
err = errors.Wrap(err, "execute search links 1")
return
}
for _, r := range temp {
c := link.Candidate{
RefID: uniqueid.Generate(),
SpaceID: r.SpaceID,
DocumentID: r.DocumentID,
TargetID: r.DocumentID,
LinkType: "document",
Title: r.Title,
Context: r.Context,
}
docs = append(docs, c)
}
// find matching sections
likeQuery = "LOWER(p.c_name) LIKE '%" + keywords + "%'"
temp = []link.Candidate{}
err = s.Runtime.Db.Select(&temp, s.Bind(`
SELECT p.c_refid AS targetid, p.c_docid AS documentid, p.c_name AS title,
p.c_type AS linktype, d.c_name AS context, d.c_spaceid AS spaceid
FROM dmz_section p LEFT JOIN dmz_doc d ON d.c_refid=p.c_docid
WHERE p.c_orgid=? AND `+likeQuery+` AND d.c_spaceid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid IN
(SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
UNION ALL
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=? AND p.c_who='role'
AND p.c_location='space' AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
)
)
ORDER BY title`),
ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err != nil {
err = errors.Wrap(err, "execute search links 2")
return
}
for _, r := range temp {
c := link.Candidate{
RefID: uniqueid.Generate(),
SpaceID: r.SpaceID,
DocumentID: r.DocumentID,
TargetID: r.TargetID,
LinkType: r.LinkType,
Title: r.Title,
Context: r.Context,
}
pages = append(pages, c)
}
// find matching attachments
likeQuery = "LOWER(a.c_filename) LIKE '%" + keywords + "%'"
temp = []link.Candidate{}
err = s.Runtime.Db.Select(&temp, s.Bind(`
SELECT a.c_refid AS targetid, a.c_docid AS documentid, a.c_filename AS title, a.c_extension AS context, d.c_spaceid AS spaceid
FROM dmz_doc_attachment a LEFT JOIN dmz_doc d ON d.c_refid=a.c_docid
WHERE a.c_orgid=? AND `+likeQuery+` AND d.c_spaceid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid IN
(SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space' AND c_action='view'
UNION ALL
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=? AND p.c_who='role'
AND p.c_location='space' AND p.c_action='view' AND (r.c_userid=? OR r.c_userid='0')
)
)
ORDER BY a.c_filename`),
ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err != nil {
err = errors.Wrap(err, "execute search links 3")
return
}
for _, r := range temp {
c := link.Candidate{
RefID: uniqueid.Generate(),
SpaceID: r.SpaceID,
DocumentID: r.DocumentID,
TargetID: r.TargetID,
LinkType: "file",
Title: r.Title,
Context: r.Context,
}
attachments = append(attachments, c)
}
if len(docs) == 0 {
docs = []link.Candidate{}
}
if len(pages) == 0 {
pages = []link.Candidate{}
}
if len(attachments) == 0 {
attachments = []link.Candidate{}
}
return
}

View file

@ -20,13 +20,14 @@ import (
"github.com/documize/community/domain"
"github.com/documize/community/domain/setting"
ds "github.com/documize/community/domain/smtp"
"github.com/documize/community/domain/store"
"github.com/documize/community/server/web"
)
// Mailer provides emailing facilities
type Mailer struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
Context domain.RequestContext
Config ds.Config
Dialer *mail.Dialer

View file

@ -62,7 +62,7 @@ func (m *Mailer) InviteNewUser(recipient, inviterName, inviterEmail, url, userna
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
}
if !ok {
m.Runtime.Log.Info(fmt.Sprintf("%s unable to send email"))
m.Runtime.Log.Info(fmt.Sprintf("%s unable to send email", method))
}
}

View file

@ -15,6 +15,7 @@ import (
"bytes"
"fmt"
"net/http"
"strings"
"text/template"
"github.com/documize/community/core/env"
@ -24,6 +25,7 @@ import (
"github.com/documize/community/domain/auth"
"github.com/documize/community/domain/organization"
indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/org"
"github.com/documize/community/model/space"
@ -32,7 +34,7 @@ import (
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
Indexer indexer.Indexer
}
@ -52,14 +54,16 @@ func (h *Handler) Meta(w http.ResponseWriter, r *http.Request) {
data.Title = org.Title
data.Message = org.Message
data.AllowAnonymousAccess = org.AllowAnonymousAccess
data.AuthProvider = org.AuthProvider
data.AuthProvider = strings.TrimSpace(org.AuthProvider)
data.AuthConfig = org.AuthConfig
data.MaxTags = org.MaxTags
data.Version = h.Runtime.Product.Version
data.Edition = h.Runtime.Product.License.Edition
data.Revision = h.Runtime.Product.Revision
data.Edition = h.Runtime.Product.Edition
data.Valid = h.Runtime.Product.License.Valid
data.ConversionEndpoint = org.ConversionEndpoint
data.License = h.Runtime.Product.License
data.Storage = h.Runtime.StoreProvider.Type()
// Strip secrets
data.AuthConfig = auth.StripAuthSecrets(h.Runtime, org.AuthProvider, org.AuthConfig)
@ -91,23 +95,24 @@ func (h *Handler) RobotsTxt(w http.ResponseWriter, r *http.Request) {
// Anonymous access would mean we allow bots to crawl.
if o.AllowAnonymousAccess {
sitemap := ctx.GetAppURL("sitemap.xml")
robots = fmt.Sprintf(
`User-agent: *
Disallow: /settings/
Disallow: /settings/*
Disallow: /profile/
Disallow: /profile/*
Disallow: /auth/login/
Disallow: /auth/login/
Disallow: /auth/logout/
Disallow: /auth/logout/*
Disallow: /auth/reset/*
Disallow: /auth/reset/*
Disallow: /auth/sso/
Disallow: /auth/sso/*
Disallow: /share
Disallow: /share/*
Sitemap: %s`, sitemap)
robots = fmt.Sprintf(`User-agent: *
Disallow: /settings/
Disallow: /settings/*
Disallow: /profile/
Disallow: /profile/*
Disallow: /auth/login/
Disallow: /auth/login/
Disallow: /auth/logout/
Disallow: /auth/logout/*
Disallow: /auth/reset/*
Disallow: /auth/reset/*
Disallow: /auth/sso/
Disallow: /auth/sso/*
Disallow: /auth/*
Disallow: /auth/**
Disallow: /share
Disallow: /share/*
Sitemap: %s`, sitemap)
}
response.WriteBytes(w, []byte(robots))
@ -166,7 +171,7 @@ func (h *Handler) Sitemap(w http.ResponseWriter, r *http.Request) {
for _, document := range documents {
var item sitemapItem
item.URL = ctx.GetAppURL(fmt.Sprintf("s/%s/%s/d/%s/%s",
document.FolderID, stringutil.MakeSlug(document.Folder), document.DocumentID, stringutil.MakeSlug(document.Document)))
document.SpaceID, stringutil.MakeSlug(document.Space), document.DocumentID, stringutil.MakeSlug(document.Document)))
item.Date = document.Revised.Format("2006-01-02T15:04:05.999999-07:00")
items = append(items, item)
}
@ -184,7 +189,7 @@ func (h *Handler) Sitemap(w http.ResponseWriter, r *http.Request) {
func (h *Handler) Reindex(w http.ResponseWriter, r *http.Request) {
ctx := domain.GetRequestContext(r)
if !ctx.Global {
if !ctx.GlobalAdmin {
response.WriteForbiddenError(w)
h.Runtime.Log.Info(fmt.Sprintf("%s attempted search reindex"))
return
@ -199,7 +204,7 @@ func (h *Handler) Reindex(w http.ResponseWriter, r *http.Request) {
func (h *Handler) rebuildSearchIndex(ctx domain.RequestContext) {
method := "meta.rebuildSearchIndex"
docs, err := h.Store.Meta.GetDocumentsID(ctx)
docs, err := h.Store.Meta.Documents(ctx)
if err != nil {
h.Runtime.Log.Error(method, err)
return
@ -210,10 +215,23 @@ func (h *Handler) rebuildSearchIndex(ctx domain.RequestContext) {
for i := range docs {
d := docs[i]
pages, err := h.Store.Meta.GetDocumentPages(ctx, d)
dc, err := h.Store.Meta.Document(ctx, d)
if err != nil {
h.Runtime.Log.Error(method, err)
return
// continue
}
at, err := h.Store.Meta.Attachments(ctx, d)
if err != nil {
h.Runtime.Log.Error(method, err)
// continue
}
h.Indexer.IndexDocument(ctx, dc, at)
pages, err := h.Store.Meta.Pages(ctx, d)
if err != nil {
h.Runtime.Log.Error(method, err)
// continue
}
for j := range pages {
@ -234,7 +252,7 @@ func (h *Handler) SearchStatus(w http.ResponseWriter, r *http.Request) {
method := "meta.SearchStatus"
ctx := domain.GetRequestContext(r)
if !ctx.Global {
if !ctx.GlobalAdmin {
response.WriteForbiddenError(w)
h.Runtime.Log.Info(fmt.Sprintf("%s attempted get of search status"))
return

View file

@ -1,73 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package mysql
import (
"database/sql"
"github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/model/page"
"github.com/pkg/errors"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
}
// GetDocumentsID returns every document ID value stored.
// The query runs at the instance level across all tenants.
func (s Scope) GetDocumentsID(ctx domain.RequestContext) (documents []string, err error) {
err = s.Runtime.Db.Select(&documents, `SELECT refid FROM document WHERE lifecycle=1`)
if err == sql.ErrNoRows {
err = nil
documents = []string{}
}
if err != nil {
err = errors.Wrap(err, "failed to get instance document ID values")
}
return
}
// GetDocumentPages returns a slice containing all published page records for a given documentID, in presentation sequence.
func (s Scope) GetDocumentPages(ctx domain.RequestContext, documentID string) (p []page.Page, err error) {
err = s.Runtime.Db.Select(&p,
`SELECT
a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype,
a.pagetype, a.level, a.sequence, a.title, a.body, a.revisions,
a.blockid, a.status, a.relativeid, a.created, a.revised
FROM page a
WHERE a.documentid=? AND (a.status=0 OR ((a.status=4 OR a.status=2) AND a.relativeid=''))`,
documentID)
if err != nil {
err = errors.Wrap(err, "failed to get instance document pages")
}
return
}
// SearchIndexCount returns the numnber of index entries.
func (s Scope) SearchIndexCount(ctx domain.RequestContext) (c int, err error) {
row := s.Runtime.Db.QueryRow("SELECT count(*) FROM search")
err = row.Scan(&c)
if err != nil {
err = errors.Wrap(err, "count search index entries")
c = 0
}
return
}

123
domain/meta/store.go Normal file
View file

@ -0,0 +1,123 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package meta
import (
"database/sql"
"github.com/documize/community/model/doc"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/attachment"
"github.com/documize/community/model/page"
"github.com/pkg/errors"
)
// Store provides data access to space category information.
type Store struct {
store.Context
store.MetaStorer
}
// Documents returns every document ID value stored.
// The query runs at the instance level across all tenants.
func (s Store) Documents(ctx domain.RequestContext) (documents []string, err error) {
err = s.Runtime.Db.Select(&documents, `SELECT c_refid FROM dmz_doc WHERE c_lifecycle=1`)
if err == sql.ErrNoRows {
err = nil
documents = []string{}
}
if err != nil {
err = errors.Wrap(err, "failed to get instance document ID values")
}
return
}
// Document fetches the document record with the given id fromt the document table and audits that it has been got.
func (s Store) Document(ctx domain.RequestContext, id string) (document doc.Document, err error) {
err = s.Runtime.Db.Get(&document, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_userid AS userid,
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
FROM dmz_doc
WHERE c_refid=?`),
id)
if err != nil {
err = errors.Wrap(err, "execute select document")
}
return
}
// Pages returns a slice containing all published page records for a given documentID, in presentation sequence.
func (s Store) Pages(ctx domain.RequestContext, documentID string) (p []page.Page, err error) {
err = s.Runtime.Db.Select(&p, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_docid AS documentid,
c_userid AS userid, c_contenttype AS contenttype,
c_type AS type, c_level AS level, c_sequence AS sequence, c_name AS name,
c_body AS body, c_revisions AS revisions, c_templateid AS templateid,
c_status AS status, c_relativeid AS relativeid, c_created AS created, c_revised AS revised
FROM dmz_section
WHERE c_docid=? AND (c_status=0 OR ((c_status=4 OR c_status=2) AND c_relativeid=''))`),
documentID)
if err == sql.ErrNoRows {
err = nil
p = []page.Page{}
}
if err != nil {
err = errors.Wrap(err, "failed to get instance document pages")
}
return
}
// Attachments returns a slice containing the attachment records (excluding their data) for document docID, ordered by filename.
func (s Store) Attachments(ctx domain.RequestContext, docID string) (a []attachment.Attachment, err error) {
err = s.Runtime.Db.Select(&a, s.Bind(`
SELECT id, c_refid AS refid,
c_orgid AS orgid, c_docid AS documentid, c_job AS job, c_fileid AS fileid,
c_filename AS filename, c_extension AS extension,
c_created AS created, c_revised AS revised
FROM dmz_doc_attachment
WHERE c_docid=?
ORDER BY c_filename`),
docID)
if err == sql.ErrNoRows {
err = nil
a = []attachment.Attachment{}
}
if err != nil {
err = errors.Wrap(err, "execute select attachments")
return
}
return
}
// SearchIndexCount returns the numnber of index entries.
func (s Store) SearchIndexCount(ctx domain.RequestContext) (c int, err error) {
row := s.Runtime.Db.QueryRow("SELECT count(*) FROM dmz_search")
err = row.Scan(&c)
if err != nil {
err = errors.Wrap(err, "count search index entries")
c = 0
}
return
}

View file

@ -22,13 +22,14 @@ import (
"github.com/documize/community/core/response"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/org"
)
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
Store *store.Store
}
// Get returns the requested organization.
@ -96,90 +97,3 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, org)
}
// GetInstanceSetting returns the requested organization level setting.
func (h *Handler) GetInstanceSetting(w http.ResponseWriter, r *http.Request) {
ctx := domain.GetRequestContext(r)
orgID := request.Param(r, "orgID")
if orgID != ctx.OrgID || !ctx.Administrator {
response.WriteForbiddenError(w)
return
}
key := request.Query(r, "key")
setting, _ := h.Store.Setting.GetUser(orgID, "", key, "")
if len(setting) == 0 {
setting = "{}"
}
response.WriteJSON(w, setting)
}
// SaveInstanceSetting saves org level setting.
func (h *Handler) SaveInstanceSetting(w http.ResponseWriter, r *http.Request) {
method := "org.SaveInstanceSetting"
ctx := domain.GetRequestContext(r)
orgID := request.Param(r, "orgID")
if orgID != ctx.OrgID || !ctx.Administrator {
response.WriteForbiddenError(w)
return
}
key := request.Query(r, "key")
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
config := string(body)
h.Store.Setting.SetUser(orgID, "", key, config)
response.WriteEmpty(w)
}
// GetGlobalSetting returns the requested organization level setting.
func (h *Handler) GetGlobalSetting(w http.ResponseWriter, r *http.Request) {
ctx := domain.GetRequestContext(r)
if !ctx.Global {
response.WriteForbiddenError(w)
return
}
key := request.Query(r, "key")
setting, _ := h.Store.Setting.Get(key, "")
response.WriteJSON(w, setting)
}
// SaveGlobalSetting saves org level setting.
func (h *Handler) SaveGlobalSetting(w http.ResponseWriter, r *http.Request) {
method := "org.SaveGlobalSetting"
ctx := domain.GetRequestContext(r)
if !ctx.Global {
response.WriteForbiddenError(w)
return
}
key := request.Query(r, "key")
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
config := string(body)
h.Store.Setting.Set(key, config)
response.WriteEmpty(w)
}

View file

@ -20,23 +20,24 @@ import (
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/org"
"github.com/pkg/errors"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
// Store provides data access to organization (tenant) information.
type Store struct {
store.Context
store.OrganizationStorer
}
// AddOrganization inserts the passed organization record into the organization table.
func (s Scope) AddOrganization(ctx domain.RequestContext, org org.Organization) (err error) {
func (s Store) AddOrganization(ctx domain.RequestContext, org org.Organization) (err error) {
org.Created = time.Now().UTC()
org.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec(
"INSERT INTO organization (refid, company, title, message, domain, email, allowanonymousaccess, serial, maxtags, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
s.Bind("INSERT INTO dmz_org (c_refid, c_company, c_title, c_message, c_domain, c_email, c_anonaccess, c_serial, c_maxtags, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
org.RefID, org.Company, org.Title, org.Message, strings.ToLower(org.Domain),
strings.ToLower(org.Email), org.AllowAnonymousAccess, org.Serial, org.MaxTags, org.Created, org.Revised)
@ -48,8 +49,15 @@ func (s Scope) AddOrganization(ctx domain.RequestContext, org org.Organization)
}
// GetOrganization returns the Organization reocrod from the organization database table with the given id.
func (s Scope) GetOrganization(ctx domain.RequestContext, id string) (org org.Organization, err error) {
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, company, title, message, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, maxtags, created, revised FROM organization WHERE refid=?")
func (s Store) GetOrganization(ctx domain.RequestContext, id string) (org org.Organization, err error) {
stmt, err := s.Runtime.Db.Preparex(s.Bind(`SELECT id, c_refid AS refid,
c_title AS title, c_message AS message, c_domain AS domain,
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
coalesce(c_authconfig,` + s.EmptyJSON() + `) AS authconfig, c_maxtags AS maxtags,
c_created AS created, c_revised AS revised
FROM dmz_org
WHERE c_refid=?`))
defer streamutil.Close(stmt)
if err != nil {
@ -58,7 +66,6 @@ func (s Scope) GetOrganization(ctx domain.RequestContext, id string) (org org.Or
}
err = stmt.Get(&org, id)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to get org %s", id))
return
@ -69,7 +76,7 @@ func (s Scope) GetOrganization(ctx domain.RequestContext, id string) (org org.Or
// GetOrganizationByDomain returns the organization matching a given URL subdomain.
// No context is required because user might not be authenticated yet.
func (s Scope) GetOrganizationByDomain(subdomain string) (o org.Organization, err error) {
func (s Store) GetOrganizationByDomain(subdomain string) (o org.Organization, err error) {
err = nil
subdomain = strings.TrimSpace(strings.ToLower(subdomain))
@ -80,14 +87,30 @@ func (s Scope) GetOrganizationByDomain(subdomain string) (o org.Organization, er
}
// match on given domain name
err = s.Runtime.Db.Get(&o, "SELECT id, refid, company, title, message, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, maxtags, created, revised FROM organization WHERE domain=? AND active=1", subdomain)
err = s.Runtime.Db.Get(&o, s.Bind(`SELECT id, c_refid AS refid,
c_title AS title, c_message AS message, c_domain AS domain,
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, c_maxtags AS maxtags,
c_created AS created, c_revised AS revised
FROM dmz_org
WHERE c_domain=? AND c_active=true`),
subdomain)
if err == nil {
return
}
err = nil
// match on empty domain as last resort
err = s.Runtime.Db.Get(&o, "SELECT id, refid, company, title, message, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, maxtags, created, revised FROM organization WHERE domain='' AND active=1")
// match on empty domain AS last resort
err = s.Runtime.Db.Get(&o, s.Bind(`SELECT id, c_refid AS refid,
c_title AS title, c_message AS message, c_domain AS domain,
c_service AS conversionendpoint, c_email AS email, c_serial AS serial, c_active AS active,
c_anonaccess AS allowanonymousaccess, c_authprovider AS authprovider,
coalesce(c_authconfig,`+s.EmptyJSON()+`) AS authconfig, c_maxtags AS maxtags,
c_created AS created, c_revised AS revised
FROM dmz_org
WHERE c_domain='' AND c_active=true`))
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "unable to execute select for empty subdomain")
}
@ -96,10 +119,13 @@ func (s Scope) GetOrganizationByDomain(subdomain string) (o org.Organization, er
}
// UpdateOrganization updates the given organization record in the database to the values supplied.
func (s Scope) UpdateOrganization(ctx domain.RequestContext, org org.Organization) (err error) {
func (s Store) UpdateOrganization(ctx domain.RequestContext, org org.Organization) (err error) {
org.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec("UPDATE organization SET title=:title, message=:message, service=:conversionendpoint, email=:email, allowanonymousaccess=:allowanonymousaccess, maxtags=:maxtags, revised=:revised WHERE refid=:refid",
_, err = ctx.Transaction.NamedExec(`UPDATE dmz_org SET
c_title=:title, c_message=:message, c_service=:conversionendpoint, c_email=:email,
c_anonaccess=:allowanonymousaccess, c_maxtags=:maxtags, c_revised=:revised
WHERE c_refid=:refid`,
&org)
if err != nil {
@ -110,14 +136,13 @@ func (s Scope) UpdateOrganization(ctx domain.RequestContext, org org.Organizatio
}
// DeleteOrganization deletes the orgID organization from the organization table.
func (s Scope) DeleteOrganization(ctx domain.RequestContext, orgID string) (rows int64, err error) {
b := mysql.BaseQuery{}
return b.Delete(ctx.Transaction, "organization", orgID)
func (s Store) DeleteOrganization(ctx domain.RequestContext, orgID string) (rows int64, err error) {
return s.Delete(ctx.Transaction, "dmz_org", orgID)
}
// RemoveOrganization sets the orgID organization to be inactive, thus executing a "soft delete" operation.
func (s Scope) RemoveOrganization(ctx domain.RequestContext, orgID string) (err error) {
_, err = ctx.Transaction.Exec("UPDATE organization SET active=0 WHERE refid=?", orgID)
func (s Store) RemoveOrganization(ctx domain.RequestContext, orgID string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind("UPDATE dmz_org SET c_active=false WHERE c_refid=?"), orgID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute soft delete for org %s", orgID))
@ -127,10 +152,13 @@ func (s Scope) RemoveOrganization(ctx domain.RequestContext, orgID string) (err
}
// UpdateAuthConfig updates the given organization record in the database with the auth config details.
func (s Scope) UpdateAuthConfig(ctx domain.RequestContext, org org.Organization) (err error) {
func (s Store) UpdateAuthConfig(ctx domain.RequestContext, org org.Organization) (err error) {
org.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec("UPDATE organization SET allowanonymousaccess=:allowanonymousaccess, authprovider=:authprovider, authconfig=:authconfig, revised=:revised WHERE refid=:refid",
_, err = ctx.Transaction.NamedExec(`UPDATE dmz_org SET
c_anonaccess=:allowanonymousaccess, c_authprovider=:authprovider, c_authconfig=:authconfig,
c_revised=:revised
WHERE c_refid=:refid`,
&org)
if err != nil {
@ -141,8 +169,8 @@ func (s Scope) UpdateAuthConfig(ctx domain.RequestContext, org org.Organization)
}
// CheckDomain makes sure there is an organisation with the correct domain
func (s Scope) CheckDomain(ctx domain.RequestContext, domain string) string {
row := s.Runtime.Db.QueryRow("SELECT COUNT(*) FROM organization WHERE domain=? AND active=1", domain)
func (s Store) CheckDomain(ctx domain.RequestContext, domain string) string {
row := s.Runtime.Db.QueryRow(s.Bind("SELECT COUNT(*) FROM dmz_org WHERE c_domain=? AND c_active=true"), domain)
var count int
err := row.Scan(&count)
@ -150,7 +178,6 @@ func (s Scope) CheckDomain(ctx domain.RequestContext, domain string) string {
if err != nil {
return ""
}
if count == 1 {
return domain
}

Some files were not shown because too many files have changed in this diff Show more