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:
commit
4aa3bba7bc
269 changed files with 19927 additions and 23963 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -59,7 +59,6 @@ npm-debug.log
|
|||
debug
|
||||
*.pem
|
||||
*.crt
|
||||
Dockerfile
|
||||
container.sh
|
||||
make.sh
|
||||
jsconfig.json
|
||||
|
|
25
Gopkg.lock
generated
25
Gopkg.lock
generated
|
@ -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",
|
||||
|
|
33
README.md
33
README.md
|
@ -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.
|
||||
|
|
|
@ -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/...
|
||||
|
|
5
build.sh
5
build.sh
|
@ -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/...
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
74
core/database/db_test.go
Normal 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
257
core/database/installer.go
Normal 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(¤tVersion)
|
||||
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(¤tVersion)
|
||||
}
|
||||
}
|
||||
|
||||
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
110
core/database/lock.go
Normal 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()
|
||||
// }
|
|
@ -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
39
core/database/params.go
Normal 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
85
core/database/scripts.go
Normal 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
|
||||
}
|
339
core/database/scripts/mysql/db_00025.sql
Normal file
339
core/database/scripts/mysql/db_00025.sql
Normal 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`;
|
479
core/database/scripts/postgresql/db_00001.sql
Normal file
479
core/database/scripts/postgresql/db_00001.sql
Normal 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"}');
|
|
@ -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
6
core/env/flags.go
vendored
|
@ -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
15
core/env/product.go
vendored
|
@ -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
113
core/env/provider.go
vendored
Normal 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
34
core/env/runtime.go
vendored
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
19
core/uniqueid/xid/LICENSE
Executable 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
112
core/uniqueid/xid/README.md
Executable 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).
|
9
core/uniqueid/xid/hostid_darwin.go
Executable file
9
core/uniqueid/xid/hostid_darwin.go
Executable file
|
@ -0,0 +1,9 @@
|
|||
// +build darwin
|
||||
|
||||
package xid
|
||||
|
||||
import "syscall"
|
||||
|
||||
func readPlatformMachineID() (string, error) {
|
||||
return syscall.Sysctl("kern.uuid")
|
||||
}
|
9
core/uniqueid/xid/hostid_fallback.go
Executable file
9
core/uniqueid/xid/hostid_fallback.go
Executable file
|
@ -0,0 +1,9 @@
|
|||
// +build !darwin,!linux,!freebsd,!windows
|
||||
|
||||
package xid
|
||||
|
||||
import "errors"
|
||||
|
||||
func readPlatformMachineID() (string, error) {
|
||||
return "", errors.New("not implemented")
|
||||
}
|
9
core/uniqueid/xid/hostid_freebsd.go
Executable file
9
core/uniqueid/xid/hostid_freebsd.go
Executable file
|
@ -0,0 +1,9 @@
|
|||
// +build freebsd
|
||||
|
||||
package xid
|
||||
|
||||
import "syscall"
|
||||
|
||||
func readPlatformMachineID() (string, error) {
|
||||
return syscall.Sysctl("kern.hostuuid")
|
||||
}
|
10
core/uniqueid/xid/hostid_linux.go
Executable file
10
core/uniqueid/xid/hostid_linux.go
Executable 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
|
||||
}
|
38
core/uniqueid/xid/hostid_windows.go
Executable file
38
core/uniqueid/xid/hostid_windows.go
Executable 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(®Buf[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
365
core/uniqueid/xid/id.go
Executable 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
396
core/uniqueid/xid/id_test.go
Executable 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()
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -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
157
domain/account/store.go
Normal 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)
|
||||
}
|
|
@ -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
84
domain/activity/store.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
118
domain/attachment/store.go
Normal 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)
|
||||
}
|
|
@ -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 {
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
186
domain/backup/endpoint.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
152
domain/block/store.go
Normal 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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
323
domain/category/store.go
Normal 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
|
||||
}
|
|
@ -42,7 +42,7 @@ type RequestContext struct {
|
|||
Analytics bool
|
||||
Active bool
|
||||
Editor bool
|
||||
Global bool
|
||||
GlobalAdmin bool
|
||||
ViewUsers bool
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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>")
|
||||
|
||||
|
|
|
@ -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
348
domain/document/store.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
181
domain/group/store.go
Normal 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
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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
278
domain/link/store.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
123
domain/meta/store.go
Normal 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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue