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

Merge pull request #121 from documize/granular-permissions

Granular permissions
This commit is contained in:
Harvey Kandola 2017-10-08 21:09:08 -04:00 committed by GitHub
commit f2794a9836
224 changed files with 11849 additions and 7759 deletions

View file

@ -1,32 +1,77 @@
# Documize Community Edition # Documize Community Edition
Documize is an Integrated Document Environment (IDE) unifying documents, wiki, reporting and dashboards -- one tool to power the enterprise-wide knowledge backbone. ## The mission
![Alt text](screenshot.png "Documize") To bring software development inspired features to the world of documenting -- refactoring, importing, testing, linting, metrics, PRs, versioning....
The mission is to bring software dev inspired features (refactoring, testing, linting, metrics, PRs) to those poor souls stuck writing docs in the dark ages. ## What is it?
Documize is an intelligent document environment (IDE) for creating, securing and sharing documents -- everything you need in one place.
## Why should I care?
Because maybe like us, you might be tired of:
* juggling WYSIWYG editors, wiki software and various document related solutions
* playing document related email tennis with contributions, versions and feedback
* sharing not-so-secure folders with external participants
Sound familiar? Read on.
## Who is it for?
Anyone who wants a single place for any kind of document.
Anyone who wants to loop in external participants complete security.
Anyone who wishes documentation and knowledge capture worked like agile software development.
## What's different about Documize?
Sane organization through personal, team and public spaces.
Granular document access control via categories.
Section based approach to document construction.
Reusable templates and content blocks.
Documentation related tasking and delegation.
Integrations for embedding SaaS data within documents.
## Latest version ## Latest version
v1.53.6 v1.54.0
## OS Support ## OS support
Documize runs on the following:
- Windows
- Linux - Linux
- Windows
- macOS - macOS
## Tech stack ## Technology stack
Documize is built with the following technologies:
- EmberJS (v2.15.0) - EmberJS (v2.15.0)
- Go (v1.9.0) - Go (v1.9.0)
- MySQL (v5.7.10+) or Percona (v5.7.16-10+) or MariaDB (10.3.0+)
## Documentation ...and supports the following databases:
<https://docs.documize.com> - MySQL (v5.7.10+)
- Percona (v5.7.16-10+)
- MariaDB (10.3.0+)
## Keycloak Integration Coming soon, PostgreSQL and Microsoft SQL Server support.
## Authentication options
Besides email/password login, you can also leverage the following options.
### Keycloak Integration
Documize provides out-of-the-box integration with [Redhat Keycloak](http://www.keycloak.org) for open source identity and access management. Documize provides out-of-the-box integration with [Redhat Keycloak](http://www.keycloak.org) for open source identity and access management.
@ -34,7 +79,7 @@ Connect and authenticate with LDAP, Active Directory and more.
<https://docs.documize.com> <https://docs.documize.com>
## Auth0 Compatible ### Auth0 Compatible
Documize is compatible with Auth0 identity as a service. Documize is compatible with Auth0 identity as a service.
@ -42,7 +87,7 @@ Documize is compatible with Auth0 identity as a service.
Open Source Identity and Access Management Open Source Identity and Access Management
## Legal ## The legal bit at the end
<https://documize.com> <https://documize.com>

View file

@ -1,5 +1,8 @@
#! /bin/bash #! /bin/bash
# ember s apiHost=https://demo1.dev:5001
# go run edition/community.go -port=5001 -forcesslport=5002 -cert selfcert/cert.pem -key selfcert/key.pem -salt=tsu3Acndky8cdTNx3
NOW=$(date) NOW=$(date)
echo "Build process started $NOW" echo "Build process started $NOW"

View file

@ -120,8 +120,8 @@ func Check(runtime *env.Runtime) bool {
{ // check all the required tables exist { // check all the required tables exist
var tables = []string{`account`, var tables = []string{`account`,
`attachment`, `audit`, `document`, `attachment`, `document`,
`label`, `labelrole`, `organization`, `label`, `organization`,
`page`, `revision`, `search`, `user`} `page`, `revision`, `search`, `user`}
for _, table := range tables { for _, table := range tables {

View file

@ -161,7 +161,7 @@ func setupAccount(rt *env.Runtime, completion onboardRequest, serial string) (er
return err return err
} }
// Set up default labels for main collection. // create space
labelID := uniqueid.Generate() labelID := uniqueid.Generate()
sql = fmt.Sprintf("insert into label (refid, orgid, label, type, userid) values (\"%s\", \"%s\", \"My Project\", 2, \"%s\")", labelID, orgID, userID) 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) _, err = runSQL(rt, sql)
@ -170,12 +170,14 @@ func setupAccount(rt *env.Runtime, completion onboardRequest, serial string) (er
rt.Log.Error("insert into label failed", err) rt.Log.Error("insert into label failed", err)
} }
labelRoleID := uniqueid.Generate() // assign permissions to space
sql = fmt.Sprintf("insert into labelrole (refid, labelid, orgid, userid, canview, canedit) values (\"%s\", \"%s\", \"%s\", \"%s\", 1, 1)", labelRoleID, labelID, orgID, userID) perms := []string{"view", "manage", "own", "doc-add", "doc-edit", "doc-delete", "doc-move", "doc-copy", "doc-template"}
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 = runSQL(rt, sql)
if err != nil { if err != nil {
rt.Log.Error("insert into labelrole failed", err) rt.Log.Error("insert into permission failed", err)
}
} }
return return

View file

@ -0,0 +1,143 @@
/* community edition */
-- permission records space and document level privelges, making existing labelrole table obsolete
-- who column can be user or role
-- whoid column contains eitehr user or role ID
-- action column records permission type (view, edit, delete...)
-- scope column details if action applies to object or table
-- location column details name of table
-- refid column details ID of item that the action applies to (only if scope=object)
DROP TABLE IF EXISTS `permission`;
CREATE TABLE IF NOT EXISTS `permission` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
`who` VARCHAR(30) NOT NULL,
`whoid` CHAR(16) DEFAULT '' NOT NULL COLLATE utf8_bin,
`action` VARCHAR(30) NOT NULL,
`scope` VARCHAR(30) NOT NULL,
`location` VARCHAR(100) NOT NULL,
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE INDEX `idx_permission_id` (`id` ASC),
INDEX `idx_permission_orgid` (`orgid` ASC))
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
ENGINE = MyISAM;
CREATE INDEX idx_permission_1 ON permission(orgid,who,whoid,location);
CREATE INDEX idx_permission_2 ON permission(orgid,who,whoid,location,action);
CREATE INDEX idx_permission_3 ON permission(orgid,location,refid);
CREATE INDEX idx_permission_4 ON permission(orgid,who,location,action);
-- category represents "folder/label/category" assignment to document (1:M)
DROP TABLE IF EXISTS `category`;
CREATE TABLE IF NOT EXISTS `category` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
`labelid` CHAR(16) NOT NULL COLLATE utf8_bin,
`category` VARCHAR(30) NOT NULL,
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE INDEX `idx_category_id` (`id` ASC),
INDEX `idx_category_refid` (`refid` ASC),
INDEX `idx_category_orgid` (`orgid` ASC))
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
ENGINE = MyISAM;
CREATE INDEX idx_category_1 ON category(orgid,labelid);
-- category member records who can see a category and the documents within
DROP TABLE IF EXISTS `categorymember`;
CREATE TABLE IF NOT EXISTS `categorymember` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
`labelid` CHAR(16) NOT NULL COLLATE utf8_bin,
`categoryid` CHAR(16) NOT NULL COLLATE utf8_bin,
`documentid` CHAR(16) NOT NULL COLLATE utf8_bin,
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE INDEX `idx_categorymember_id` (`id` ASC),
INDEX `idx_category_documentid` (`documentid`))
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
ENGINE = MyISAM;
CREATE INDEX idx_categorymember_1 ON categorymember(orgid,documentid);
CREATE INDEX idx_categorymember_2 ON categorymember(orgid,labelid);
-- rolee represent user groups
DROP TABLE IF EXISTS `role`;
CREATE TABLE IF NOT EXISTS `role` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
`role` VARCHAR(30) NOT NULL,
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE INDEX `idx_category_id` (`id` ASC),
INDEX `idx_category_refid` (`refid` ASC),
INDEX `idx_category_orgid` (`orgid` ASC))
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
ENGINE = MyISAM;
-- role member records user role membership
DROP TABLE IF EXISTS `rolemember`;
CREATE TABLE IF NOT EXISTS `rolemember` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
`roleid` CHAR(16) NOT NULL COLLATE utf8_bin,
`userid` CHAR(16) NOT NULL COLLATE utf8_bin,
UNIQUE INDEX `idx_category_id` (`id` ASC))
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
ENGINE = MyISAM;
CREATE INDEX idx_rolemember_1 ON rolemember(roleid,userid);
CREATE INDEX idx_rolemember_2 ON rolemember(orgid,roleid,userid);
-- user account can have global permssion to state if user can see all other users
-- provides granular control for external users
ALTER TABLE account ADD COLUMN `users` BOOL NOT NULL DEFAULT 1 AFTER `admin`;
-- migrate space/document permissions
-- space own
INSERT INTO permission (orgid, who, whoid, `action`, scope, location, refid)
SELECT orgid, 'user' as who, userid as whois, 'own' as `action`, 'object' as scope, 'space' as location, refid
FROM label;
-- space manage (same as owner)
INSERT INTO permission (orgid, who, whoid, `action`, scope, location, refid)
SELECT orgid, 'user' as who, userid as whois, 'manage' as `action`, 'object' as scope, 'space' as location, refid
FROM label;
-- view space
INSERT INTO permission (orgid, who, whoid, `action`, scope, location, refid)
SELECT orgid, 'user' as who, userid as whois, 'view' as `action`, 'object' as scope, 'space' as location, labelid as refid
FROM labelrole WHERE canview=1;
-- edit space => add/edit/delete/move/copy/template documents
INSERT INTO permission (orgid, who, whoid, `action`, scope, location, refid)
SELECT orgid, 'user' as who, userid as whois, 'doc-add' as `action`, 'object' as scope, 'space' as location, labelid as refid
FROM labelrole WHERE canedit=1;
INSERT INTO permission (orgid, who, whoid, `action`, scope, location, refid)
SELECT orgid, 'user' as who, userid as whois, 'doc-edit' as `action`, 'object' as scope, 'space' as location, labelid as refid
FROM labelrole WHERE canedit=1;
INSERT INTO permission (orgid, who, whoid, `action`, scope, location, refid)
SELECT orgid, 'user' as who, userid as whois, 'doc-delete' as `action`, 'object' as scope, 'space' as location, labelid as refid
FROM labelrole WHERE canedit=1;
INSERT INTO permission (orgid, who, whoid, `action`, scope, location, refid)
SELECT orgid, 'user' as who, userid as whois, 'doc-move' as `action`, 'object' as scope, 'space' as location, labelid as refid
FROM labelrole WHERE canedit=1;
INSERT INTO permission (orgid, who, whoid, `action`, scope, location, refid)
SELECT orgid, 'user' as who, userid as whois, 'doc-copy' as `action`, 'object' as scope, 'space' as location, labelid as refid
FROM labelrole WHERE canedit=1;
INSERT INTO permission (orgid, who, whoid, `action`, scope, location, refid)
SELECT orgid, 'user' as who, userid as whois, 'doc-template' as `action`, 'object' as scope, 'space' as location, labelid as refid
FROM labelrole WHERE canedit=1;
-- everyone users ID changed to 0
UPDATE permission SET whoid='0' WHERE whoid='';

View file

@ -17,7 +17,6 @@ import (
"time" "time"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql" "github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/account" "github.com/documize/community/model/account"
@ -34,19 +33,11 @@ func (s Scope) Add(ctx domain.RequestContext, account account.Account) (err erro
account.Created = time.Now().UTC() account.Created = time.Now().UTC()
account.Revised = time.Now().UTC() account.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO account (refid, orgid, userid, admin, editor, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)") _, err = ctx.Transaction.Exec("INSERT INTO account (refid, orgid, userid, admin, editor, users, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
defer streamutil.Close(stmt) account.RefID, account.OrgID, account.UserID, account.Admin, account.Editor, account.Users, account.Active, account.Created, account.Revised)
if err != nil {
err = errors.Wrap(err, "unable to prepare insert for account")
return
}
_, err = stmt.Exec(account.RefID, account.OrgID, account.UserID, account.Admin, account.Editor, account.Active, account.Created, account.Revised)
if err != nil { if err != nil {
err = errors.Wrap(err, "unable to execute insert for account") err = errors.Wrap(err, "unable to execute insert for account")
return
} }
return return
@ -54,21 +45,14 @@ func (s Scope) Add(ctx domain.RequestContext, account account.Account) (err erro
// GetUserAccount returns the database account record corresponding to the given userID, using the client's current organizaion. // 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) { func (s Scope) GetUserAccount(ctx domain.RequestContext, userID string) (account account.Account, err error) {
stmt, err := s.Runtime.Db.Preparex(` err = s.Runtime.Db.Get(&account, `
SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.active, a.created, a.revised, b.company, b.title, b.message, b.domain SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.users, a.active, a.created, a.revised,
b.company, b.title, b.message, b.domain
FROM account a, organization b FROM account a, organization b
WHERE b.refid=a.orgid and a.orgid=? and a.userid=?`) WHERE b.refid=a.orgid AND a.orgid=? AND a.userid=?`, ctx.OrgID, userID)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare select for account by user %s", userID))
return
}
err = stmt.Get(&account, ctx.OrgID, userID)
if err != sql.ErrNoRows && err != nil { if err != sql.ErrNoRows && err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute select for account by user %s", userID)) err = errors.Wrap(err, fmt.Sprintf("execute select for account by user %s", userID))
return
} }
return return
@ -76,8 +60,8 @@ func (s Scope) GetUserAccount(ctx domain.RequestContext, userID string) (account
// GetUserAccounts returns a slice of database account records, for all organizations that the userID is a member of, in organization title order. // 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) { func (s Scope) GetUserAccounts(ctx domain.RequestContext, userID string) (t []account.Account, err error) {
err = s.Runtime.Db.Select(&t, err = s.Runtime.Db.Select(&t, `
`SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.active, a.created, a.revised, SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.users, a.active, a.created, a.revised,
b.company, b.title, b.message, b.domain b.company, b.title, b.message, b.domain
FROM account a, organization b FROM account a, organization b
WHERE a.userid=? AND a.orgid=b.refid AND a.active=1 ORDER BY b.title`, userID) WHERE a.userid=? AND a.orgid=b.refid AND a.active=1 ORDER BY b.title`, userID)
@ -92,7 +76,8 @@ func (s Scope) GetUserAccounts(ctx domain.RequestContext, userID string) (t []ac
// GetAccountsByOrg returns a slice of database account records, for all users in the client's organization. // 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) { func (s Scope) GetAccountsByOrg(ctx domain.RequestContext) (t []account.Account, err error) {
err = s.Runtime.Db.Select(&t, err = s.Runtime.Db.Select(&t,
`SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.active, a.created, a.revised, b.company, b.title, b.message, b.domain `SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.users, a.active, a.created, a.revised,
b.company, b.title, b.message, b.domain
FROM account a, organization b FROM account a, organization b
WHERE a.orgid=b.refid AND a.orgid=? AND a.active=1`, ctx.OrgID) WHERE a.orgid=b.refid AND a.orgid=? AND a.active=1`, ctx.OrgID)
@ -112,7 +97,6 @@ func (s Scope) CountOrgAccounts(ctx domain.RequestContext) (c int) {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return 0 return 0
} }
if err != nil { if err != nil {
err = errors.Wrap(err, "count org accounts") err = errors.Wrap(err, "count org accounts")
return 0 return 0
@ -125,18 +109,10 @@ func (s Scope) CountOrgAccounts(ctx domain.RequestContext) (c int) {
func (s Scope) UpdateAccount(ctx domain.RequestContext, account account.Account) (err error) { func (s Scope) UpdateAccount(ctx domain.RequestContext, account account.Account) (err error) {
account.Revised = time.Now().UTC() account.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.PrepareNamed("UPDATE account SET userid=:userid, admin=:admin, editor=:editor, active=:active, revised=:revised WHERE orgid=:orgid AND refid=:refid") _, err = ctx.Transaction.NamedExec("UPDATE account SET userid=:userid, admin=:admin, editor=:editor, users=:users, active=:active, revised=:revised WHERE orgid=:orgid AND refid=:refid", &account)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare update for account %s", account.RefID))
return
}
_, err = stmt.Exec(&account)
if err != sql.ErrNoRows && err != nil { if err != sql.ErrNoRows && err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute update for account %s", account.RefID)) err = errors.Wrap(err, fmt.Sprintf("execute update for account %s", account.RefID))
return
} }
return return

View file

@ -16,7 +16,6 @@ import (
"time" "time"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/model/activity" "github.com/documize/community/model/activity"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -33,19 +32,11 @@ func (s Scope) RecordUserActivity(ctx domain.RequestContext, activity activity.U
activity.UserID = ctx.UserID activity.UserID = ctx.UserID
activity.Created = time.Now().UTC() activity.Created = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created) VALUES (?, ?, ?, ?, ?, ?, ?)") _, err = ctx.Transaction.Exec("INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created) VALUES (?, ?, ?, ?, ?, ?, ?)",
defer streamutil.Close(stmt) activity.OrgID, activity.UserID, activity.LabelID, activity.SourceID, activity.SourceType, activity.ActivityType, activity.Created)
if err != nil {
err = errors.Wrap(err, "prepare record user activity")
return
}
_, err = stmt.Exec(activity.OrgID, activity.UserID, activity.LabelID, activity.SourceID, activity.SourceType, activity.ActivityType, activity.Created)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute record user activity") err = errors.Wrap(err, "execute record user activity")
return
} }
return return

View file

@ -25,8 +25,8 @@ import (
"github.com/documize/community/core/secrets" "github.com/documize/community/core/secrets"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/document"
"github.com/documize/community/domain/organization" "github.com/documize/community/domain/organization"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search" indexer "github.com/documize/community/domain/search"
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
@ -89,7 +89,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -125,7 +125,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -177,7 +177,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }

View file

@ -20,7 +20,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
) )
@ -37,18 +36,11 @@ func (s Scope) Add(ctx domain.RequestContext, a attachment.Attachment) (err erro
bits := strings.Split(a.Filename, ".") bits := strings.Split(a.Filename, ".")
a.Extension = bits[len(bits)-1] a.Extension = bits[len(bits)-1]
stmt, err := ctx.Transaction.Preparex("INSERT INTO attachment (refid, orgid, documentid, job, fileid, filename, data, extension, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") _, err = ctx.Transaction.Exec("INSERT INTO attachment (refid, orgid, documentid, job, fileid, filename, data, extension, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
defer streamutil.Close(stmt) 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, "prepare insert attachment")
return
}
_, err = stmt.Exec(a.RefID, a.OrgID, a.DocumentID, a.Job, a.FileID, a.Filename, a.Data, a.Extension, a.Created, a.Revised)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute insert attachment") err = errors.Wrap(err, "execute insert attachment")
return
} }
return return
@ -56,18 +48,11 @@ func (s Scope) Add(ctx domain.RequestContext, a attachment.Attachment) (err erro
// GetAttachment returns the database attachment record specified by the parameters. // 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) { func (s Scope) GetAttachment(ctx domain.RequestContext, orgID, attachmentID string) (a attachment.Attachment, err error) {
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, orgid, documentid, job, fileid, filename, data, extension, created, revised FROM attachment WHERE orgid=? and refid=?") err = s.Runtime.Db.Get(&a, "SELECT id, refid, orgid, documentid, job, fileid, filename, data, extension, created, revised FROM attachment WHERE orgid=? and refid=?",
defer streamutil.Close(stmt) orgID, attachmentID)
if err != nil {
err = errors.Wrap(err, "prepare select attachment")
return
}
err = stmt.Get(&a, orgID, attachmentID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute select attachment") err = errors.Wrap(err, "execute select attachment")
return
} }
return return
@ -79,7 +64,6 @@ func (s Scope) GetAttachments(ctx domain.RequestContext, docID string) (a []atta
if err != nil { if err != nil {
err = errors.Wrap(err, "execute select attachments") err = errors.Wrap(err, "execute select attachments")
return
} }
return return
@ -91,7 +75,6 @@ func (s Scope) GetAttachmentsWithData(ctx domain.RequestContext, docID string) (
if err != nil { if err != nil {
err = errors.Wrap(err, "execute select attachments with data") err = errors.Wrap(err, "execute select attachments with data")
return
} }
return return

View file

@ -40,21 +40,15 @@ func (s Scope) Record(ctx domain.RequestContext, t audit.EventType) {
return return
} }
stmt, err := tx.Preparex("INSERT INTO userevent (orgid, userid, eventtype, ip, created) VALUES (?, ?, ?, ?, ?)") _, err = tx.Exec("INSERT INTO userevent (orgid, userid, eventtype, ip, created) VALUES (?, ?, ?, ?, ?)",
e.OrgID, e.UserID, e.Type, e.IP, e.Created)
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
s.Runtime.Log.Error("prepare audit insert", err) s.Runtime.Log.Error("prepare audit insert", err)
return return
} }
_, err = stmt.Exec(e.OrgID, e.UserID, e.Type, e.IP, e.Created)
if err != nil {
tx.Rollback()
s.Runtime.Log.Error("execute audit insert", err)
return
}
stmt.Close()
tx.Commit() tx.Commit()
return return

View file

@ -110,7 +110,7 @@ func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
return return
} }
h.Runtime.Log.Info("login " + email + " @ " + dom) h.Runtime.Log.Info("logged in " + email + " @ " + dom)
authModel := auth.AuthenticationModel{} authModel := auth.AuthenticationModel{}
authModel.Token = GenerateJWT(h.Runtime, u.RefID, org.RefID, dom) authModel.Token = GenerateJWT(h.Runtime, u.RefID, org.RefID, dom)

View file

@ -22,7 +22,7 @@ import (
"github.com/documize/community/core/streamutil" "github.com/documize/community/core/streamutil"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/document" "github.com/documize/community/domain/permission"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/block" "github.com/documize/community/model/block"
) )
@ -57,7 +57,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanUploadDocument(ctx, *h.Store, b.LabelID) { if !permission.CanUploadDocument(ctx, *h.Store, b.LabelID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -165,7 +165,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
b.RefID = blockID b.RefID = blockID
if !document.CanUploadDocument(ctx, *h.Store, b.LabelID) { if !permission.CanUploadDocument(ctx, *h.Store, b.LabelID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }

View file

@ -16,11 +16,9 @@ import (
"time" "time"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql" "github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/block" "github.com/documize/community/model/block"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -36,18 +34,11 @@ func (s Scope) Add(ctx domain.RequestContext, b block.Block) (err error) {
b.Created = time.Now().UTC() b.Created = time.Now().UTC()
b.Revised = time.Now().UTC() b.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO block (refid, orgid, labelid, userid, contenttype, pagetype, title, body, excerpt, rawbody, config, externalsource, used, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") _, err = ctx.Transaction.Exec("INSERT INTO block (refid, orgid, labelid, userid, contenttype, pagetype, title, body, excerpt, rawbody, config, externalsource, used, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
defer streamutil.Close(stmt) 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, "prepare insert block")
return
}
_, err = stmt.Exec(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 { if err != nil {
err = errors.Wrap(err, "execute insert block") err = errors.Wrap(err, "execute insert block")
return
} }
return return
@ -55,18 +46,11 @@ func (s Scope) Add(ctx domain.RequestContext, b block.Block) (err error) {
// Get returns requested reusable content block. // Get returns requested reusable content block.
func (s Scope) Get(ctx domain.RequestContext, id string) (b block.Block, err error) { func (s Scope) Get(ctx domain.RequestContext, id string) (b block.Block, err error) {
stmt, err := s.Runtime.Db.Preparex("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=?") 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=?",
defer streamutil.Close(stmt) ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "prepare select block")
return
}
err = stmt.Get(&b, ctx.OrgID, id)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute select block") err = errors.Wrap(err, "execute select block")
return
} }
return return
@ -78,7 +62,6 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (b []block.
if err != nil { if err != nil {
err = errors.Wrap(err, "select space blocks") err = errors.Wrap(err, "select space blocks")
return
} }
return return
@ -86,18 +69,10 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (b []block.
// IncrementUsage increments usage counter for content block. // IncrementUsage increments usage counter for content block.
func (s Scope) IncrementUsage(ctx domain.RequestContext, id string) (err error) { func (s Scope) IncrementUsage(ctx domain.RequestContext, id string) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE block SET used=used+1, revised=? WHERE orgid=? AND refid=?") _, err = ctx.Transaction.Exec("UPDATE block SET used=used+1, revised=? WHERE orgid=? AND refid=?", time.Now().UTC(), ctx.OrgID, id)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "prepare increment block usage")
return
}
_, err = stmt.Exec(time.Now().UTC(), ctx.OrgID, id)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute increment block usage") err = errors.Wrap(err, "execute increment block usage")
return
} }
return return
@ -105,18 +80,10 @@ func (s Scope) IncrementUsage(ctx domain.RequestContext, id string) (err error)
// DecrementUsage decrements usage counter for content block. // DecrementUsage decrements usage counter for content block.
func (s Scope) DecrementUsage(ctx domain.RequestContext, id string) (err error) { func (s Scope) DecrementUsage(ctx domain.RequestContext, id string) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE block SET used=used-1, revised=? WHERE orgid=? AND refid=?") _, err = ctx.Transaction.Exec("UPDATE block SET used=used-1, revised=? WHERE orgid=? AND refid=?", time.Now().UTC(), ctx.OrgID, id)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "prepare decrement block usage")
return
}
_, err = stmt.Exec(time.Now().UTC(), ctx.OrgID, id)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute decrement block usage") err = errors.Wrap(err, "execute decrement block usage")
return
} }
return return
@ -124,23 +91,13 @@ func (s Scope) DecrementUsage(ctx domain.RequestContext, id string) (err error)
// RemoveReference clears page.blockid for given blockID. // RemoveReference clears page.blockid for given blockID.
func (s Scope) RemoveReference(ctx domain.RequestContext, id string) (err error) { func (s Scope) RemoveReference(ctx domain.RequestContext, id string) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE page SET blockid='', revised=? WHERE orgid=? AND blockid=?") _, err = ctx.Transaction.Exec("UPDATE page SET blockid='', revised=? WHERE orgid=? AND blockid=?", time.Now().UTC(), ctx.OrgID, id)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "prepare remove block ref")
return
}
_, err = stmt.Exec(time.Now().UTC(), ctx.OrgID, id)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
err = nil err = nil
} }
if err != nil { if err != nil {
err = errors.Wrap(err, "execute remove block ref") err = errors.Wrap(err, "execute remove block ref")
return
} }
return return
@ -150,19 +107,10 @@ func (s Scope) RemoveReference(ctx domain.RequestContext, id string) (err error)
func (s Scope) Update(ctx domain.RequestContext, b block.Block) (err error) { func (s Scope) Update(ctx domain.RequestContext, b block.Block) (err error) {
b.Revised = time.Now().UTC() b.Revised = time.Now().UTC()
var stmt *sqlx.NamedStmt _, 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)
stmt, err = ctx.Transaction.PrepareNamed("UPDATE block SET title=:title, body=:body, excerpt=:excerpt, rawbody=:rawbody, config=:config, revised=:revised WHERE orgid=:orgid AND refid=:refid")
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "prepare update block")
return
}
_, err = stmt.Exec(&b)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute update block") err = errors.Wrap(err, "execute update block")
return
} }
return return

526
domain/category/endpoint.go Normal file
View file

@ -0,0 +1,526 @@
// 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 handles API calls and persistence for categories.
// Categories sub-divide spaces.
package category
import (
"database/sql"
"encoding/json"
"io/ioutil"
"net/http"
"github.com/documize/community/core/env"
"github.com/documize/community/core/request"
"github.com/documize/community/core/response"
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/permission"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/category"
pm "github.com/documize/community/model/permission"
)
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
}
// Add saves space category.
func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
method := "category.add"
ctx := domain.GetRequestContext(r)
if !ctx.Authenticated {
response.WriteForbiddenError(w)
return
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, "body")
h.Runtime.Log.Error(method, err)
return
}
var cat category.Category
err = json.Unmarshal(body, &cat)
if err != nil {
response.WriteBadRequestError(w, method, "category")
h.Runtime.Log.Error(method, err)
return
}
cat.RefID = uniqueid.Generate()
cat.OrgID = ctx.OrgID
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
err = h.Store.Category.Add(ctx, cat)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Store.Audit.Record(ctx, audit.EventTypeCategoryAdd)
ctx.Transaction.Commit()
cat, err = h.Store.Category.Get(ctx, cat.RefID)
if err != nil {
response.WriteServerError(w, method, err)
return
}
response.WriteJSON(w, cat)
}
// Get returns categories visible to user within a space.
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
method := "category.get"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
ok := permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceManage, pm.SpaceOwner, pm.SpaceView)
if !ok {
response.WriteForbiddenError(w)
return
}
cat, err := h.Store.Category.GetBySpace(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
h.Runtime.Log.Error("get space categories visible to user failed", err)
response.WriteServerError(w, method, err)
return
}
if len(cat) == 0 {
cat = []category.Category{}
}
response.WriteJSON(w, cat)
}
// GetAll returns categories within a space, disregarding permissions.
// Used in admin screens, lists, functions.
func (h *Handler) GetAll(w http.ResponseWriter, r *http.Request) {
method := "category.getAll"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
cat, err := h.Store.Category.GetAllBySpace(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(cat) == 0 {
cat = []category.Category{}
}
response.WriteJSON(w, cat)
}
// Update saves existing space category.
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
method := "category.update"
ctx := domain.GetRequestContext(r)
categoryID := request.Param(r, "categoryID")
if len(categoryID) == 0 {
response.WriteMissingDataError(w, method, "categoryID")
return
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, "body")
h.Runtime.Log.Error(method, err)
return
}
var cat category.Category
err = json.Unmarshal(body, &cat)
if err != nil {
response.WriteBadRequestError(w, method, "category")
h.Runtime.Log.Error(method, err)
return
}
cat.OrgID = ctx.OrgID
cat.RefID = categoryID
ok := permission.HasPermission(ctx, *h.Store, cat.LabelID, pm.SpaceManage, pm.SpaceOwner)
if !ok || !ctx.Authenticated {
response.WriteForbiddenError(w)
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
err = h.Store.Category.Update(ctx, cat)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Store.Audit.Record(ctx, audit.EventTypeCategoryUpdate)
ctx.Transaction.Commit()
cat, err = h.Store.Category.Get(ctx, cat.RefID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
response.WriteJSON(w, cat)
}
// Delete removes category and associated member records.
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
method := "category.delete"
ctx := domain.GetRequestContext(r)
catID := request.Param(r, "categoryID")
if len(catID) == 0 {
response.WriteMissingDataError(w, method, "categoryID")
return
}
cat, err := h.Store.Category.Get(ctx, catID)
if err != nil {
response.WriteServerError(w, method, err)
return
}
ok := permission.HasPermission(ctx, *h.Store, cat.LabelID, pm.SpaceManage, pm.SpaceOwner)
if !ok || !ctx.Authenticated {
response.WriteForbiddenError(w)
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// remove category members
_, err = h.Store.Category.RemoveCategoryMembership(ctx, cat.RefID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// remove category permissions
_, err = h.Store.Permission.DeleteCategoryPermissions(ctx, cat.RefID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// remove category
_, err = h.Store.Category.Delete(ctx, cat.RefID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Store.Audit.Record(ctx, audit.EventTypeCategoryDelete)
ctx.Transaction.Commit()
response.WriteEmpty(w)
}
// GetSummary returns number of documents and users for space categories.
func (h *Handler) GetSummary(w http.ResponseWriter, r *http.Request) {
method := "category.GetSummary"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
ok := permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceManage, pm.SpaceOwner, pm.SpaceView)
if !ok {
response.WriteForbiddenError(w)
return
}
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)
return
}
if len(s) == 0 {
s = []category.SummaryModel{}
}
response.WriteJSON(w, s)
}
// SetDocumentCategoryMembership will link/unlink document from categories (query string switch mode=link or mode=unlink).
func (h *Handler) SetDocumentCategoryMembership(w http.ResponseWriter, r *http.Request) {
method := "category.addMember"
ctx := domain.GetRequestContext(r)
mode := request.Query(r, "mode")
if len(mode) == 0 {
response.WriteMissingDataError(w, method, "mode")
return
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, "body")
h.Runtime.Log.Error(method, err)
return
}
var cats []category.Member
err = json.Unmarshal(body, &cats)
if err != nil {
response.WriteBadRequestError(w, method, "category")
h.Runtime.Log.Error(method, err)
return
}
if len(cats) == 0 {
response.WriteEmpty(w)
return
}
if !permission.HasPermission(ctx, *h.Store, cats[0].LabelID, pm.DocumentAdd, pm.DocumentEdit) {
response.WriteForbiddenError(w)
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
for _, c := range cats {
if mode == "link" {
c.OrgID = ctx.OrgID
c.RefID = uniqueid.Generate()
_, err = h.Store.Category.DisassociateDocument(ctx, c.CategoryID, c.DocumentID)
err = h.Store.Category.AssociateDocument(ctx, c)
} else {
_, err = h.Store.Category.DisassociateDocument(ctx, c.CategoryID, c.DocumentID)
}
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
}
h.Store.Audit.Record(ctx, audit.EventTypeCategoryLink)
ctx.Transaction.Commit()
response.WriteEmpty(w)
}
// GetDocumentCategoryMembership returns user viewable categories associated with a given document.
func (h *Handler) GetDocumentCategoryMembership(w http.ResponseWriter, r *http.Request) {
method := "category.GetDocumentCategoryMembership"
ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
doc, err := h.Store.Document.Get(ctx, documentID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error("no document for category", err)
return
}
if !permission.HasPermission(ctx, *h.Store, doc.LabelID, pm.SpaceView, pm.DocumentAdd, pm.DocumentEdit) {
response.WriteForbiddenError(w)
return
}
cat, err := h.Store.Category.GetDocumentCategoryMembership(ctx, doc.RefID)
if err != nil && err != sql.ErrNoRows {
h.Runtime.Log.Error("get document category membership", err)
response.WriteServerError(w, method, err)
return
}
if len(cat) == 0 {
cat = []category.Category{}
}
perm, err := h.Store.Permission.GetUserCategoryPermissions(ctx, ctx.UserID)
if err != nil {
h.Runtime.Log.Error("get user category permissions", err)
response.WriteServerError(w, method, err)
return
}
see := []category.Category{}
for _, c := range cat {
for _, p := range perm {
if p.RefID == c.RefID {
see = append(see, c)
break
}
}
}
response.WriteJSON(w, see)
}
// GetSpaceCategoryMembers returns category/document associations within space.
func (h *Handler) GetSpaceCategoryMembers(w http.ResponseWriter, r *http.Request) {
method := "category.GetSpaceCategoryMembers"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
if !permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceView) {
response.WriteForbiddenError(w)
return
}
cat, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
h.Runtime.Log.Error("get document category membership for space", err)
response.WriteServerError(w, method, err)
return
}
if len(cat) == 0 {
cat = []category.Member{}
}
response.WriteJSON(w, cat)
}
// FetchSpaceData returns:
// 1. categories that user can see for given space
// 2. summary data for each category
// 3. category viewing membership records
func (h *Handler) FetchSpaceData(w http.ResponseWriter, r *http.Request) {
method := "category.FetchSpaceData"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
ok := permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceManage, pm.SpaceOwner, pm.SpaceView)
if !ok {
response.WriteForbiddenError(w)
return
}
fetch := category.FetchSpaceModel{}
// get space categories visible to user
cat, err := h.Store.Category.GetBySpace(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
h.Runtime.Log.Error("get space categories visible to user failed", err)
response.WriteServerError(w, method, err)
return
}
if len(cat) == 0 {
cat = []category.Category{}
}
// summary of space category usage
summary, 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)
return
}
if len(summary) == 0 {
summary = []category.SummaryModel{}
}
// get category membership records
member, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
h.Runtime.Log.Error("get document category membership for space", err)
response.WriteServerError(w, method, err)
return
}
if len(member) == 0 {
member = []category.Member{}
}
fetch.Category = cat
fetch.Summary = summary
fetch.Membership = member
response.WriteJSON(w, fetch)
}

View file

@ -0,0 +1,251 @@
// 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) {
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' 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
}
// 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) {
err = s.Runtime.Db.Select(&c, `
SELECT 'documents' as type, categoryid, COUNT(*) as count FROM categorymember WHERE orgid=? AND labelid=? GROUP BY categoryid, type
UNION ALL
SELECT 'users' as type, refid AS categoryid, count(*) AS count FROM permission WHERE orgid=? AND who='user' AND location='category'
AND refid IN (SELECT refid FROM category WHERE orgid=? AND labelid=?)
GROUP BY refid, type
UNION ALL
SELECT 'users' as type, p.refid AS categoryid, count(*) AS count FROM rolemember r LEFT JOIN permission p ON p.whoid=r.roleid
WHERE p.orgid=? AND p.who='role' AND p.location='category'
AND p.refid IN (SELECT refid FROM category WHERE orgid=? AND labelid=?)
GROUP BY p.refid, type`,
ctx.OrgID, spaceID, ctx.OrgID, 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) {
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=? AND location='space' 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=?
))
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
}

View file

@ -28,7 +28,7 @@ import (
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/conversion/store" "github.com/documize/community/domain/conversion/store"
"github.com/documize/community/domain/document" "github.com/documize/community/domain/permission"
"github.com/documize/community/model/activity" "github.com/documize/community/model/activity"
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
@ -50,7 +50,7 @@ func (h *Handler) upload(w http.ResponseWriter, r *http.Request) (string, string
folderID := request.Param(r, "folderID") folderID := request.Param(r, "folderID")
if !document.CanUploadDocument(ctx, *h.Store, folderID) { if !permission.CanUploadDocument(ctx, *h.Store, folderID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return "", "", "" return "", "", ""
} }

View file

@ -23,13 +23,15 @@ import (
"github.com/documize/community/core/streamutil" "github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil" "github.com/documize/community/core/stringutil"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search" indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/space"
"github.com/documize/community/model/activity" "github.com/documize/community/model/activity"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/doc" "github.com/documize/community/model/doc"
"github.com/documize/community/model/link" "github.com/documize/community/model/link"
pm "github.com/documize/community/model/permission"
"github.com/documize/community/model/search" "github.com/documize/community/model/search"
"github.com/documize/community/model/space"
) )
// Handler contains the runtime information such as logging and database. // Handler contains the runtime information such as logging and database.
@ -61,7 +63,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
return return
} }
if !CanViewDocumentInFolder(ctx, *h.Store, document.LabelID) { if !permission.CanViewSpaceDocument(ctx, *h.Store, document.LabelID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -135,62 +137,63 @@ func (h *Handler) DocumentLinks(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, l) response.WriteJSON(w, l)
} }
// BySpace is an endpoint that returns the documents in a given folder. // BySpace is an endpoint that returns the documents for given space.
func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) { func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
method := "document.space" method := "document.BySpace"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
folderID := request.Query(r, "folder") spaceID := request.Query(r, "space")
if len(spaceID) == 0 {
if len(folderID) == 0 { response.WriteMissingDataError(w, method, "space")
response.WriteMissingDataError(w, method, "folder")
return return
} }
if !space.CanViewSpace(ctx, *h.Store, folderID) { if !permission.CanViewSpace(ctx, *h.Store, spaceID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
documents, err := h.Store.Document.GetBySpace(ctx, folderID) // get complete list of documents
documents, err := h.Store.Document.GetBySpace(ctx, spaceID)
if len(documents) == 0 {
documents = []doc.Document{}
}
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)
return return
} }
response.WriteJSON(w, documents)
}
// ByTag is an endpoint that returns the documents with a given tag.
func (h *Handler) ByTag(w http.ResponseWriter, r *http.Request) {
method := "document.space"
ctx := domain.GetRequestContext(r)
tag := request.Query(r, "tag")
if len(tag) == 0 {
response.WriteMissingDataError(w, method, "tag")
return
}
documents, err := h.Store.Document.GetByTag(ctx, tag)
if len(documents) == 0 { if len(documents) == 0 {
documents = []doc.Document{} documents = []doc.Document{}
} }
if err != nil && err != sql.ErrNoRows { // remove documents that cannot be seen due to lack of
response.WriteServerError(w, method, err) // category view/access permission
h.Runtime.Log.Error(method, err) filtered := []doc.Document{}
return cats, err := h.Store.Category.GetBySpace(ctx, spaceID)
members, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
for _, doc := range documents {
hasCategory := false
canSeeCategory := false
OUTER:
for _, m := range members {
if m.DocumentID == doc.RefID {
hasCategory = true
for _, cat := range cats {
if cat.RefID == m.CategoryID {
canSeeCategory = true
continue OUTER
}
}
}
} }
response.WriteJSON(w, documents) if !hasCategory || canSeeCategory {
filtered = append(filtered, doc)
}
}
response.WriteJSON(w, filtered)
} }
// Update updates an existing document using the // Update updates an existing document using the
@ -205,12 +208,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return return
} }
// if !ctx.Editor { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
// response.WriteForbiddenError(w)
// return
// }
if !CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -240,6 +238,18 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return return
} }
// if space changed for document, remove document categories
oldDoc, err := h.Store.Document.Get(ctx, documentID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if oldDoc.LabelID != d.LabelID {
h.Store.Category.RemoveDocumentCategories(ctx, d.RefID)
}
err = h.Store.Document.Update(ctx, d) err = h.Store.Document.Update(ctx, d)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
@ -269,7 +279,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
if !CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanDeleteDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -363,3 +373,104 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, results) response.WriteJSON(w, results)
} }
// FetchDocumentData returns all document data in single API call.
func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
method := "document.FetchDocumentData"
ctx := domain.GetRequestContext(r)
id := request.Param(r, "documentID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
// document
document, err := h.Store.Document.Get(ctx, id)
if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, id)
return
}
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if !permission.CanViewSpaceDocument(ctx, *h.Store, document.LabelID) {
response.WriteForbiddenError(w)
return
}
// permissions
perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, document.LabelID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(perms) == 0 {
perms = []pm.Permission{}
}
record := pm.DecodeUserPermissions(perms)
// links
l, err := h.Store.Link.GetDocumentOutboundLinks(ctx, id)
if len(l) == 0 {
l = []link.Link{}
}
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// spaces
sp, err := h.Store.Space.GetViewable(ctx)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(sp) == 0 {
sp = []space.Space{}
}
data := documentData{}
data.Document = document
data.Permissions = record
data.Links = l
data.Spaces = sp
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: document.LabelID,
SourceID: document.RefID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeRead})
if err != nil {
h.Runtime.Log.Error(method, err)
}
h.Store.Audit.Record(ctx, audit.EventTypeDocumentView)
ctx.Transaction.Commit()
response.WriteJSON(w, data)
}
// documentData represents all data associated for a single document.
// Used by FetchDocumentData() bulk data load call.
type documentData struct {
Document doc.Document `json:"document"`
Permissions pm.Record `json:"permissions"`
Spaces []space.Space `json:"folders"`
Links []link.Link `json:"link"`
}

View file

@ -17,7 +17,6 @@ import (
"time" "time"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql" "github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/doc" "github.com/documize/community/model/doc"
@ -35,19 +34,11 @@ func (s Scope) Add(ctx domain.RequestContext, document doc.Document) (err error)
document.Created = time.Now().UTC() document.Created = time.Now().UTC()
document.Revised = document.Created // put same time in both fields document.Revised = document.Created // put same time in both fields
stmt, err := ctx.Transaction.Preparex("INSERT INTO document (refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") _, err = ctx.Transaction.Exec("INSERT INTO document (refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
defer streamutil.Close(stmt) document.RefID, document.OrgID, document.LabelID, document.UserID, document.Job, document.Location, document.Title, document.Excerpt, document.Slug, document.Tags, document.Template, document.Created, document.Revised)
if err != nil {
err = errors.Wrap(err, "prepare insert document")
return
}
_, err = stmt.Exec(document.RefID, document.OrgID, document.LabelID, document.UserID, document.Job, document.Location, document.Title, document.Excerpt, document.Slug, document.Tags, document.Template, document.Created, document.Revised)
if err != nil { if err != nil {
err = errors.Wrap(err, "execuet insert document") err = errors.Wrap(err, "execuet insert document")
return
} }
return return
@ -55,18 +46,11 @@ func (s Scope) Add(ctx domain.RequestContext, document doc.Document) (err error)
// Get fetches the document record with the given id fromt the document table and audits that it has been got. // 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) { func (s Scope) Get(ctx domain.RequestContext, id string) (document doc.Document, err error) {
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? and refid=?") err = s.Runtime.Db.Get(&document, "SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? and refid=?",
defer streamutil.Close(stmt) ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "prepare select document")
return
}
err = stmt.Get(&document, ctx.OrgID, id)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute select document") err = errors.Wrap(err, "execute select document")
return
} }
return return
@ -112,46 +96,30 @@ func (s Scope) GetAll() (ctx domain.RequestContext, documents []doc.Document, er
if err != nil { if err != nil {
err = errors.Wrap(err, "select documents") err = errors.Wrap(err, "select documents")
return
} }
return return
} }
// GetBySpace returns a slice containing the documents for a given space, most recient first. // GetBySpace returns a slice containing the documents for a given space.
func (s Scope) GetBySpace(ctx domain.RequestContext, folderID string) (documents []doc.Document, err error) { // No attempt is made to hide documents that are protected
err = s.Runtime.Db.Select(&documents, "SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND template=0 AND labelid=? ORDER BY revised DESC", ctx.OrgID, folderID) // by category permissions -- caller must filter as required.
func (s Scope) GetBySpace(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, layout, 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=? AND location='space' 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=?
))
)
ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, spaceID, ctx.UserID)
if err != nil { if err != nil {
err = errors.Wrap(err, "select documents by space") err = errors.Wrap(err, "select documents by space")
return
}
return
}
// GetByTag returns a slice containing the documents with the specified tag, in title order.
func (s Scope) GetByTag(ctx domain.RequestContext, tag string) (documents []doc.Document, err error) {
tagQuery := "tags LIKE '%#" + tag + "#%'"
err = s.Runtime.Db.Select(&documents,
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND template=0 AND `+tagQuery+` AND labelid IN
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
ORDER BY title`,
ctx.OrgID,
ctx.OrgID,
ctx.UserID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID)
if err != nil {
err = errors.Wrap(err, "select documents by tag")
return
} }
return return
@ -160,23 +128,20 @@ func (s Scope) GetByTag(ctx domain.RequestContext, tag string) (documents []doc.
// Templates returns a slice containing the documents available as templates to the client's organisation, in title order. // Templates returns a slice containing the documents available as templates to the client's organisation, in title order.
func (s Scope) Templates(ctx domain.RequestContext) (documents []doc.Document, err error) { func (s Scope) Templates(ctx domain.RequestContext) (documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents, err = s.Runtime.Db.Select(&documents,
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND template=1 AND labelid IN `SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND template=1
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=? AND labelid IN
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) (
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) SELECT refid FROM label WHERE orgid=?
ORDER BY title`, AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
ctx.OrgID, SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? AND location='space'
ctx.OrgID, UNION ALL
ctx.UserID, 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=?
ctx.OrgID, ))
ctx.OrgID, )
ctx.OrgID, ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
ctx.OrgID,
ctx.UserID)
if err != nil { if err != nil {
err = errors.Wrap(err, "select document templates") err = errors.Wrap(err, "select document templates")
return
} }
return return
@ -185,20 +150,17 @@ func (s Scope) Templates(ctx domain.RequestContext) (documents []doc.Document, e
// TemplatesBySpace returns a slice containing the documents available as templates for given space. // 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) { func (s Scope) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents, err = s.Runtime.Db.Select(&documents,
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND labelid=? AND template=1 AND labelid IN `SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND labelid=? AND template=1
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=? AND labelid IN
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) (
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) SELECT refid FROM label WHERE orgid=?
ORDER BY title`, AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
ctx.OrgID, SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? AND location='space'
spaceID, UNION ALL
ctx.OrgID, 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=?
ctx.UserID, ))
ctx.OrgID, )
ctx.OrgID, ORDER BY title`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
ctx.OrgID,
ctx.OrgID,
ctx.UserID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
err = nil err = nil
@ -207,7 +169,6 @@ func (s Scope) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (docu
if err != nil { if err != nil {
err = errors.Wrap(err, "select space document templates") err = errors.Wrap(err, "select space document templates")
return
} }
return return
@ -224,7 +185,6 @@ func (s Scope) PublicDocuments(ctx domain.RequestContext, orgID string) (documen
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute GetPublicDocuments for org %s%s", orgID)) err = errors.Wrap(err, fmt.Sprintf("execute GetPublicDocuments for org %s%s", orgID))
return
} }
return return
@ -233,19 +193,17 @@ func (s Scope) PublicDocuments(ctx domain.RequestContext, orgID string) (documen
// DocumentList returns a slice containing the documents available as templates to the client's organisation, in title order. // DocumentList returns a slice containing the documents available as templates to the client's organisation, in title order.
func (s Scope) DocumentList(ctx domain.RequestContext) (documents []doc.Document, err error) { func (s Scope) DocumentList(ctx domain.RequestContext) (documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents, err = s.Runtime.Db.Select(&documents,
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND template=0 AND labelid IN `SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND template=0
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=? AND labelid IN
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) (
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) SELECT refid FROM label WHERE orgid=?
ORDER BY title`, AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
ctx.OrgID, SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? AND location='space'
ctx.OrgID, UNION ALL
ctx.UserID, 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=?
ctx.OrgID, ))
ctx.OrgID, )
ctx.OrgID, ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
ctx.OrgID,
ctx.UserID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
err = nil err = nil
@ -254,7 +212,6 @@ func (s Scope) DocumentList(ctx domain.RequestContext) (documents []doc.Document
if err != nil { if err != nil {
err = errors.Wrap(err, "select documents list") err = errors.Wrap(err, "select documents list")
return
} }
return return
@ -264,19 +221,11 @@ func (s Scope) DocumentList(ctx domain.RequestContext) (documents []doc.Document
func (s Scope) Update(ctx domain.RequestContext, document doc.Document) (err error) { func (s Scope) Update(ctx domain.RequestContext, document doc.Document) (err error) {
document.Revised = time.Now().UTC() document.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.PrepareNamed("UPDATE document SET labelid=:labelid, userid=:userid, job=:job, location=:location, title=:title, excerpt=:excerpt, slug=:slug, tags=:tags, template=:template, layout=:layout, revised=:revised WHERE orgid=:orgid AND refid=:refid") _, 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, layout=:layout, revised=:revised WHERE orgid=:orgid AND refid=:refid",
defer streamutil.Close(stmt) &document)
if err != nil {
err = errors.Wrap(err, "prepare update document")
return
}
_, err = stmt.Exec(&document)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute update document") err = errors.Wrap(err, "execute update document")
return
} }
return return
@ -286,19 +235,11 @@ func (s Scope) Update(ctx domain.RequestContext, document doc.Document) (err err
func (s Scope) ChangeDocumentSpace(ctx domain.RequestContext, document, space string) (err error) { func (s Scope) ChangeDocumentSpace(ctx domain.RequestContext, document, space string) (err error) {
revised := time.Now().UTC() revised := time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("UPDATE document SET labelid=?, revised=? WHERE orgid=? AND refid=?") _, err = ctx.Transaction.Exec("UPDATE document SET labelid=?, revised=? WHERE orgid=? AND refid=?",
defer streamutil.Close(stmt) space, revised, ctx.OrgID, document)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare change document space %s", document))
return
}
_, err = stmt.Exec(space, revised, ctx.OrgID, document)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute change document space %s", document)) err = errors.Wrap(err, fmt.Sprintf("execute change document space %s", document))
return
} }
return return
@ -306,25 +247,18 @@ func (s Scope) ChangeDocumentSpace(ctx domain.RequestContext, document, space st
// MoveDocumentSpace changes the space for client's organization's documents which have space "id", to "move". // 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) { func (s Scope) MoveDocumentSpace(ctx domain.RequestContext, id, move string) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE document SET labelid=? WHERE orgid=? AND labelid=?") _, err = ctx.Transaction.Exec("UPDATE document SET labelid=? WHERE orgid=? AND labelid=?",
defer streamutil.Close(stmt) move, ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare document space move %s", id))
return
}
_, err = stmt.Exec(move, ctx.OrgID, id)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute document space move %s", id)) err = errors.Wrap(err, fmt.Sprintf("execute document space move %s", id))
return
} }
return return
} }
// Delete delete the document pages in the database, updates the search subsystem, deletes the associated revisions and attachments, // Delete removes the specified document.
// audits the deletion, then finally deletes the document itself. // Remove document pages, revisions, attachments, updates the search subsystem.
func (s Scope) Delete(ctx domain.RequestContext, documentID string) (rows int64, err error) { func (s Scope) Delete(ctx domain.RequestContext, documentID string) (rows int64, err error) {
b := mysql.BaseQuery{} b := mysql.BaseQuery{}
rows, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE from page WHERE documentid=\"%s\" AND orgid=\"%s\"", documentID, ctx.OrgID)) rows, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE from page WHERE documentid=\"%s\" AND orgid=\"%s\"", documentID, ctx.OrgID))
@ -345,3 +279,26 @@ func (s Scope) Delete(ctx domain.RequestContext, documentID string) (rows int64,
return b.DeleteConstrained(ctx.Transaction, "document", ctx.OrgID, documentID) 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
}
return b.DeleteConstrained(ctx.Transaction, "document", ctx.OrgID, spaceID)
}

View file

@ -1,116 +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 document
import (
"database/sql"
"github.com/documize/community/domain"
)
// CanViewDocumentInFolder returns if the user has permission to view a document within the specified folder.
func CanViewDocumentInFolder(ctx domain.RequestContext, s domain.Store, labelID string) (hasPermission bool) {
roles, err := s.Space.GetUserRoles(ctx)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.LabelID == labelID && (role.CanView || role.CanEdit) {
return true
}
}
return false
}
// CanViewDocument returns if the clinet has permission to view a given document.
func CanViewDocument(ctx domain.RequestContext, s domain.Store, documentID string) (hasPermission bool) {
document, err := s.Document.Get(ctx, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
roles, err := s.Space.GetUserRoles(ctx)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.LabelID == document.LabelID && (role.CanView || role.CanEdit) {
return true
}
}
return false
}
// CanChangeDocument returns if the clinet has permission to change a given document.
func CanChangeDocument(ctx domain.RequestContext, s domain.Store, documentID string) (hasPermission bool) {
document, err := s.Document.Get(ctx, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
roles, err := s.Space.GetUserRoles(ctx)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.LabelID == document.LabelID && role.CanEdit {
return true
}
}
return false
}
// CanUploadDocument returns if the client has permission to upload documents to the given folderID.
func CanUploadDocument(ctx domain.RequestContext, s domain.Store, folderID string) (hasPermission bool) {
roles, err := s.Space.GetUserRoles(ctx)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.LabelID == folderID && role.CanEdit {
return true
}
}
return false
}

View file

@ -21,7 +21,7 @@ import (
"github.com/documize/community/core/response" "github.com/documize/community/core/response"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/document" "github.com/documize/community/domain/permission"
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
"github.com/documize/community/model/link" "github.com/documize/community/model/link"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
@ -57,7 +57,7 @@ func (h *Handler) GetLinkCandidates(w http.ResponseWriter, r *http.Request) {
} }
// permission check // permission check
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }

View file

@ -12,12 +12,12 @@
package mysql package mysql
import ( import (
"database/sql"
"fmt" "fmt"
"strings" "strings"
"time" "time"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql" "github.com/documize/community/domain/store/mysql"
@ -36,18 +36,11 @@ func (s Scope) Add(ctx domain.RequestContext, l link.Link) (err error) {
l.Created = time.Now().UTC() l.Created = time.Now().UTC()
l.Revised = time.Now().UTC() l.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO link (refid, orgid, folderid, userid, sourcedocumentid, sourcepageid, targetdocumentid, targetid, linktype, orphan, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") _, err = ctx.Transaction.Exec("INSERT INTO link (refid, orgid, folderid, userid, sourcedocumentid, sourcepageid, targetdocumentid, targetid, linktype, orphan, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
defer streamutil.Close(stmt) l.RefID, l.OrgID, l.FolderID, l.UserID, l.SourceDocumentID, l.SourcePageID, l.TargetDocumentID, l.TargetID, l.LinkType, l.Orphan, l.Created, l.Revised)
if err != nil {
err = errors.Wrap(err, "prepare link insert")
return
}
_, err = stmt.Exec(l.RefID, l.OrgID, l.FolderID, l.UserID, l.SourceDocumentID, l.SourcePageID, l.TargetDocumentID, l.TargetID, l.LinkType, l.Orphan, l.Created, l.Revised)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute link insert") err = errors.Wrap(err, "execute link insert")
return
} }
return return
@ -62,7 +55,8 @@ func (s Scope) GetDocumentOutboundLinks(ctx domain.RequestContext, documentID st
ctx.OrgID, ctx.OrgID,
documentID) documentID)
if err != nil { if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "select document oubound links")
return return
} }
@ -83,7 +77,8 @@ func (s Scope) GetPageLinks(ctx domain.RequestContext, documentID, pageID string
documentID, documentID,
pageID) pageID)
if err != nil { if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "get page links")
return return
} }
@ -98,15 +93,13 @@ func (s Scope) GetPageLinks(ctx domain.RequestContext, documentID, pageID string
func (s Scope) MarkOrphanDocumentLink(ctx domain.RequestContext, documentID string) (err error) { func (s Scope) MarkOrphanDocumentLink(ctx domain.RequestContext, documentID string) (err error) {
revised := time.Now().UTC() revised := time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='document' AND orgid=? AND targetdocumentid=?") _, err = ctx.Transaction.Exec("UPDATE link SET orphan=1, revised=? WHERE linktype='document' AND orgid=? AND targetdocumentid=?",
defer streamutil.Close(stmt) revised, ctx.OrgID, documentID)
if err != nil { if err != nil {
return err = errors.Wrap(err, "mark link as orphan")
} }
_, err = stmt.Exec(revised, ctx.OrgID, documentID)
return return
} }
@ -114,15 +107,12 @@ func (s Scope) MarkOrphanDocumentLink(ctx domain.RequestContext, documentID stri
func (s Scope) MarkOrphanPageLink(ctx domain.RequestContext, pageID string) (err error) { func (s Scope) MarkOrphanPageLink(ctx domain.RequestContext, pageID string) (err error) {
revised := time.Now().UTC() revised := time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='section' AND orgid=? AND targetid=?") _, err = ctx.Transaction.Exec("UPDATE link SET orphan=1, revised=? WHERE linktype='section' AND orgid=? AND targetid=?", revised, ctx.OrgID, pageID)
defer streamutil.Close(stmt)
if err != nil { if err != nil {
return err = errors.Wrap(err, "mark orphan page link")
} }
_, err = stmt.Exec(revised, ctx.OrgID, pageID)
return return
} }
@ -130,15 +120,13 @@ func (s Scope) MarkOrphanPageLink(ctx domain.RequestContext, pageID string) (err
func (s Scope) MarkOrphanAttachmentLink(ctx domain.RequestContext, attachmentID string) (err error) { func (s Scope) MarkOrphanAttachmentLink(ctx domain.RequestContext, attachmentID string) (err error) {
revised := time.Now().UTC() revised := time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='file' AND orgid=? AND targetid=?") _, err = ctx.Transaction.Exec("UPDATE link SET orphan=1, revised=? WHERE linktype='file' AND orgid=? AND targetid=?",
defer streamutil.Close(stmt) revised, ctx.OrgID, attachmentID)
if err != nil { if err != nil {
return err = errors.Wrap(err, "mark orphan attachment link")
} }
_, err = stmt.Exec(revised, ctx.OrgID, attachmentID)
return return
} }
@ -169,21 +157,19 @@ func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (doc
keywords = strings.TrimSpace(strings.ToLower(keywords)) keywords = strings.TrimSpace(strings.ToLower(keywords))
likeQuery := "LOWER(title) LIKE '%" + keywords + "%'" likeQuery := "LOWER(title) LIKE '%" + keywords + "%'"
err = s.Runtime.Db.Select(&temp, err = s.Runtime.Db.Select(&temp, `
`SELECT d.refid as documentid, d. labelid as folderid, d.title, l.label as context 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 FROM document d LEFT JOIN label l ON d.labelid=l.refid WHERE l.orgid=? AND `+likeQuery+`
(SELECT refid FROM label WHERE orgid=? AND type=2 AND userid=? AND d.labelid IN
UNION ALL SELECT refid FROM label a WHERE orgid=? AND type=1 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) (
UNION ALL SELECT refid FROM label a WHERE orgid=? AND type=3 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) SELECT refid FROM label WHERE orgid=?
ORDER BY title`, AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
ctx.OrgID, SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? AND location='space'
ctx.OrgID, UNION ALL
ctx.UserID, 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=?
ctx.OrgID, ))
ctx.OrgID, )
ctx.OrgID, ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
ctx.OrgID,
ctx.UserID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute search links 1") err = errors.Wrap(err, "execute search links 1")
@ -210,19 +196,17 @@ func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (doc
err = s.Runtime.Db.Select(&temp, 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 `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 FROM page p LEFT JOIN document d ON d.refid=p.documentid WHERE p.orgid=? AND `+likeQuery+`
(SELECT refid FROM label WHERE orgid=? AND type=2 AND userid=? AND d.labelid IN
UNION ALL SELECT refid FROM label a WHERE orgid=? AND type=1 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) (
UNION ALL SELECT refid FROM label a WHERE orgid=? AND type=3 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) SELECT refid FROM label WHERE orgid=?
ORDER BY p.title`, AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
ctx.OrgID, SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? AND location='space'
ctx.OrgID, UNION ALL
ctx.UserID, 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=?
ctx.OrgID, ))
ctx.OrgID, )
ctx.OrgID, ORDER BY p.title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
ctx.OrgID,
ctx.UserID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute search links 2") err = errors.Wrap(err, "execute search links 2")
@ -249,19 +233,17 @@ func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (doc
err = s.Runtime.Db.Select(&temp, 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 `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 FROM attachment a LEFT JOIN document d ON d.refid=a.documentid WHERE a.orgid=? AND `+likeQuery+`
(SELECT refid FROM label WHERE orgid=? AND type=2 AND userid=? AND d.labelid IN
UNION ALL SELECT refid FROM label a WHERE orgid=? AND type=1 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) (
UNION ALL SELECT refid FROM label a WHERE orgid=? AND type=3 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) SELECT refid FROM label WHERE orgid=?
ORDER BY a.filename`, AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
ctx.OrgID, SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? AND location='space'
ctx.OrgID, UNION ALL
ctx.UserID, 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=?
ctx.OrgID, ))
ctx.OrgID, )
ctx.OrgID, ORDER BY a.filename`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
ctx.OrgID,
ctx.UserID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute search links 3") err = errors.Wrap(err, "execute search links 3")

View file

@ -168,12 +168,12 @@ func (m *Mailer) PasswordReset(recipient, url string) {
} }
} }
// ShareFolderExistingUser provides an existing user with a link to a newly shared folder. // ShareSpaceExistingUser provides an existing user with a link to a newly shared space.
func (m *Mailer) ShareFolderExistingUser(recipient, inviter, url, folder, intro string) { func (m *Mailer) ShareSpaceExistingUser(recipient, inviter, url, folder, intro string) {
method := "ShareFolderExistingUser" method := "ShareSpaceExistingUser"
m.LoadCredentials() m.LoadCredentials()
file, err := web.ReadFile("mail/share-folder-existing-user.html") file, err := web.ReadFile("mail/share-space-existing-user.html")
if err != nil { if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err) m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return return
@ -218,12 +218,12 @@ func (m *Mailer) ShareFolderExistingUser(recipient, inviter, url, folder, intro
} }
} }
// ShareFolderNewUser invites new user providing Credentials, explaining the product and stating who is inviting them. // ShareSpaceNewUser invites new user providing Credentials, explaining the product and stating who is inviting them.
func (m *Mailer) ShareFolderNewUser(recipient, inviter, url, folder, invitationMessage string) { func (m *Mailer) ShareSpaceNewUser(recipient, inviter, url, space, invitationMessage string) {
method := "ShareFolderNewUser" method := "ShareSpaceNewUser"
m.LoadCredentials() m.LoadCredentials()
file, err := web.ReadFile("mail/share-folder-new-user.html") file, err := web.ReadFile("mail/share-space-new-user.html")
if err != nil { if err != nil {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err) m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return return
@ -236,7 +236,7 @@ func (m *Mailer) ShareFolderNewUser(recipient, inviter, url, folder, invitationM
inviter = "Your colleague" inviter = "Your colleague"
} }
subject := fmt.Sprintf("%s has shared %s with you on Documize", inviter, folder) subject := fmt.Sprintf("%s has shared %s with you on Documize", inviter, space)
e := NewEmail() e := NewEmail()
e.From = m.Credentials.SMTPsender e.From = m.Credentials.SMTPsender
@ -254,7 +254,7 @@ func (m *Mailer) ShareFolderNewUser(recipient, inviter, url, folder, invitationM
inviter, inviter,
url, url,
invitationMessage, invitationMessage,
folder, space,
} }
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)

View file

@ -22,7 +22,6 @@ import (
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql" "github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/org" "github.com/documize/community/model/org"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -32,25 +31,17 @@ type Scope struct {
} }
// AddOrganization inserts the passed organization record into the organization table. // AddOrganization inserts the passed organization record into the organization table.
func (s Scope) AddOrganization(ctx domain.RequestContext, org org.Organization) error { func (s Scope) AddOrganization(ctx domain.RequestContext, org org.Organization) (err error) {
org.Created = time.Now().UTC() org.Created = time.Now().UTC()
org.Revised = time.Now().UTC() org.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex( _, err = ctx.Transaction.Exec(
"INSERT INTO organization (refid, company, title, message, url, domain, email, allowanonymousaccess, serial, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") "INSERT INTO organization (refid, company, title, message, url, domain, email, allowanonymousaccess, serial, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
defer streamutil.Close(stmt) org.RefID, org.Company, org.Title, org.Message, strings.ToLower(org.URL), strings.ToLower(org.Domain),
if err != nil {
err = errors.Wrap(err, "unable to prepare insert for org")
return err
}
_, err = stmt.Exec(org.RefID, org.Company, org.Title, org.Message, strings.ToLower(org.URL), strings.ToLower(org.Domain),
strings.ToLower(org.Email), org.AllowAnonymousAccess, org.Serial, org.Created, org.Revised) strings.ToLower(org.Email), org.AllowAnonymousAccess, org.Serial, org.Created, org.Revised)
if err != nil { if err != nil {
err = errors.Wrap(err, "unable to execute insert for org") err = errors.Wrap(err, "unable to execute insert for org")
return err
} }
return nil return nil
@ -88,33 +79,18 @@ func (s Scope) GetOrganizationByDomain(subdomain string) (o org.Organization, er
return return
} }
var stmt *sqlx.Stmt err = s.Runtime.Db.Get(&o, "SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, created, revised FROM organization WHERE domain=? AND active=1",
stmt, err = s.Runtime.Db.Preparex("SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, created, revised FROM organization WHERE domain=? AND active=1") subdomain)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare select for subdomain %s", subdomain))
return
}
err = stmt.Get(&o, subdomain)
if err == nil { if err == nil {
return return
} }
// we try to match on empty domain as last resort // we try to match on empty domain as last resort
stmt, err = s.Runtime.Db.Preparex("SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, created, revised FROM organization WHERE domain='' AND active=1") err = s.Runtime.Db.Get(&o, "SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, created, revised FROM organization WHERE domain='' AND active=1")
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "unable to prepare select for empty subdomain")
return
}
err = stmt.Get(&o)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "unable to execute select for empty subdomain") err = errors.Wrap(err, "unable to execute select for empty subdomain")
return
} }
return return
@ -124,18 +100,11 @@ func (s Scope) GetOrganizationByDomain(subdomain string) (o org.Organization, er
func (s Scope) UpdateOrganization(ctx domain.RequestContext, org org.Organization) (err error) { func (s Scope) UpdateOrganization(ctx domain.RequestContext, org org.Organization) (err error) {
org.Revised = time.Now().UTC() org.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.PrepareNamed("UPDATE organization SET title=:title, message=:message, service=:conversionendpoint, email=:email, allowanonymousaccess=:allowanonymousaccess, revised=:revised WHERE refid=:refid") _, err = ctx.Transaction.NamedExec("UPDATE organization SET title=:title, message=:message, service=:conversionendpoint, email=:email, allowanonymousaccess=:allowanonymousaccess, revised=:revised WHERE refid=:refid",
defer streamutil.Close(stmt) &org)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare update for org %s", org.RefID))
return
}
_, err = stmt.Exec(&org)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute update for org %s", org.RefID)) err = errors.Wrap(err, fmt.Sprintf("unable to execute update for org %s", org.RefID))
return
} }
return return
@ -149,18 +118,10 @@ func (s Scope) DeleteOrganization(ctx domain.RequestContext, orgID string) (rows
// RemoveOrganization sets the orgID organization to be inactive, thus executing a "soft delete" operation. // 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) { func (s Scope) RemoveOrganization(ctx domain.RequestContext, orgID string) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE organization SET active=0 WHERE refid=?") _, err = ctx.Transaction.Exec("UPDATE organization SET active=0 WHERE refid=?", orgID)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare soft delete for org %s", orgID))
return
}
_, err = stmt.Exec(orgID)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute soft delete for org %s", orgID)) err = errors.Wrap(err, fmt.Sprintf("unable to execute soft delete for org %s", orgID))
return
} }
return return
@ -170,18 +131,11 @@ func (s Scope) RemoveOrganization(ctx domain.RequestContext, orgID string) (err
func (s Scope) UpdateAuthConfig(ctx domain.RequestContext, org org.Organization) (err error) { func (s Scope) UpdateAuthConfig(ctx domain.RequestContext, org org.Organization) (err error) {
org.Revised = time.Now().UTC() org.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.PrepareNamed("UPDATE organization SET allowanonymousaccess=:allowanonymousaccess, authprovider=:authprovider, authconfig=:authconfig, revised=:revised WHERE refid=:refid") _, err = ctx.Transaction.NamedExec("UPDATE organization SET allowanonymousaccess=:allowanonymousaccess, authprovider=:authprovider, authconfig=:authconfig, revised=:revised WHERE refid=:refid",
defer streamutil.Close(stmt) &org)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare UpdateAuthConfig %s", org.RefID))
return
}
_, err = stmt.Exec(&org)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute UpdateAuthConfig %s", org.RefID)) err = errors.Wrap(err, fmt.Sprintf("unable to execute UpdateAuthConfig %s", org.RefID))
return
} }
return return

View file

@ -25,8 +25,8 @@ import (
"github.com/documize/community/core/streamutil" "github.com/documize/community/core/streamutil"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/document"
"github.com/documize/community/domain/link" "github.com/documize/community/domain/link"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search" indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
"github.com/documize/community/model/activity" "github.com/documize/community/model/activity"
@ -59,7 +59,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -165,7 +165,7 @@ func (h *Handler) GetPage(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -200,7 +200,7 @@ func (h *Handler) GetPages(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -227,47 +227,6 @@ func (h *Handler) GetPages(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, pages) response.WriteJSON(w, pages)
} }
// GetPagesBatch gets specified pages for document.
func (h *Handler) GetPagesBatch(w http.ResponseWriter, r *http.Request) {
method := "page.batch"
ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
if !document.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
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
}
requestedPages := string(body)
pages, err := h.Store.Page.GetPagesWhereIn(ctx, documentID, requestedPages)
if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, documentID)
h.Runtime.Log.Error(method, err)
return
}
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
response.WriteJSON(w, pages)
}
// Delete deletes a page. // Delete deletes a page.
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) { func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
method := "page.delete" method := "page.delete"
@ -290,7 +249,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -366,7 +325,7 @@ func (h *Handler) DeletePages(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -471,7 +430,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -617,7 +576,7 @@ func (h *Handler) ChangePageSequence(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -678,7 +637,7 @@ func (h *Handler) ChangePageLevel(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -740,7 +699,7 @@ func (h *Handler) GetMeta(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -816,7 +775,7 @@ func (h *Handler) Copy(w http.ResponseWriter, r *http.Request) {
} }
// permission // permission
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -922,7 +881,7 @@ func (h *Handler) GetDocumentRevisions(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -953,7 +912,7 @@ func (h *Handler) GetRevisions(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -1000,7 +959,7 @@ func (h *Handler) GetDiff(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -1064,7 +1023,7 @@ func (h *Handler) Rollback(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }

View file

@ -12,16 +12,14 @@
package mysql package mysql
import ( import (
"database/sql"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql" "github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -56,33 +54,14 @@ func (s Scope) Add(ctx domain.RequestContext, model page.NewPage) (err error) {
model.Page.Sequence = maxSeq * 2 model.Page.Sequence = maxSeq * 2
} }
stmt, err := ctx.Transaction.Preparex("INSERT INTO page (refid, orgid, documentid, userid, contenttype, pagetype, level, title, body, revisions, sequence, blockid, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") _, err = ctx.Transaction.Exec("INSERT INTO page (refid, orgid, documentid, userid, contenttype, pagetype, level, title, body, revisions, sequence, blockid, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
defer streamutil.Close(stmt) model.Page.RefID, model.Page.OrgID, model.Page.DocumentID, model.Page.UserID, model.Page.ContentType, model.Page.PageType, model.Page.Level, model.Page.Title, model.Page.Body, model.Page.Revisions, model.Page.Sequence, model.Page.BlockID, model.Page.Created, model.Page.Revised)
if err != nil { _, err = ctx.Transaction.Exec("INSERT INTO pagemeta (pageid, orgid, userid, documentid, rawbody, config, externalsource, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
err = errors.Wrap(err, "prepare page insert") model.Meta.PageID, model.Meta.OrgID, model.Meta.UserID, model.Meta.DocumentID, model.Meta.RawBody, model.Meta.Config, model.Meta.ExternalSource, model.Meta.Created, model.Meta.Revised)
return
}
_, err = stmt.Exec(model.Page.RefID, model.Page.OrgID, model.Page.DocumentID, model.Page.UserID, model.Page.ContentType, model.Page.PageType, model.Page.Level, model.Page.Title, model.Page.Body, model.Page.Revisions, model.Page.Sequence, model.Page.BlockID, model.Page.Created, model.Page.Revised)
if err != nil {
err = errors.Wrap(err, "execute page insert")
return
}
stmt2, err := ctx.Transaction.Preparex("INSERT INTO pagemeta (pageid, orgid, userid, documentid, rawbody, config, externalsource, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt2)
if err != nil {
err = errors.Wrap(err, "prepare page meta insert")
return
}
_, err = stmt2.Exec(model.Meta.PageID, model.Meta.OrgID, model.Meta.UserID, model.Meta.DocumentID, model.Meta.RawBody, model.Meta.Config, model.Meta.ExternalSource, model.Meta.Created, model.Meta.Revised)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute page meta insert") err = errors.Wrap(err, "execute page meta insert")
return
} }
return return
@ -90,18 +69,11 @@ func (s Scope) Add(ctx domain.RequestContext, model page.NewPage) (err error) {
// Get returns the pageID page record from the page table. // Get returns the pageID page record from the page table.
func (s Scope) Get(ctx domain.RequestContext, pageID string) (p page.Page, err error) { func (s Scope) Get(ctx domain.RequestContext, pageID string) (p page.Page, err error) {
stmt, err := s.Runtime.Db.Preparex("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.created, a.revised FROM page a WHERE a.orgid=? AND a.refid=?") err = s.Runtime.Db.Get(&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.created, a.revised FROM page a WHERE a.orgid=? AND a.refid=?",
defer streamutil.Close(stmt) ctx.OrgID, pageID)
if err != nil {
err = errors.Wrap(err, "prepare get page")
return
}
err = stmt.Get(&p, ctx.OrgID, pageID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute get page") err = errors.Wrap(err, "execute get page")
return
} }
return return
@ -113,59 +85,6 @@ func (s Scope) GetPages(ctx domain.RequestContext, documentID string) (p []page.
if err != nil { if err != nil {
err = errors.Wrap(err, "execute get pages") err = errors.Wrap(err, "execute get pages")
return
}
return
}
// GetPagesWhereIn returns a slice, in presentation sequence, containing those page records for a given documentID
// where their refid is in the comma-separated list passed as inPages.
func (s Scope) GetPagesWhereIn(ctx domain.RequestContext, documentID, inPages string) (p []page.Page, err error) {
args := []interface{}{ctx.OrgID, documentID}
tempValues := strings.Split(inPages, ",")
sql := "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.pagetype, a.level, a.sequence, a.title, a.body, a.blockid, a.revisions, a.created, a.revised FROM page a WHERE a.orgid=? AND a.documentid=? AND a.refid IN (?" + strings.Repeat(",?", len(tempValues)-1) + ") ORDER BY sequence"
inValues := make([]interface{}, len(tempValues))
for i, v := range tempValues {
inValues[i] = interface{}(v)
}
args = append(args, inValues...)
stmt, err := s.Runtime.Db.Preparex(sql)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, err.Error())
return
}
rows, err := stmt.Queryx(args...)
defer streamutil.Close(rows)
if err != nil {
err = errors.Wrap(err, err.Error())
return
}
for rows.Next() {
page := page.Page{}
err = rows.StructScan(&page)
if err != nil {
err = errors.Wrap(err, err.Error())
return
}
p = append(p, page)
}
if err != nil {
err = errors.Wrap(err, err.Error())
return
} }
return return
@ -178,7 +97,6 @@ func (s Scope) GetPagesWithoutContent(ctx domain.RequestContext, documentID stri
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("Unable to execute select pages for org %s and document %s", ctx.OrgID, documentID)) err = errors.Wrap(err, fmt.Sprintf("Unable to execute select pages for org %s and document %s", ctx.OrgID, documentID))
return
} }
return return
@ -191,17 +109,9 @@ func (s Scope) Update(ctx domain.RequestContext, page page.Page, refID, userID s
// Store revision history // Store revision history
if !skipRevision { if !skipRevision {
var stmt *sqlx.Stmt _, err = ctx.Transaction.Exec("INSERT INTO revision (refid, orgid, documentid, ownerid, pageid, userid, contenttype, pagetype, title, body, rawbody, config, created, revised) SELECT ? as refid, a.orgid, a.documentid, a.userid as ownerid, a.refid as pageid, ? as userid, a.contenttype, a.pagetype, a.title, a.body, b.rawbody, b.config, ? as created, ? as revised FROM page a, pagemeta b WHERE a.refid=? AND a.refid=b.pageid",
stmt, err = ctx.Transaction.Preparex("INSERT INTO revision (refid, orgid, documentid, ownerid, pageid, userid, contenttype, pagetype, title, body, rawbody, config, created, revised) SELECT ? as refid, a.orgid, a.documentid, a.userid as ownerid, a.refid as pageid, ? as userid, a.contenttype, a.pagetype, a.title, a.body, b.rawbody, b.config, ? as created, ? as revised FROM page a, pagemeta b WHERE a.refid=? AND a.refid=b.pageid") refID, userID, time.Now().UTC(), time.Now().UTC(), page.RefID)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "prepare page revision insert")
return err
}
_, err = stmt.Exec(refID, userID, time.Now().UTC(), time.Now().UTC(), page.RefID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute page revision insert") err = errors.Wrap(err, "execute page revision insert")
return err return err
@ -209,16 +119,9 @@ func (s Scope) Update(ctx domain.RequestContext, page page.Page, refID, userID s
} }
// Update page // Update page
var stmt2 *sqlx.NamedStmt _, err = ctx.Transaction.NamedExec("UPDATE page SET documentid=:documentid, level=:level, title=:title, body=:body, revisions=:revisions, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid",
stmt2, err = ctx.Transaction.PrepareNamed("UPDATE page SET documentid=:documentid, level=:level, title=:title, body=:body, revisions=:revisions, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid") &page)
defer streamutil.Close(stmt2)
if err != nil {
err = errors.Wrap(err, "prepare page insert")
return
}
_, err = stmt2.Exec(&page)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute page insert") err = errors.Wrap(err, "execute page insert")
return return
@ -226,18 +129,10 @@ func (s Scope) Update(ctx domain.RequestContext, page page.Page, refID, userID s
// Update revisions counter // Update revisions counter
if !skipRevision { if !skipRevision {
stmt3, err := ctx.Transaction.Preparex("UPDATE page SET revisions=revisions+1 WHERE orgid=? AND refid=?") _, err = ctx.Transaction.Exec("UPDATE page SET revisions=revisions+1 WHERE orgid=? AND refid=?", ctx.OrgID, page.RefID)
defer streamutil.Close(stmt3)
if err != nil {
err = errors.Wrap(err, "prepare page revision counter")
return err
}
_, err = stmt3.Exec(ctx.OrgID, page.RefID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute page revision counter") err = errors.Wrap(err, "execute page revision counter")
return err
} }
} }
@ -252,19 +147,11 @@ func (s Scope) UpdateMeta(ctx domain.RequestContext, meta page.Meta, updateUserI
meta.UserID = ctx.UserID meta.UserID = ctx.UserID
} }
var stmt *sqlx.NamedStmt _, err = ctx.Transaction.NamedExec("UPDATE pagemeta SET userid=:userid, documentid=:documentid, rawbody=:rawbody, config=:config, externalsource=:externalsource, revised=:revised WHERE orgid=:orgid AND pageid=:pageid",
stmt, err = ctx.Transaction.PrepareNamed("UPDATE pagemeta SET userid=:userid, documentid=:documentid, rawbody=:rawbody, config=:config, externalsource=:externalsource, revised=:revised WHERE orgid=:orgid AND pageid=:pageid") &meta)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "prepare page meta update")
return
}
_, err = stmt.Exec(&meta)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute page meta update") err = errors.Wrap(err, "execute page meta update")
return
} }
return return
@ -273,18 +160,10 @@ func (s Scope) UpdateMeta(ctx domain.RequestContext, meta page.Meta, updateUserI
// UpdateSequence changes the presentation sequence of the pageID page in the document. // UpdateSequence changes the presentation sequence of the pageID page in the document.
// It then propagates that change into the search table and audits that it has occurred. // It then propagates that change into the search table and audits that it has occurred.
func (s Scope) UpdateSequence(ctx domain.RequestContext, documentID, pageID string, sequence float64) (err error) { func (s Scope) UpdateSequence(ctx domain.RequestContext, documentID, pageID string, sequence float64) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE page SET sequence=? WHERE orgid=? AND refid=?") _, err = ctx.Transaction.Exec("UPDATE page SET sequence=? WHERE orgid=? AND refid=?", sequence, ctx.OrgID, pageID)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "prepare page sequence update")
return
}
_, err = stmt.Exec(sequence, ctx.OrgID, pageID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute page sequence update") err = errors.Wrap(err, "execute page sequence update")
return
} }
return return
@ -293,18 +172,10 @@ func (s Scope) UpdateSequence(ctx domain.RequestContext, documentID, pageID stri
// UpdateLevel changes the heading level of the pageID page in the document. // UpdateLevel changes the heading level of the pageID page in the document.
// It then propagates that change into the search table and audits that it has occurred. // It then propagates that change into the search table and audits that it has occurred.
func (s Scope) UpdateLevel(ctx domain.RequestContext, documentID, pageID string, level int) (err error) { func (s Scope) UpdateLevel(ctx domain.RequestContext, documentID, pageID string, level int) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE page SET level=? WHERE orgid=? AND refid=?") _, err = ctx.Transaction.Exec("UPDATE page SET level=? WHERE orgid=? AND refid=?", level, ctx.OrgID, pageID)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "prepare page level update")
return
}
_, err = stmt.Exec(level, ctx.OrgID, pageID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute page level update") err = errors.Wrap(err, "execute page level update")
return
} }
return return
@ -325,18 +196,11 @@ func (s Scope) Delete(ctx domain.RequestContext, documentID, pageID string) (row
// GetPageMeta returns the meta information associated with the page. // GetPageMeta returns the meta information associated with the page.
func (s Scope) GetPageMeta(ctx domain.RequestContext, pageID string) (meta page.Meta, err error) { func (s Scope) GetPageMeta(ctx domain.RequestContext, pageID string) (meta page.Meta, err error) {
stmt, err := s.Runtime.Db.Preparex("SELECT id, pageid, orgid, userid, documentid, rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, externalsource, created, revised FROM pagemeta WHERE orgid=? AND pageid=?") err = s.Runtime.Db.Get(&meta, "SELECT id, pageid, orgid, userid, documentid, rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, externalsource, created, revised FROM pagemeta WHERE orgid=? AND pageid=?",
defer streamutil.Close(stmt) ctx.OrgID, pageID)
if err != nil { if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "prepare get page meta")
return
}
err = stmt.Get(&meta, ctx.OrgID, pageID)
if err != nil {
err = errors.Wrap(err, "execute get page meta") err = errors.Wrap(err, "execute get page meta")
return
} }
return return
@ -353,7 +217,6 @@ func (s Scope) GetDocumentPageMeta(ctx domain.RequestContext, documentID string,
if err != nil { if err != nil {
err = errors.Wrap(err, "get document page meta") err = errors.Wrap(err, "get document page meta")
return
} }
return return
@ -365,18 +228,11 @@ func (s Scope) GetDocumentPageMeta(ctx domain.RequestContext, documentID string,
// GetPageRevision returns the revisionID page revision record. // GetPageRevision returns the revisionID page revision record.
func (s Scope) GetPageRevision(ctx domain.RequestContext, revisionID string) (revision page.Revision, err error) { func (s Scope) GetPageRevision(ctx domain.RequestContext, revisionID string) (revision page.Revision, err error) {
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, orgid, documentid, ownerid, pageid, userid, contenttype, pagetype, title, body, coalesce(rawbody, '') as rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, created, revised FROM revision WHERE orgid=? and refid=?") err = s.Runtime.Db.Get(&revision, "SELECT id, refid, orgid, documentid, ownerid, pageid, userid, contenttype, pagetype, title, body, coalesce(rawbody, '') as rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, created, revised FROM revision WHERE orgid=? and refid=?",
defer streamutil.Close(stmt) ctx.OrgID, revisionID)
if err != nil {
err = errors.Wrap(err, "prepare get page revisions")
return
}
err = stmt.Get(&revision, ctx.OrgID, revisionID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute get page revisions") err = errors.Wrap(err, "execute get page revisions")
return
} }
return return
@ -389,7 +245,6 @@ func (s Scope) GetPageRevisions(ctx domain.RequestContext, pageID string) (revis
if err != nil { if err != nil {
err = errors.Wrap(err, "get page revisions") err = errors.Wrap(err, "get page revisions")
return
} }
return return
@ -400,15 +255,14 @@ func (s Scope) GetPageRevisions(ctx domain.RequestContext, pageID string) (revis
func (s Scope) GetDocumentRevisions(ctx domain.RequestContext, documentID string) (revisions []page.Revision, err error) { func (s Scope) GetDocumentRevisions(ctx domain.RequestContext, documentID string) (revisions []page.Revision, err error) {
err = s.Runtime.Db.Select(&revisions, "SELECT a.id, a.refid, a.orgid, a.documentid, a.ownerid, a.pageid, a.userid, a.contenttype, a.pagetype, a.title, /*a.body, a.rawbody, a.config,*/ a.created, a.revised, coalesce(b.email,'') as email, coalesce(b.firstname,'') as firstname, coalesce(b.lastname,'') as lastname, coalesce(b.initials,'') as initials, coalesce(p.revisions, 0) as revisions FROM revision a LEFT JOIN user b ON a.userid=b.refid LEFT JOIN page p ON a.pageid=p.refid WHERE a.orgid=? AND a.documentid=? AND a.pagetype='section' ORDER BY a.id DESC", ctx.OrgID, documentID) err = s.Runtime.Db.Select(&revisions, "SELECT a.id, a.refid, a.orgid, a.documentid, a.ownerid, a.pageid, a.userid, a.contenttype, a.pagetype, a.title, /*a.body, a.rawbody, a.config,*/ a.created, a.revised, coalesce(b.email,'') as email, coalesce(b.firstname,'') as firstname, coalesce(b.lastname,'') as lastname, coalesce(b.initials,'') as initials, coalesce(p.revisions, 0) as revisions FROM revision a LEFT JOIN user b ON a.userid=b.refid LEFT JOIN page p ON a.pageid=p.refid WHERE a.orgid=? AND a.documentid=? AND a.pagetype='section' ORDER BY a.id DESC", ctx.OrgID, documentID)
if err != nil {
err = errors.Wrap(err, "get document revisions")
return
}
if len(revisions) == 0 { if len(revisions) == 0 {
revisions = []page.Revision{} revisions = []page.Revision{}
} }
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "get document revisions")
}
return return
} }

View file

@ -0,0 +1,400 @@
// 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 permission handles API calls and persistence for spaces.
// Spaces in Documize contain documents.
package permission
import (
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/documize/community/core/env"
"github.com/documize/community/core/request"
"github.com/documize/community/core/response"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/mail"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/space"
"github.com/documize/community/model/user"
)
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *domain.Store
}
// SetSpacePermissions persists specified space permissions
func (h *Handler) SetSpacePermissions(w http.ResponseWriter, r *http.Request) {
method := "space.SetPermissions"
ctx := domain.GetRequestContext(r)
id := request.Param(r, "spaceID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
if !HasPermission(ctx, *h.Store, id, permission.SpaceManage, permission.SpaceOwner) {
response.WriteForbiddenError(w)
return
}
sp, err := h.Store.Space.Get(ctx, id)
if err != nil {
response.WriteNotFoundError(w, method, "space not found")
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
}
var model = permission.PermissionsModel{}
err = json.Unmarshal(body, &model)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// We compare new permisions to what we had before.
// Why? So we can send out space invitation emails.
previousRoles, err := h.Store.Permission.GetSpacePermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Store all previous roles as map for easy querying
previousRoleUsers := make(map[string]bool)
for _, v := range previousRoles {
previousRoleUsers[v.WhoID] = true
}
// Who is sharing this space?
inviter, err := h.Store.User.Get(ctx, ctx.UserID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Nuke all previous permissions for this space
_, err = h.Store.Permission.DeleteSpacePermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
me := false
hasEveryoneRole := false
roleCount := 0
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
for _, perm := range model.Permissions {
perm.OrgID = ctx.OrgID
perm.SpaceID = id
// Ensure the space owner always has access!
if perm.UserID == ctx.UserID {
me = true
}
// Only persist if there is a role!
if permission.HasAnyPermission(perm) {
// identify publically shared spaces
if perm.UserID == "0" || perm.UserID == "" {
perm.UserID = "0"
hasEveryoneRole = true
}
r := permission.EncodeUserPermissions(perm)
for _, p := range r {
err = h.Store.Permission.AddPermission(ctx, p)
if err != nil {
h.Runtime.Log.Error("set permission", err)
}
roleCount++
}
// We send out space invitation emails to those users
// that have *just* been given permissions.
if _, isExisting := previousRoleUsers[perm.UserID]; !isExisting {
// we skip 'everyone' (user id != empty string)
if perm.UserID != "0" && perm.UserID != "" {
existingUser, err := h.Store.User.Get(ctx, perm.UserID)
if err != nil {
response.WriteServerError(w, method, err)
break
}
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.ShareSpaceExistingUser(existingUser.Email, inviter.Fullname(), url, sp.Name, model.Message)
h.Runtime.Log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, sp.Name, existingUser.Email))
}
}
}
}
// Do we need to ensure permissions for space owner when shared?
if !me {
perm := permission.Permission{}
perm.OrgID = ctx.OrgID
perm.Who = "user"
perm.WhoID = ctx.UserID
perm.Scope = "object"
perm.Location = "space"
perm.RefID = id
perm.Action = "" // we send array for actions below
err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceView, permission.SpaceManage)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
return
}
}
// Mark up space type as either public, private or restricted access.
if hasEveryoneRole {
sp.Type = space.ScopePublic
} else {
if roleCount > 1 {
sp.Type = space.ScopeRestricted
} else {
sp.Type = space.ScopePrivate
}
}
err = h.Store.Space.Update(ctx, sp)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Store.Audit.Record(ctx, audit.EventTypeSpacePermission)
ctx.Transaction.Commit()
response.WriteEmpty(w)
}
// GetSpacePermissions returns permissions for all users for given space.
func (h *Handler) GetSpacePermissions(w http.ResponseWriter, r *http.Request) {
method := "space.GetPermissions"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
perms, err := h.Store.Permission.GetSpacePermissions(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(perms) == 0 {
perms = []permission.Permission{}
}
userPerms := make(map[string][]permission.Permission)
for _, p := range perms {
userPerms[p.WhoID] = append(userPerms[p.WhoID], p)
}
records := []permission.Record{}
for _, up := range userPerms {
records = append(records, permission.DecodeUserPermissions(up))
}
response.WriteJSON(w, records)
}
// GetUserSpacePermissions returns permissions for the requested space, for current user.
func (h *Handler) GetUserSpacePermissions(w http.ResponseWriter, r *http.Request) {
method := "space.GetUserSpacePermissions"
ctx := domain.GetRequestContext(r)
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(perms) == 0 {
perms = []permission.Permission{}
}
record := permission.DecodeUserPermissions(perms)
response.WriteJSON(w, record)
}
// GetCategoryViewers returns user permissions for given category.
func (h *Handler) GetCategoryViewers(w http.ResponseWriter, r *http.Request) {
method := "space.GetCategoryViewers"
ctx := domain.GetRequestContext(r)
categoryID := request.Param(r, "categoryID")
if len(categoryID) == 0 {
response.WriteMissingDataError(w, method, "categoryID")
return
}
u, err := h.Store.Permission.GetCategoryUsers(ctx, categoryID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(u) == 0 {
u = []user.User{}
}
response.WriteJSON(w, u)
}
// GetCategoryPermissions returns user permissions for given category.
func (h *Handler) GetCategoryPermissions(w http.ResponseWriter, r *http.Request) {
method := "space.GetCategoryPermissions"
ctx := domain.GetRequestContext(r)
categoryID := request.Param(r, "categoryID")
if len(categoryID) == 0 {
response.WriteMissingDataError(w, method, "categoryID")
return
}
u, err := h.Store.Permission.GetCategoryPermissions(ctx, categoryID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(u) == 0 {
u = []permission.Permission{}
}
response.WriteJSON(w, u)
}
// SetCategoryPermissions persists specified category permissions
func (h *Handler) SetCategoryPermissions(w http.ResponseWriter, r *http.Request) {
method := "permission.SetCategoryPermissions"
ctx := domain.GetRequestContext(r)
id := request.Param(r, "categoryID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "categoryID")
return
}
spaceID := request.Query(r, "space")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "space")
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
}
var model = []permission.CategoryViewRequestModel{}
err = json.Unmarshal(body, &model)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if !HasPermission(ctx, *h.Store, spaceID, permission.SpaceManage, permission.SpaceOwner) {
response.WriteForbiddenError(w)
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Nuke all previous permissions for this category
_, err = h.Store.Permission.DeleteCategoryPermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
for _, m := range model {
perm := permission.Permission{}
perm.OrgID = ctx.OrgID
perm.Who = "user"
perm.WhoID = m.UserID
perm.Scope = "object"
perm.Location = "category"
perm.RefID = m.CategoryID
perm.Action = permission.CategoryView
err = h.Store.Permission.AddPermission(ctx, perm)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
return
}
}
h.Store.Audit.Record(ctx, audit.EventTypeCategoryPermission)
ctx.Transaction.Commit()
response.WriteEmpty(w)
}

View file

@ -0,0 +1,214 @@
// 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 space and document permissions.
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/permission"
"github.com/documize/community/model/user"
"github.com/pkg/errors"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
}
// AddPermission inserts the given record into the permisssion table.
func (s Scope) AddPermission(ctx domain.RequestContext, r permission.Permission) (err error) {
r.Created = time.Now().UTC()
_, err = ctx.Transaction.Exec("INSERT INTO permission (orgid, who, whoid, action, scope, location, refid, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
r.OrgID, r.Who, r.WhoID, string(r.Action), r.Scope, r.Location, r.RefID, r.Created)
if err != nil {
err = errors.Wrap(err, "unable to execute insert permission")
}
return
}
// AddPermissions inserts records into permission database table, one per action.
func (s Scope) AddPermissions(ctx domain.RequestContext, r permission.Permission, actions ...permission.Action) (err error) {
for _, a := range actions {
r.Action = a
s.AddPermission(ctx, r)
}
return
}
// GetUserSpacePermissions returns space permissions for user.
// Context is used to for user ID.
func (s Scope) GetUserSpacePermissions(ctx domain.RequestContext, spaceID string) (r []permission.Permission, err error) {
err = s.Runtime.Db.Select(&r, `
SELECT id, orgid, who, whoid, action, scope, location, refid
FROM permission WHERE orgid=? AND location='space' AND refid=? AND who='user' AND (whoid=? OR whoid='0')
UNION ALL
SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid
FROM permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.location='space' AND refid=?
AND p.who='role' AND (r.userid=? OR r.userid='0')`,
ctx.OrgID, spaceID, ctx.UserID, ctx.OrgID, spaceID, ctx.OrgID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select user permissions %s", ctx.UserID))
}
return
}
// GetSpacePermissions returns space permissions for all users.
func (s Scope) GetSpacePermissions(ctx domain.RequestContext, spaceID string) (r []permission.Permission, err error) {
err = s.Runtime.Db.Select(&r, `
SELECT id, orgid, who, whoid, action, scope, location, refid
FROM permission WHERE orgid=? AND location='space' AND refid=? AND who='user'
UNION ALL
SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid
FROM permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.location='space' AND p.refid=?
AND p.who='role'`,
ctx.OrgID, spaceID, ctx.OrgID, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select space permissions %s", ctx.UserID))
}
return
}
// DeleteSpacePermissions removes records from permissions table for given space ID.
func (s Scope) DeleteSpacePermissions(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND location='space' AND refid='%s'", ctx.OrgID, spaceID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteUserSpacePermissions removes all roles for the specified user, for the specified space.
func (s Scope) DeleteUserSpacePermissions(ctx domain.RequestContext, spaceID, userID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND location='space' AND refid='%s' who='user' AND whoid='%s'",
ctx.OrgID, spaceID, userID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteUserPermissions removes all roles for the specified user, for the specified space.
func (s Scope) DeleteUserPermissions(ctx domain.RequestContext, userID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND who='user' AND whoid='%s'",
ctx.OrgID, userID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteCategoryPermissions removes records from permissions table for given category ID.
func (s Scope) DeleteCategoryPermissions(ctx domain.RequestContext, categoryID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM permission WHERE orgid='%s' AND location='category' AND refid='%s'", ctx.OrgID, categoryID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteSpaceCategoryPermissions removes all category permission for for given space.
func (s Scope) DeleteSpaceCategoryPermissions(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf(`
DELETE FROM permission WHERE orgid='%s' AND location='category'
AND refid IN (SELECT refid FROM category WHERE orgid='%s' AND labelid='%s')`,
ctx.OrgID, ctx.OrgID, spaceID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// GetCategoryPermissions returns category permissions for all users.
func (s Scope) GetCategoryPermissions(ctx domain.RequestContext, catID string) (r []permission.Permission, err error) {
err = s.Runtime.Db.Select(&r, `
SELECT id, orgid, who, whoid, action, scope, location, refid
FROM permission WHERE orgid=? AND location='category' AND refid=? AND who='user'
UNION ALL
SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid
FROM permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.location='space' AND p.refid=?
AND p.who='role'`,
ctx.OrgID, catID, ctx.OrgID, catID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select category permissions %s", catID))
}
return
}
// GetCategoryUsers returns space permissions for all users.
func (s Scope) GetCategoryUsers(ctx domain.RequestContext, catID string) (u []user.User, err error) {
err = s.Runtime.Db.Select(&u, `
SELECT u.id, IFNULL(u.refid, '') AS refid, IFNULL(u.firstname, '') AS firstname, IFNULL(u.lastname, '') as lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised
FROM user u LEFT JOIN account a ON u.refid = a.userid
WHERE a.orgid=? AND a.active=1 AND u.refid IN (
SELECT whoid from permission WHERE orgid=? AND who='user' AND location='category' AND refid=? UNION ALL
SELECT r.userid from rolemember r LEFT JOIN permission p ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role'
AND p.location='category' AND p.refid=?
)
GROUP by u.id
ORDER BY firstname, lastname`,
ctx.OrgID, ctx.OrgID, catID, ctx.OrgID, catID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select users for category %s", catID))
}
return
}
// GetUserCategoryPermissions returns category permissions for given user.
func (s Scope) GetUserCategoryPermissions(ctx domain.RequestContext, userID string) (r []permission.Permission, err error) {
err = s.Runtime.Db.Select(&r, `
SELECT id, orgid, who, whoid, action, scope, location, refid
FROM permission WHERE orgid=? AND location='category' AND who='user' AND (whoid=? OR whoid='0')
UNION ALL
SELECT p.id, p.orgid, p.who, p.whoid, p.action, p.scope, p.location, p.refid
FROM permission p LEFT JOIN rolemember r ON p.whoid=r.roleid
WHERE p.orgid=? AND p.location='category' AND p.who='role'`,
ctx.OrgID, userID, ctx.OrgID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select category permissions for user %s", userID))
}
return
}

View file

@ -0,0 +1,189 @@
// 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 permission
import (
"database/sql"
"github.com/documize/community/domain"
pm "github.com/documize/community/model/permission"
)
// CanViewSpaceDocument returns if the user has permission to view a document within the specified folder.
func CanViewSpaceDocument(ctx domain.RequestContext, s domain.Store, labelID string) bool {
roles, err := s.Permission.GetUserSpacePermissions(ctx, labelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == labelID && role.Location == "space" && role.Scope == "object" &&
pm.ContainsPermission(role.Action, pm.SpaceView, pm.SpaceManage, pm.SpaceOwner) {
return true
}
}
return false
}
// CanViewDocument returns if the client has permission to view a given document.
func CanViewDocument(ctx domain.RequestContext, s domain.Store, documentID string) bool {
document, err := s.Document.Get(ctx, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
roles, err := s.Permission.GetUserSpacePermissions(ctx, document.LabelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == document.LabelID && role.Location == "space" && role.Scope == "object" &&
pm.ContainsPermission(role.Action, pm.SpaceView, pm.SpaceManage, pm.SpaceOwner) {
return true
}
}
return false
}
// CanChangeDocument returns if the clinet has permission to change a given document.
func CanChangeDocument(ctx domain.RequestContext, s domain.Store, documentID string) bool {
document, err := s.Document.Get(ctx, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
roles, err := s.Permission.GetUserSpacePermissions(ctx, document.LabelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == document.LabelID && role.Location == "space" && role.Scope == "object" && role.Action == pm.DocumentEdit {
return true
}
}
return false
}
// CanDeleteDocument returns if the clinet has permission to change a given document.
func CanDeleteDocument(ctx domain.RequestContext, s domain.Store, documentID string) bool {
document, err := s.Document.Get(ctx, documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
roles, err := s.Permission.GetUserSpacePermissions(ctx, document.LabelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == document.LabelID && role.Location == "space" && role.Scope == "object" && role.Action == pm.DocumentDelete {
return true
}
}
return false
}
// CanUploadDocument returns if the client has permission to upload documents to the given space.
func CanUploadDocument(ctx domain.RequestContext, s domain.Store, spaceID string) bool {
roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == spaceID && role.Location == "space" && role.Scope == "object" &&
pm.ContainsPermission(role.Action, pm.DocumentAdd) {
return true
}
}
return false
}
// CanViewSpace returns if the user has permission to view the given spaceID.
func CanViewSpace(ctx domain.RequestContext, s domain.Store, spaceID string) bool {
roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == spaceID && role.Location == "space" && role.Scope == "object" &&
pm.ContainsPermission(role.Action, pm.SpaceView, pm.SpaceManage, pm.SpaceOwner) {
return true
}
}
return false
}
// HasPermission returns if user can perform specified actions.
func HasPermission(ctx domain.RequestContext, s domain.Store, spaceID string, actions ...pm.Action) bool {
roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.RefID == spaceID && role.Location == "space" && role.Scope == "object" {
for _, a := range actions {
if role.Action == a {
return true
}
}
}
}
return false
}

View file

@ -16,11 +16,9 @@ import (
"time" "time"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql" "github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/pin" "github.com/documize/community/model/pin"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -43,18 +41,11 @@ func (s Scope) Add(ctx domain.RequestContext, pin pin.Pin) (err error) {
pin.Revised = time.Now().UTC() pin.Revised = time.Now().UTC()
pin.Sequence = maxSeq + 1 pin.Sequence = maxSeq + 1
stmt, err := ctx.Transaction.Preparex("INSERT INTO pin (refid, orgid, userid, labelid, documentid, pin, sequence, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") _, err = ctx.Transaction.Exec("INSERT INTO pin (refid, orgid, userid, labelid, documentid, pin, sequence, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
defer streamutil.Close(stmt) pin.RefID, pin.OrgID, pin.UserID, pin.FolderID, pin.DocumentID, pin.Pin, pin.Sequence, pin.Created, pin.Revised)
if err != nil {
err = errors.Wrap(err, "prepare pin insert")
return
}
_, err = stmt.Exec(pin.RefID, pin.OrgID, pin.UserID, pin.FolderID, pin.DocumentID, pin.Pin, pin.Sequence, pin.Created, pin.Revised)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute pin insert") err = errors.Wrap(err, "execute pin insert")
return
} }
return return
@ -62,18 +53,11 @@ func (s Scope) Add(ctx domain.RequestContext, pin pin.Pin) (err error) {
// GetPin returns requested pinned item. // GetPin returns requested pinned item.
func (s Scope) GetPin(ctx domain.RequestContext, id string) (pin pin.Pin, err error) { func (s Scope) GetPin(ctx domain.RequestContext, id string) (pin pin.Pin, err error) {
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, orgid, userid, labelid as folderid, documentid, pin, sequence, created, revised FROM pin WHERE orgid=? AND refid=?") err = s.Runtime.Db.Get(&pin, "SELECT id, refid, orgid, userid, labelid as folderid, documentid, pin, sequence, created, revised FROM pin WHERE orgid=? AND refid=?",
defer streamutil.Close(stmt) ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare select for pin %s", id))
return
}
err = stmt.Get(&pin, ctx.OrgID, id)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute select for pin %s", id)) err = errors.Wrap(err, fmt.Sprintf("execute select for pin %s", id))
return
} }
return return
@ -85,7 +69,6 @@ func (s Scope) GetUserPins(ctx domain.RequestContext, userID string) (pins []pin
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute select pins for org %s and user %s", ctx.OrgID, userID)) err = errors.Wrap(err, fmt.Sprintf("execute select pins for org %s and user %s", ctx.OrgID, userID))
return
} }
return return
@ -95,19 +78,11 @@ func (s Scope) GetUserPins(ctx domain.RequestContext, userID string) (pins []pin
func (s Scope) UpdatePin(ctx domain.RequestContext, pin pin.Pin) (err error) { func (s Scope) UpdatePin(ctx domain.RequestContext, pin pin.Pin) (err error) {
pin.Revised = time.Now().UTC() pin.Revised = time.Now().UTC()
var stmt *sqlx.NamedStmt _, err = ctx.Transaction.NamedExec("UPDATE pin SET labelid=:folderid, documentid=:documentid, pin=:pin, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid",
stmt, err = ctx.Transaction.PrepareNamed("UPDATE pin SET labelid=:folderid, documentid=:documentid, pin=:pin, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid") &pin)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare pin update %s", pin.RefID))
return
}
_, err = stmt.Exec(&pin)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute pin update %s", pin.RefID)) err = errors.Wrap(err, fmt.Sprintf("execute pin update %s", pin.RefID))
return
} }
return return
@ -115,18 +90,11 @@ func (s Scope) UpdatePin(ctx domain.RequestContext, pin pin.Pin) (err error) {
// UpdatePinSequence updates existing pinned item sequence number // UpdatePinSequence updates existing pinned item sequence number
func (s Scope) UpdatePinSequence(ctx domain.RequestContext, pinID string, sequence int) (err error) { func (s Scope) UpdatePinSequence(ctx domain.RequestContext, pinID string, sequence int) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE pin SET sequence=?, revised=? WHERE orgid=? AND userid=? AND refid=?") _, err = ctx.Transaction.Exec("UPDATE pin SET sequence=?, revised=? WHERE orgid=? AND userid=? AND refid=?",
defer streamutil.Close(stmt) sequence, time.Now().UTC(), ctx.OrgID, ctx.UserID, pinID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare pin sequence update %s", pinID))
return
}
_, err = stmt.Exec(sequence, time.Now().UTC(), ctx.OrgID, ctx.UserID, pinID)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute pin sequence update %s", pinID)) err = errors.Wrap(err, fmt.Sprintf("execute pin sequence update %s", pinID))
return
} }
return return

View file

@ -37,33 +37,18 @@ type Scope struct {
// searchable items. Any existing document entries are removed. // searchable items. Any existing document entries are removed.
func (s Scope) IndexDocument(ctx domain.RequestContext, doc doc.Document, a []attachment.Attachment) (err error) { func (s Scope) IndexDocument(ctx domain.RequestContext, doc doc.Document, a []attachment.Attachment) (err error) {
// remove previous search entries // remove previous search entries
var stmt1 *sqlx.Stmt _, err = ctx.Transaction.Exec("DELETE FROM search WHERE orgid=? AND documentid=? AND (itemtype='doc' OR itemtype='file' OR itemtype='tag')",
stmt1, err = ctx.Transaction.Preparex("DELETE FROM search WHERE orgid=? AND documentid=? AND (itemtype='doc' OR itemtype='file' OR itemtype='tag')") ctx.OrgID, doc.RefID)
defer streamutil.Close(stmt1)
if err != nil {
err = errors.Wrap(err, "prepare delete document index entries")
return
}
_, err = stmt1.Exec(ctx.OrgID, doc.RefID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute delete document index entries") err = errors.Wrap(err, "execute delete document index entries")
return
} }
// insert doc title // insert doc title
var stmt2 *sqlx.Stmt _, err = ctx.Transaction.Exec("INSERT INTO search (orgid, documentid, itemid, itemtype, content) VALUES (?, ?, ?, ?, ?)",
stmt2, err = ctx.Transaction.Preparex("INSERT INTO search (orgid, documentid, itemid, itemtype, content) VALUES (?, ?, ?, ?, ?)") ctx.OrgID, doc.RefID, "", "doc", doc.Title)
defer streamutil.Close(stmt2)
if err != nil {
err = errors.Wrap(err, "prepare insert document title entry")
return
}
_, err = stmt2.Exec(ctx.OrgID, doc.RefID, "", "doc", doc.Title)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute insert document title entry") err = errors.Wrap(err, "execute insert document title entry")
return
} }
// insert doc tags // insert doc tags
@ -73,15 +58,9 @@ func (s Scope) IndexDocument(ctx domain.RequestContext, doc doc.Document, a []at
continue continue
} }
var stmt3 *sqlx.Stmt _, err = ctx.Transaction.Exec("INSERT INTO search (orgid, documentid, itemid, itemtype, content) VALUES (?, ?, ?, ?, ?)",
stmt3, err = ctx.Transaction.Preparex("INSERT INTO search (orgid, documentid, itemid, itemtype, content) VALUES (?, ?, ?, ?, ?)") ctx.OrgID, doc.RefID, "", "tag", t)
defer streamutil.Close(stmt3)
if err != nil {
err = errors.Wrap(err, "prepare insert document tag entry")
return
}
_, err = stmt3.Exec(ctx.OrgID, doc.RefID, "", "tag", t)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute insert document tag entry") err = errors.Wrap(err, "execute insert document tag entry")
return return
@ -89,18 +68,11 @@ func (s Scope) IndexDocument(ctx domain.RequestContext, doc doc.Document, a []at
} }
for _, file := range a { for _, file := range a {
var stmt4 *sqlx.Stmt _, err = ctx.Transaction.Exec("INSERT INTO search (orgid, documentid, itemid, itemtype, content) VALUES (?, ?, ?, ?, ?)",
stmt4, err = ctx.Transaction.Preparex("INSERT INTO search (orgid, documentid, itemid, itemtype, content) VALUES (?, ?, ?, ?, ?)") ctx.OrgID, doc.RefID, file.RefID, "file", file.Filename)
defer streamutil.Close(stmt4)
if err != nil {
err = errors.Wrap(err, "prepare insert document file entry")
return
}
_, err = stmt4.Exec(ctx.OrgID, doc.RefID, file.RefID, "file", file.Filename)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute insert document file entry") err = errors.Wrap(err, "execute insert document file entry")
return
} }
} }
@ -109,19 +81,10 @@ func (s Scope) IndexDocument(ctx domain.RequestContext, doc doc.Document, a []at
// DeleteDocument removes all search entries for document. // DeleteDocument removes all search entries for document.
func (s Scope) DeleteDocument(ctx domain.RequestContext, ID string) (err error) { func (s Scope) DeleteDocument(ctx domain.RequestContext, ID string) (err error) {
// remove all search entries _, err = ctx.Transaction.Exec("DELETE FROM search WHERE orgid=? AND documentid=?", ctx.OrgID, ID)
var stmt1 *sqlx.Stmt
stmt1, err = ctx.Transaction.Preparex("DELETE FROM search WHERE orgid=? AND documentid=?")
defer streamutil.Close(stmt1)
if err != nil {
err = errors.Wrap(err, "prepare delete document entries")
return
}
_, err = stmt1.Exec(ctx.OrgID, ID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute delete document entries") err = errors.Wrap(err, "execute delete document entries")
return
} }
return return
@ -131,27 +94,11 @@ func (s Scope) DeleteDocument(ctx domain.RequestContext, ID string) (err error)
// Any existing document entries are removed. // Any existing document entries are removed.
func (s Scope) IndexContent(ctx domain.RequestContext, p page.Page) (err error) { func (s Scope) IndexContent(ctx domain.RequestContext, p page.Page) (err error) {
// remove previous search entries // remove previous search entries
var stmt1 *sqlx.Stmt _, err = ctx.Transaction.Exec("DELETE FROM search WHERE orgid=? AND documentid=? AND itemid=? AND itemtype='page'",
stmt1, err = ctx.Transaction.Preparex("DELETE FROM search WHERE orgid=? AND documentid=? AND itemid=? AND itemtype='page'") ctx.OrgID, p.DocumentID, p.RefID)
defer streamutil.Close(stmt1)
if err != nil {
err = errors.Wrap(err, "prepare delete document content entry")
return
}
_, err = stmt1.Exec(ctx.OrgID, p.DocumentID, p.RefID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute delete document content entry") err = errors.Wrap(err, "execute delete document content entry")
return
}
// insert doc title
var stmt2 *sqlx.Stmt
stmt2, err = ctx.Transaction.Preparex("INSERT INTO search (orgid, documentid, itemid, itemtype, content) VALUES (?, ?, ?, ?, ?)")
defer streamutil.Close(stmt2)
if err != nil {
err = errors.Wrap(err, "prepare insert document content entry")
return
} }
// prepare content // prepare content
@ -162,10 +109,10 @@ func (s Scope) IndexContent(ctx domain.RequestContext, p page.Page) (err error)
} }
content = strings.TrimSpace(content) content = strings.TrimSpace(content)
_, err = stmt2.Exec(ctx.OrgID, p.DocumentID, p.RefID, "page", content) _, err = ctx.Transaction.Exec("INSERT INTO search (orgid, documentid, itemid, itemtype, content) VALUES (?, ?, ?, ?, ?)",
ctx.OrgID, p.DocumentID, p.RefID, "page", content)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute insert document content entry") err = errors.Wrap(err, "execute insert document content entry")
return
} }
return nil return nil
@ -265,9 +212,15 @@ func (s Scope) matchFullText(ctx domain.RequestContext, keywords, itemType strin
AND s.itemtype = ? AND s.itemtype = ?
AND s.documentid = d.refid AND s.documentid = d.refid
-- AND d.template = 0 -- AND d.template = 0
AND d.labelid IN (SELECT refid from label WHERE orgid=? AND type=2 AND userid=? AND d.labelid IN
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) (
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) 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=? AND location='space'
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=?
))
)
AND MATCH(s.content) AGAINST(? IN BOOLEAN MODE)` AND MATCH(s.content) AGAINST(? IN BOOLEAN MODE)`
err = s.Runtime.Db.Select(&r, err = s.Runtime.Db.Select(&r,
@ -275,11 +228,10 @@ func (s Scope) matchFullText(ctx domain.RequestContext, keywords, itemType strin
ctx.OrgID, ctx.OrgID,
itemType, itemType,
ctx.OrgID, ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID, ctx.UserID,
ctx.OrgID, ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID, ctx.UserID,
keywords) keywords)
@ -290,7 +242,6 @@ func (s Scope) matchFullText(ctx domain.RequestContext, keywords, itemType strin
if err != nil { if err != nil {
err = errors.Wrap(err, "search document "+itemType) err = errors.Wrap(err, "search document "+itemType)
return
} }
return return
@ -318,9 +269,15 @@ func (s Scope) matchLike(ctx domain.RequestContext, keywords, itemType string) (
AND s.itemtype = ? AND s.itemtype = ?
AND s.documentid = d.refid AND s.documentid = d.refid
-- AND d.template = 0 -- AND d.template = 0
AND d.labelid IN (SELECT refid from label WHERE orgid=? AND type=2 AND userid=? AND d.labelid IN
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)) (
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) 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=? AND location='space'
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=?
))
)
AND s.content LIKE ?` AND s.content LIKE ?`
err = s.Runtime.Db.Select(&r, err = s.Runtime.Db.Select(&r,
@ -328,11 +285,10 @@ func (s Scope) matchLike(ctx domain.RequestContext, keywords, itemType string) (
ctx.OrgID, ctx.OrgID,
itemType, itemType,
ctx.OrgID, ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID, ctx.UserID,
ctx.OrgID, ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID, ctx.UserID,
keywords) keywords)
@ -343,7 +299,6 @@ func (s Scope) matchLike(ctx domain.RequestContext, keywords, itemType string) (
if err != nil { if err != nil {
err = errors.Wrap(err, "search document "+itemType) err = errors.Wrap(err, "search document "+itemType)
return
} }
return return

View file

@ -20,7 +20,7 @@ import (
"github.com/documize/community/core/response" "github.com/documize/community/core/response"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/document" "github.com/documize/community/domain/permission"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
) )
@ -59,7 +59,7 @@ func (h *Handler) RunSectionCommand(w http.ResponseWriter, r *http.Request) {
// it's up to the section handler to parse if required. // it's up to the section handler to parse if required.
// Permission checks // Permission checks
if !document.CanChangeDocument(ctx, *h.Store, documentID) { if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }
@ -86,7 +86,7 @@ func (h *Handler) RefreshSections(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanViewDocument(ctx, *h.Store, documentID) { if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
return return
} }

View file

@ -153,7 +153,6 @@ func (p *Provider) Refresh(ctx *provider.Context, configJSON, data string) strin
err := json.Unmarshal([]byte(configJSON), &c) err := json.Unmarshal([]byte(configJSON), &c)
if err != nil { if err != nil {
p.Runtime.Log.Error("unable to unmarshall github config", err)
return "internal configuration error '" + err.Error() + "'" return "internal configuration error '" + err.Error() + "'"
} }
@ -164,7 +163,6 @@ func (p *Provider) Refresh(ctx *provider.Context, configJSON, data string) strin
byts, err := json.Marshal(refreshReportData(&c, client)) byts, err := json.Marshal(refreshReportData(&c, client))
if err != nil { if err != nil {
p.Runtime.Log.Error("unable to marshall github data", err)
return "internal configuration error '" + err.Error() + "'" return "internal configuration error '" + err.Error() + "'"
} }
@ -188,9 +186,7 @@ func (p *Provider) Render(ctx *provider.Context, config, data string) string {
var c = githubConfig{} var c = githubConfig{}
err = json.Unmarshal([]byte(config), &c) err = json.Unmarshal([]byte(config), &c)
if err != nil { if err != nil {
p.Runtime.Log.Error("unable to unmarshall github config", err)
return "Please delete and recreate this Github section." return "Please delete and recreate this Github section."
} }
@ -206,7 +202,6 @@ func (p *Provider) Render(ctx *provider.Context, config, data string) string {
err = json.Unmarshal([]byte(data), &payload) err = json.Unmarshal([]byte(data), &payload)
if err != nil { if err != nil {
p.Runtime.Log.Error("unable to unmarshall github data", err)
return "Please delete and recreate this Github section." return "Please delete and recreate this Github section."
} }

View file

@ -16,7 +16,6 @@ import (
"database/sql" "database/sql"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -30,18 +29,13 @@ func (s Scope) Get(area, path string) (value string, err error) {
if path != "" { if path != "" {
path = "." + path path = "." + path
} }
sql := "SELECT JSON_EXTRACT(`config`,'$" + path + "') FROM `config` WHERE `key` = '" + area + "';" sql := "SELECT JSON_EXTRACT(`config`,'$" + path + "') FROM `config` WHERE `key` = '" + area + "';"
stmt, err := s.Runtime.Db.Preparex(sql)
defer streamutil.Close(stmt)
if err != nil {
return "", err
}
var item = make([]uint8, 0) var item = make([]uint8, 0)
err = stmt.Get(&item) err = s.Runtime.Db.Get(&item, sql)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -55,7 +49,7 @@ func (s Scope) Get(area, path string) (value string, err error) {
} }
// Set writes a configuration JSON element to the config table. // Set writes a configuration JSON element to the config table.
func (s Scope) Set(area, json string) error { func (s Scope) Set(area, json string) (err error) {
if area == "" { if area == "" {
return errors.New("no area") return errors.New("no area")
} }
@ -64,15 +58,8 @@ func (s Scope) Set(area, json string) error {
"VALUES ('" + area + "','" + json + "VALUES ('" + area + "','" + json +
"') ON DUPLICATE KEY UPDATE `config`='" + json + "';" "') ON DUPLICATE KEY UPDATE `config`='" + json + "';"
stmt, err := s.Runtime.Db.Preparex(sql) _, err = s.Runtime.Db.Exec(sql)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "failed to save global config value")
return err
}
_, err = stmt.Exec()
return err return err
} }
@ -86,16 +73,10 @@ func (s Scope) GetUser(orgID, userID, area, path string) (value string, err erro
qry := "SELECT JSON_EXTRACT(`config`,'$" + path + "') FROM `userconfig` WHERE `key` = '" + area + qry := "SELECT JSON_EXTRACT(`config`,'$" + path + "') FROM `userconfig` WHERE `key` = '" + area +
"' AND `orgid` = '" + orgID + "' AND `userid` = '" + userID + "';" "' AND `orgid` = '" + orgID + "' AND `userid` = '" + userID + "';"
stmt, err := s.Runtime.Db.Preparex(qry)
defer streamutil.Close(stmt)
if err != nil {
return "", err
}
var item = make([]uint8, 0) var item = make([]uint8, 0)
err = stmt.Get(&item) err = s.Runtime.Db.Get(&item, qry)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
return "", err return "", err
} }
@ -109,7 +90,7 @@ func (s Scope) GetUser(orgID, userID, area, path string) (value string, err erro
} }
// SetUser writes a configuration JSON element to the userconfig table for the current user. // SetUser writes a configuration JSON element to the userconfig table for the current user.
func (s Scope) SetUser(orgID, userID, area, json string) error { func (s Scope) SetUser(orgID, userID, area, json string) (err error) {
if area == "" { if area == "" {
return errors.New("no area") return errors.New("no area")
} }
@ -118,14 +99,7 @@ func (s Scope) SetUser(orgID, userID, area, json string) error {
"VALUES ('" + orgID + "','" + userID + "','" + area + "','" + json + "VALUES ('" + orgID + "','" + userID + "','" + area + "','" + json +
"') ON DUPLICATE KEY UPDATE `config`='" + json + "';" "') ON DUPLICATE KEY UPDATE `config`='" + json + "';"
stmt, err := s.Runtime.Db.Preparex(sql) _, err = s.Runtime.Db.Exec(sql)
defer streamutil.Close(stmt)
if err != nil {
return err
}
_, err = stmt.Exec()
return err return err
} }

View file

@ -35,8 +35,8 @@ import (
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/doc" "github.com/documize/community/model/doc"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/space" "github.com/documize/community/model/space"
"github.com/documize/community/model/user"
uuid "github.com/nu7hatch/gouuid" uuid "github.com/nu7hatch/gouuid"
) )
@ -48,7 +48,7 @@ type Handler struct {
// Add creates a new space. // Add creates a new space.
func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
method := "space.Add" method := "space.add"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
if !h.Runtime.Product.License.IsValid() { if !h.Runtime.Product.License.IsValid() {
@ -106,15 +106,16 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return return
} }
role := space.Role{} perm := permission.Permission{}
role.LabelID = sp.RefID perm.OrgID = sp.OrgID
role.OrgID = sp.OrgID perm.Who = "user"
role.UserID = ctx.UserID perm.WhoID = ctx.UserID
role.CanEdit = true perm.Scope = "object"
role.CanView = true perm.Location = "space"
role.RefID = uniqueid.Generate() perm.RefID = sp.RefID
perm.Action = "" // we send array for actions below
err = h.Store.Space.AddRole(ctx, role) err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceOwner, permission.SpaceManage, permission.SpaceView)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
@ -138,7 +139,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return return
} }
spCloneRoles, err := h.Store.Space.GetRoles(ctx, model.CloneID) spCloneRoles, err := h.Store.Permission.GetSpacePermissions(ctx, model.CloneID)
if err != nil { if err != nil {
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)
@ -147,10 +148,9 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
if model.CopyPermission { if model.CopyPermission {
for _, r := range spCloneRoles { for _, r := range spCloneRoles {
r.RefID = uniqueid.Generate() r.RefID = sp.RefID
r.LabelID = sp.RefID
err = h.Store.Space.AddRole(ctx, r) err = h.Store.Permission.AddPermission(ctx, r)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
@ -276,12 +276,12 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
// Get returns the requested space. // Get returns the requested space.
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
method := "Get" method := "space.get"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
id := request.Param(r, "folderID") id := request.Param(r, "spaceID")
if len(id) == 0 { if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID") response.WriteMissingDataError(w, method, "spaceID")
return return
} }
@ -300,11 +300,36 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, sp) response.WriteJSON(w, sp)
} }
// GetAll returns spaces the user can see. // GetAlGetViewablel returns spaces the user can see.
func (h *Handler) GetAll(w http.ResponseWriter, r *http.Request) { func (h *Handler) GetViewable(w http.ResponseWriter, r *http.Request) {
method := "GetAll" method := "space.GetViewable"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
sp, err := h.Store.Space.GetViewable(ctx)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(sp) == 0 {
sp = []space.Space{}
}
response.WriteJSON(w, sp)
}
// GetAll returns every space for documize admin users to manage
func (h *Handler) GetAll(w http.ResponseWriter, r *http.Request) {
method := "space.getAll"
ctx := domain.GetRequestContext(r)
if !ctx.Administrator {
response.WriteForbiddenError(w)
h.Runtime.Log.Info("rejected non-admin user request for all spaces")
return
}
sp, err := h.Store.Space.GetAll(ctx) sp, err := h.Store.Space.GetAll(ctx)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
@ -320,28 +345,9 @@ func (h *Handler) GetAll(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, sp) response.WriteJSON(w, sp)
} }
// GetSpaceViewers returns the users that can see the shared spaces.
func (h *Handler) GetSpaceViewers(w http.ResponseWriter, r *http.Request) {
method := "space.Viewers"
ctx := domain.GetRequestContext(r)
v, err := h.Store.Space.Viewers(ctx)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(v) == 0 {
v = []space.Viewer{}
}
response.WriteJSON(w, v)
}
// Update processes request to save space object to the database // Update processes request to save space object to the database
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
method := "space.Update" method := "space.update"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
if !ctx.Editor { if !ctx.Editor {
@ -349,9 +355,9 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return return
} }
folderID := request.Param(r, "folderID") spaceID := request.Param(r, "spaceID")
if len(folderID) == 0 { if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "folderID") response.WriteMissingDataError(w, method, "spaceID")
return return
} }
@ -377,7 +383,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return return
} }
sp.RefID = folderID sp.RefID = spaceID
ctx.Transaction, err = h.Runtime.Db.Beginx() ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil { if err != nil {
@ -401,9 +407,9 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, sp) response.WriteJSON(w, sp)
} }
// Remove moves documents to another folder before deleting it // Remove moves documents to another space before deleting it
func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) { func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
method := "space.Remove" method := "space.remove"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
if !h.Runtime.Product.License.IsValid() { if !h.Runtime.Product.License.IsValid() {
@ -416,9 +422,9 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
return return
} }
id := request.Param(r, "folderID") id := request.Param(r, "spaceID")
if len(id) == 0 { if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID") response.WriteMissingDataError(w, method, "spaceID")
return return
} }
@ -436,14 +442,6 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
return return
} }
_, err = h.Store.Space.Delete(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
err = h.Store.Document.MoveDocumentSpace(ctx, id, move) err = h.Store.Document.MoveDocumentSpace(ctx, id, move)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
@ -452,7 +450,23 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
return return
} }
err = h.Store.Space.MoveSpaceRoles(ctx, id, move) _, err = h.Store.Category.RemoveSpaceCategoryMemberships(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
_, err = h.Store.Space.Delete(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
_, err = h.Store.Permission.DeleteSpacePermissions(ctx, id)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
@ -475,9 +489,9 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
response.WriteEmpty(w) response.WriteEmpty(w)
} }
// Delete deletes empty space. // Delete removes space.
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) { func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
method := "space.Delete" method := "space.delete"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
if !h.Runtime.Product.License.IsValid() { if !h.Runtime.Product.License.IsValid() {
@ -490,9 +504,9 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
id := request.Param(r, "folderID") id := request.Param(r, "spaceID")
if len(id) == 0 { if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID") response.WriteMissingDataError(w, method, "spaceID")
return return
} }
@ -504,7 +518,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
_, err = h.Store.Space.Delete(ctx, id) _, err = h.Store.Document.DeleteBySpace(ctx, id)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
@ -512,7 +526,16 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
_, err = h.Store.Space.DeleteSpaceRoles(ctx, id) _, err = h.Store.Permission.DeleteSpacePermissions(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// remove category permissions
_, err = h.Store.Permission.DeleteSpaceCategoryPermissions(ctx, id)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
@ -528,6 +551,23 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
// remove category and members for space
_, err = h.Store.Category.DeleteBySpace(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
_, err = h.Store.Space.Delete(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Store.Audit.Record(ctx, audit.EventTypeSpaceDelete) h.Store.Audit.Record(ctx, audit.EventTypeSpaceDelete)
ctx.Transaction.Commit() ctx.Transaction.Commit()
@ -535,222 +575,15 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
response.WriteEmpty(w) response.WriteEmpty(w)
} }
// SetPermissions persists specified spac3 permissions
func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) {
method := "space.SetPermissions"
ctx := domain.GetRequestContext(r)
if !ctx.Editor {
response.WriteForbiddenError(w)
return
}
id := request.Param(r, "folderID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID")
return
}
sp, err := h.Store.Space.Get(ctx, id)
if err != nil {
response.WriteNotFoundError(w, method, "No such space")
return
}
if sp.UserID != ctx.UserID {
response.WriteForbiddenError(w)
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
}
var model = space.RolesModel{}
err = json.Unmarshal(body, &model)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// We compare new permisions to what we had before.
// Why? So we can send out folder invitation emails.
previousRoles, err := h.Store.Space.GetRoles(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Store all previous roles as map for easy querying
previousRoleUsers := make(map[string]bool)
for _, v := range previousRoles {
previousRoleUsers[v.UserID] = true
}
// Who is sharing this folder?
inviter, err := h.Store.User.Get(ctx, ctx.UserID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Nuke all previous permissions for this folder
_, err = h.Store.Space.DeleteSpaceRoles(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
me := false
hasEveryoneRole := false
roleCount := 0
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
for _, role := range model.Roles {
role.OrgID = ctx.OrgID
role.LabelID = id
// Ensure the folder owner always has access!
if role.UserID == ctx.UserID {
me = true
role.CanView = true
role.CanEdit = true
}
if len(role.UserID) == 0 && (role.CanView || role.CanEdit) {
hasEveryoneRole = true
}
// Only persist if there is a role!
if role.CanView || role.CanEdit {
roleID := uniqueid.Generate()
role.RefID = roleID
err = h.Store.Space.AddRole(ctx, role)
if err != nil {
h.Runtime.Log.Error("add role", err)
}
roleCount++
// We send out folder invitation emails to those users
// that have *just* been given permissions.
if _, isExisting := previousRoleUsers[role.UserID]; !isExisting {
// we skip 'everyone' (user id != empty string)
if len(role.UserID) > 0 {
var existingUser user.User
existingUser, err = h.Store.User.Get(ctx, role.UserID)
if err == nil {
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.ShareFolderExistingUser(existingUser.Email, inviter.Fullname(), url, sp.Name, model.Message)
h.Runtime.Log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, sp.Name, existingUser.Email))
} else {
response.WriteServerError(w, method, err)
}
}
}
}
}
// Do we need to ensure permissions for space owner when shared?
if !me {
role := space.Role{}
role.LabelID = id
role.OrgID = ctx.OrgID
role.UserID = ctx.UserID
role.CanEdit = true
role.CanView = true
roleID := uniqueid.Generate()
role.RefID = roleID
err = h.Store.Space.AddRole(ctx, role)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
return
}
}
// Mark up folder type as either public, private or restricted access.
if hasEveryoneRole {
sp.Type = space.ScopePublic
} else {
if roleCount > 1 {
sp.Type = space.ScopeRestricted
} else {
sp.Type = space.ScopePrivate
}
}
err = h.Store.Space.Update(ctx, sp)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Store.Audit.Record(ctx, audit.EventTypeSpacePermission)
ctx.Transaction.Commit()
response.WriteEmpty(w)
}
// GetPermissions returns user permissions for the requested folder.
func (h *Handler) GetPermissions(w http.ResponseWriter, r *http.Request) {
method := "space.GetPermissions"
ctx := domain.GetRequestContext(r)
folderID := request.Param(r, "folderID")
if len(folderID) == 0 {
response.WriteMissingDataError(w, method, "folderID")
return
}
roles, err := h.Store.Space.GetRoles(ctx, folderID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(roles) == 0 {
roles = []space.Role{}
}
response.WriteJSON(w, roles)
}
// AcceptInvitation records the fact that a user has completed space onboard process. // AcceptInvitation records the fact that a user has completed space onboard process.
func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) { func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
method := "space.AcceptInvitation" method := "space.AcceptInvitation"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
ctx.Subdomain = organization.GetSubdomainFromHost(r) ctx.Subdomain = organization.GetSubdomainFromHost(r)
folderID := request.Param(r, "folderID") spaceID := request.Param(r, "spaceID")
if len(folderID) == 0 { if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "folderID") response.WriteMissingDataError(w, method, "spaceID")
return return
} }
@ -831,14 +664,14 @@ func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, u) response.WriteJSON(w, u)
} }
// Invite sends users folder invitation emails. // Invite sends users space invitation emails.
func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) { func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
method := "space.Invite" method := "space.Invite"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
id := request.Param(r, "folderID") id := request.Param(r, "spaceID")
if len(id) == 0 { if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID") response.WriteMissingDataError(w, method, "spaceID")
return return
} }
@ -917,6 +750,7 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
a.OrgID = ctx.OrgID a.OrgID = ctx.OrgID
a.Admin = false a.Admin = false
a.Editor = false a.Editor = false
a.Users = false
a.Active = true a.Active = true
accountID := uniqueid.Generate() accountID := uniqueid.Generate()
a.RefID = accountID a.RefID = accountID
@ -931,18 +765,18 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
} }
// Ensure they have space roles // Ensure they have space roles
h.Store.Space.DeleteUserSpaceRoles(ctx, sp.RefID, u.RefID) h.Store.Permission.DeleteUserSpacePermissions(ctx, sp.RefID, u.RefID)
role := space.Role{} perm := permission.Permission{}
role.LabelID = sp.RefID perm.OrgID = sp.OrgID
role.OrgID = ctx.OrgID perm.Who = "user"
role.UserID = u.RefID perm.WhoID = u.RefID
role.CanEdit = false perm.Scope = "object"
role.CanView = true perm.Location = "space"
roleID := uniqueid.Generate() perm.RefID = sp.RefID
role.RefID = roleID perm.Action = "" // we send array for actions below
err = h.Store.Space.AddRole(ctx, role) err = h.Store.Permission.AddPermissions(ctx, perm, permission.SpaceView)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
@ -952,7 +786,7 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name))) url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx} mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.ShareFolderExistingUser(email, inviter.Fullname(), url, sp.Name, model.Message) go mailer.ShareSpaceExistingUser(email, inviter.Fullname(), url, sp.Name, model.Message)
h.Runtime.Log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, sp.Name, email)) h.Runtime.Log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, sp.Name, email))
} else { } else {
@ -973,7 +807,7 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
} }
} }
// We ensure that the folder is marked as restricted as a minimum! // We ensure that the space is marked as restricted as a minimum!
if len(model.Recipients) > 0 && sp.Type == space.ScopePrivate { if len(model.Recipients) > 0 && sp.Type == space.ScopePrivate {
sp.Type = space.ScopeRestricted sp.Type = space.ScopeRestricted

View file

@ -13,12 +13,10 @@
package mysql package mysql
import ( import (
"database/sql"
"fmt" "fmt"
"time" "time"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql" "github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/space" "github.com/documize/community/model/space"
@ -36,18 +34,11 @@ func (s Scope) Add(ctx domain.RequestContext, sp space.Space) (err error) {
sp.Created = time.Now().UTC() sp.Created = time.Now().UTC()
sp.Revised = time.Now().UTC() sp.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO label (refid, label, orgid, userid, type, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?)") _, err = ctx.Transaction.Exec("INSERT INTO label (refid, label, orgid, userid, type, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?)",
defer streamutil.Close(stmt) sp.RefID, sp.Name, sp.OrgID, sp.UserID, sp.Type, sp.Created, sp.Revised)
if err != nil {
err = errors.Wrap(err, "unable to prepare insert for label")
return
}
_, err = stmt.Exec(sp.RefID, sp.Name, sp.OrgID, sp.UserID, sp.Type, sp.Created, sp.Revised)
if err != nil { if err != nil {
err = errors.Wrap(err, "unable to execute insert for label") err = errors.Wrap(err, "unable to execute insert for label")
return
} }
return return
@ -55,18 +46,11 @@ func (s Scope) Add(ctx domain.RequestContext, sp space.Space) (err error) {
// Get returns a space from the store. // Get returns a space from the store.
func (s Scope) Get(ctx domain.RequestContext, id string) (sp space.Space, err error) { func (s Scope) Get(ctx domain.RequestContext, id string) (sp space.Space, err error) {
stmt, err := s.Runtime.Db.Preparex("SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label WHERE orgid=? and refid=?") err = s.Runtime.Db.Get(&sp, "SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label WHERE orgid=? and refid=?",
defer streamutil.Close(stmt) ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare select for label %s", id))
return
}
err = stmt.Get(&sp, ctx.OrgID, id)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select for label %s", id)) err = errors.Wrap(err, fmt.Sprintf("unable to execute select for label %s", id))
return
} }
return return
@ -80,39 +64,52 @@ func (s Scope) PublicSpaces(ctx domain.RequestContext, orgID string) (sp []space
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("Unable to execute GetPublicFolders for org %s", orgID)) err = errors.Wrap(err, fmt.Sprintf("Unable to execute GetPublicFolders for org %s", orgID))
return
} }
return return
} }
// GetAll returns spaces that the user can see. // GetViewable returns spaces that the user can see.
// Also handles which spaces can be seen by anonymous users. // Also handles which spaces can be seen by anonymous users.
func (s Scope) GetAll(ctx domain.RequestContext) (sp []space.Space, err error) { func (s Scope) GetViewable(ctx domain.RequestContext) (sp []space.Space, err error) {
sql := ` sql := `
(SELECT id,refid,label as name,orgid,userid,type,created,revised from label WHERE orgid=? AND type=2 AND userid=?) SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label
UNION ALL WHERE orgid=?
(SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label a where orgid=? AND type=1 AND refid in AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
(SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))) SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' UNION ALL
UNION ALL SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role'
(SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label a where orgid=? AND type=3 AND refid in AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0')
(SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) ))
ORDER BY name` ORDER BY name`
err = s.Runtime.Db.Select(&sp, sql, err = s.Runtime.Db.Select(&sp, sql,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID, ctx.OrgID,
ctx.UserID, ctx.UserID,
ctx.OrgID, ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID) ctx.UserID)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("Unable to execute select labels for org %s", ctx.OrgID)) err = errors.Wrap(err, fmt.Sprintf("failed space.GetViewable org %s", ctx.OrgID))
}
return return
} }
// GetAll for admin users!
func (s Scope) GetAll(ctx domain.RequestContext) (sp []space.Space, err error) {
sql := `
SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label
WHERE orgid=?
ORDER BY name`
err = s.Runtime.Db.Select(&sp, sql, ctx.OrgID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed space.GetAll org %s", ctx.OrgID))
}
return return
} }
@ -120,173 +117,17 @@ func (s Scope) GetAll(ctx domain.RequestContext) (sp []space.Space, err error) {
func (s Scope) Update(ctx domain.RequestContext, sp space.Space) (err error) { func (s Scope) Update(ctx domain.RequestContext, sp space.Space) (err error) {
sp.Revised = time.Now().UTC() sp.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.PrepareNamed("UPDATE label SET label=:name, type=:type, userid=:userid, revised=:revised WHERE orgid=:orgid AND refid=:refid") _, err = ctx.Transaction.NamedExec("UPDATE label SET label=:name, type=:type, userid=:userid, revised=:revised WHERE orgid=:orgid AND refid=:refid", &sp)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare update for label %s", sp.RefID))
return
}
_, err = stmt.Exec(&sp)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute update for label %s", sp.RefID)) err = errors.Wrap(err, fmt.Sprintf("unable to execute update for label %s", sp.RefID))
return
} }
return return
} }
// ChangeOwner transfer space ownership.
func (s Scope) ChangeOwner(ctx domain.RequestContext, currentOwner, newOwner string) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE label SET userid=? WHERE userid=? AND orgid=?")
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare change space owner for %s", currentOwner))
return
}
_, err = stmt.Exec(newOwner, currentOwner, ctx.OrgID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute change space owner for %s", currentOwner))
return
}
return
}
// Viewers returns the list of people who can see shared spaces.
func (s Scope) Viewers(ctx domain.RequestContext) (v []space.Viewer, err error) {
sql := `
SELECT a.userid,
COALESCE(u.firstname, '') as firstname,
COALESCE(u.lastname, '') as lastname,
COALESCE(u.email, '') as email,
a.labelid,
b.label as name,
b.type
FROM labelrole a
LEFT JOIN label b ON b.refid=a.labelid
LEFT JOIN user u ON u.refid=a.userid
WHERE a.orgid=? AND b.type != 2
GROUP BY a.labelid,a.userid
ORDER BY u.firstname,u.lastname`
err = s.Runtime.Db.Select(&v, sql, ctx.OrgID)
return
}
// Delete removes space from the store. // Delete removes space from the store.
func (s Scope) Delete(ctx domain.RequestContext, id string) (rows int64, err error) { func (s Scope) Delete(ctx domain.RequestContext, id string) (rows int64, err error) {
b := mysql.BaseQuery{} b := mysql.BaseQuery{}
return b.DeleteConstrained(ctx.Transaction, "label", ctx.OrgID, id) return b.DeleteConstrained(ctx.Transaction, "label", ctx.OrgID, id)
} }
// AddRole inserts the given record into the labelrole database table.
func (s Scope) AddRole(ctx domain.RequestContext, r space.Role) (err error) {
r.Created = time.Now().UTC()
r.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO labelrole (refid, labelid, orgid, userid, canview, canedit, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "unable to prepare insert for space role")
return
}
_, err = stmt.Exec(r.RefID, r.LabelID, r.OrgID, r.UserID, r.CanView, r.CanEdit, r.Created, r.Revised)
if err != nil {
err = errors.Wrap(err, "unable to execute insert for space role")
return
}
return
}
// GetRoles returns a slice of labelrole records, for the given labelID in the client's organization, grouped by user.
func (s Scope) GetRoles(ctx domain.RequestContext, labelID string) (r []space.Role, err error) {
query := `SELECT id, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? AND labelid=?` // was + "GROUP BY userid"
err = s.Runtime.Db.Select(&r, query, ctx.OrgID, labelID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select for space roles %s", labelID))
return
}
return
}
// GetUserRoles returns a slice of role records, for both the client's user and organization, and
// those space roles that exist for all users in the client's organization.
func (s Scope) GetUserRoles(ctx domain.RequestContext) (r []space.Role, err error) {
err = s.Runtime.Db.Select(&r, `
SELECT id, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? and userid=?
UNION ALL
SELECT id, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? AND userid=''`,
ctx.OrgID, ctx.UserID, ctx.OrgID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select for user space roles %s", ctx.UserID))
return
}
return
}
// DeleteRole deletes the labelRoleID record from the labelrole table.
func (s Scope) DeleteRole(ctx domain.RequestContext, roleID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM labelrole WHERE orgid='%s' AND refid='%s'", ctx.OrgID, roleID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteSpaceRoles deletes records from the labelrole table which have the given space ID.
func (s Scope) DeleteSpaceRoles(ctx domain.RequestContext, spaceID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM labelrole WHERE orgid='%s' AND labelid='%s'", ctx.OrgID, spaceID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// DeleteUserSpaceRoles removes all roles for the specified user, for the specified space.
func (s Scope) DeleteUserSpaceRoles(ctx domain.RequestContext, spaceID, userID string) (rows int64, err error) {
b := mysql.BaseQuery{}
sql := fmt.Sprintf("DELETE FROM labelrole WHERE orgid='%s' AND labelid='%s' AND userid='%s'",
ctx.OrgID, spaceID, userID)
return b.DeleteWhere(ctx.Transaction, sql)
}
// MoveSpaceRoles changes the space ID for space role records from previousLabel to newLabel.
func (s Scope) MoveSpaceRoles(ctx domain.RequestContext, previousLabel, newLabel string) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE labelrole SET labelid=? WHERE labelid=? AND orgid=?")
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare move space roles for label %s", previousLabel))
return
}
_, err = stmt.Exec(newLabel, previousLabel, ctx.OrgID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute move space roles for label %s", previousLabel))
}
return
}

View file

@ -1,59 +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 space handles API calls and persistence for spaces.
// Spaces in Documize contain documents.
package space
import (
"database/sql"
"github.com/documize/community/domain"
)
// CanViewSpace returns if the user has permission to view the given spaceID.
func CanViewSpace(ctx domain.RequestContext, s domain.Store, spaceID string) (hasPermission bool) {
roles, err := s.Space.GetRoles(ctx, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.LabelID == spaceID && (role.CanView || role.CanEdit) {
return true
}
}
return false
}
// CanViewSpaceDocuments returns if the user has permission to view a document within the specified space.
func CanViewSpaceDocuments(ctx domain.RequestContext, s domain.Store, spaceID string) (hasPermission bool) {
roles, err := s.Space.GetRoles(ctx, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.LabelID == spaceID && (role.CanView || role.CanEdit) {
return true
}
}
return false
}

View file

@ -20,37 +20,15 @@ import (
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/mail" "github.com/documize/community/domain/mail"
"github.com/documize/community/model/account" "github.com/documize/community/model/account"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/space" "github.com/documize/community/model/space"
"github.com/documize/community/model/user" "github.com/documize/community/model/user"
) )
// addSpace prepares and creates space record. // Invite new user to a space that someone has shared with them.
func addSpace(ctx domain.RequestContext, s *domain.Store, sp space.Space) (err error) {
sp.Type = space.ScopePrivate
sp.UserID = ctx.UserID
err = s.Space.Add(ctx, sp)
if err != nil {
return
}
role := space.Role{}
role.LabelID = sp.RefID
role.OrgID = sp.OrgID
role.UserID = ctx.UserID
role.CanEdit = true
role.CanView = true
role.RefID = uniqueid.Generate()
err = s.Space.AddRole(ctx, role)
return
}
// Invite new user to a folder that someone has shared with them.
// We create the user account with default values and then take them // We create the user account with default values and then take them
// through a welcome process designed to capture profile data. // through a welcome process designed to capture profile data.
// We add them to the organization and grant them view-only folder access. // We add them to the organization and grant them view-only space access.
func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *domain.Store, email string, invitedBy user.User, func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *domain.Store, email string, invitedBy user.User,
baseURL string, sp space.Space, invitationMessage string) (err error) { baseURL string, sp space.Space, invitationMessage string) (err error) {
@ -75,25 +53,25 @@ func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *d
a.OrgID = ctx.OrgID a.OrgID = ctx.OrgID
a.Admin = false a.Admin = false
a.Editor = false a.Editor = false
a.Users = false
a.Active = true a.Active = true
accountID := uniqueid.Generate() a.RefID = uniqueid.Generate()
a.RefID = accountID
err = s.Account.Add(ctx, a) err = s.Account.Add(ctx, a)
if err != nil { if err != nil {
return return
} }
role := space.Role{} perm := permission.Permission{}
role.LabelID = sp.RefID perm.OrgID = sp.OrgID
role.OrgID = ctx.OrgID perm.Who = "user"
role.UserID = userID perm.WhoID = userID
role.CanEdit = false perm.Scope = "object"
role.CanView = true perm.Location = "space"
roleID := uniqueid.Generate() perm.RefID = sp.RefID
role.RefID = roleID perm.Action = "" // we send array for actions below
err = s.Space.AddRole(ctx, role) err = s.Permission.AddPermissions(ctx, perm, permission.SpaceView)
if err != nil { if err != nil {
return return
} }
@ -101,7 +79,7 @@ func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *d
mailer := mail.Mailer{Runtime: rt, Store: s, Context: ctx} mailer := mail.Mailer{Runtime: rt, Store: s, Context: ctx}
url := fmt.Sprintf("%s/%s", baseURL, u.Salt) url := fmt.Sprintf("%s/%s", baseURL, u.Salt)
go mailer.ShareFolderNewUser(u.Email, invitedBy.Fullname(), url, sp.Name, invitationMessage) go mailer.ShareSpaceNewUser(u.Email, invitedBy.Fullname(), url, sp.Name, invitationMessage)
return return
} }

View file

@ -1,7 +1,6 @@
package space package space
import ( import (
"database/sql"
"testing" "testing"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
@ -39,17 +38,19 @@ func TestSpace(t *testing.T) {
t.Error("failed to add sp space") t.Error("failed to add sp space")
} }
r.RefID = uniqueid.Generate() perm := space.Permission{}
r.LabelID = spaceID perm.OrgID = ctx.OrgID
r.OrgID = ctx.OrgID perm.Who = "user"
r.UserID = "testAddSpace" perm.WhoID = ctx.UserID
r.CanView = true perm.Scope = "object"
r.CanEdit = true perm.Location = "space"
perm.RefID = spaceID
perm.Action = "" // we send array for actions below
err = s.Space.AddRole(ctx, r) err = s.Space.AddPermissions(ctx, perm, space.SpaceOwner, space.SpaceManage, space.SpaceView)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
t.Error("failed to add role r") t.Error("failed to add permission")
} }
ctx.Transaction.Commit() ctx.Transaction.Commit()
@ -104,17 +105,19 @@ func TestSpace(t *testing.T) {
t.Error("failed to add sp2") t.Error("failed to add sp2")
} }
r2.RefID = uniqueid.Generate() perm := space.Permission{}
r2.LabelID = spaceID2 perm.OrgID = ctx.OrgID
r2.OrgID = ctx.OrgID perm.Who = "user"
r2.UserID = ctx.UserID perm.WhoID = ctx.UserID
r2.CanView = true perm.Scope = "object"
r2.CanEdit = true perm.Location = "space"
perm.RefID = spaceID2
perm.Action = "" // we send array for actions below
err = s.Space.AddRole(ctx, r2) err = s.Space.AddPermissions(ctx, perm, space.SpaceOwner, space.SpaceManage, space.SpaceView)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
t.Error("failed to add role") t.Error("failed to add permission")
} }
ctx.Transaction.Commit() ctx.Transaction.Commit()
@ -160,33 +163,27 @@ func TestSpace(t *testing.T) {
} }
}) })
t.Run("Viewers", func(t *testing.T) {
viewers, err := s.Space.Viewers(ctx)
if err != nil || viewers == nil {
t.Error("failed to get viewers")
return
}
})
t.Run("Add Role", func(t *testing.T) { t.Run("Add Role", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx() ctx.Transaction, err = rt.Db.Beginx()
r3.CanView = true perm := space.Permission{}
r3.CanEdit = true perm.OrgID = ctx.OrgID
r3.RefID = uniqueid.Generate() perm.Who = "user"
r3.LabelID = spaceID perm.WhoID = ctx.UserID
r3.OrgID = ctx.OrgID perm.Scope = "object"
r3.UserID = "testAddRole" perm.Location = "space"
perm.RefID = spaceID
perm.Action = "" // we send array for actions below
err = s.Space.AddRole(ctx, r3) err = s.Space.AddPermissions(ctx, perm, space.DocumentAdd, space.DocumentDelete, space.DocumentMove)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
t.Error("failed to add role") t.Error("failed to add permission")
return
} }
ctx.Transaction.Commit() ctx.Transaction.Commit()
roles, err := s.Space.GetRoles(ctx, spaceID) roles, err := s.Space.GetUserPermissions(ctx, spaceID)
if err != nil || roles == nil { if err != nil || roles == nil {
t.Error("Could not get any roles") t.Error("Could not get any roles")
return return
@ -194,61 +191,16 @@ func TestSpace(t *testing.T) {
// TODO: could we Verify the role was added with the if r3.UserID == Returned.UserID? // TODO: could we Verify the role was added with the if r3.UserID == Returned.UserID?
}) })
t.Run("Get User Roles", func(t *testing.T) { t.Run("Get User Permissions", func(t *testing.T) {
userRoles, err := s.Space.GetUserRoles(ctx) userRoles, err := s.Space.GetUserPermissions(ctx, spaceID)
if err != nil || userRoles == nil { if err != nil || userRoles == nil {
t.Error("failed to get user roles") t.Error("failed to get user roles")
return return
} }
}) })
t.Run("Move space Roles", func(t *testing.T) { // teardown
ctx.Transaction, err = rt.Db.Beginx() t.Run("Delete space", func(t *testing.T) {
err := s.Space.MoveSpaceRoles(ctx, spaceID, spaceID2)
if err != nil {
ctx.Transaction.Rollback()
t.Error("failed to move space roles")
return
}
ctx.Transaction.Commit()
})
t.Run("Delete Role", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
rowsDeleted, err := s.Space.DeleteRole(ctx, r3.RefID)
if err != nil || rowsDeleted == 0 {
ctx.Transaction.Rollback()
t.Error("failed to delete roles")
return
}
ctx.Transaction.Commit()
})
t.Run("Delete space Roles", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
_, err := s.Space.DeleteSpaceRoles(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
ctx.Transaction.Rollback()
t.Error("failed to delete space roles")
return
}
ctx.Transaction.Commit()
})
t.Run("Delete user space Roles", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
_, err := s.Space.DeleteUserSpaceRoles(ctx, spaceID2, ctx.UserID)
if err != nil && err != sql.ErrNoRows {
ctx.Transaction.Rollback()
t.Error("failed to delete user space roles")
return
}
ctx.Transaction.Commit()
})
//Delete spaces last, otherwise tests may fail
t.Run("Delete Space", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx() ctx.Transaction, err = rt.Db.Beginx()
_, err = s.Space.Delete(ctx, spaceID) _, err = s.Space.Delete(ctx, spaceID)
@ -261,11 +213,7 @@ func TestSpace(t *testing.T) {
ctx.Transaction.Commit() ctx.Transaction.Commit()
}) })
// t.Run("Delete space 2", func(t *testing.T) {
// teardown code goes here
//
t.Run("Delete sp2 Space", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx() ctx.Transaction, err = rt.Db.Beginx()
_, err = s.Space.Delete(ctx, spaceID2) _, err = s.Space.Delete(ctx, spaceID2)
@ -277,15 +225,4 @@ func TestSpace(t *testing.T) {
ctx.Transaction.Commit() ctx.Transaction.Commit()
}) })
t.Run("Delete r Role", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
rowsDeleted, err := s.Space.DeleteRole(ctx, r.RefID)
if err != nil || rowsDeleted == 0 {
ctx.Transaction.Rollback()
t.Error("failed to delete role r in teardown")
return
}
ctx.Transaction.Commit()
})
} }

View file

@ -14,7 +14,6 @@ package mysql
import ( import (
"fmt" "fmt"
"github.com/documize/community/core/streamutil"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -25,15 +24,8 @@ type BaseQuery struct {
// Delete record. // Delete record.
func (m *BaseQuery) Delete(tx *sqlx.Tx, table string, id string) (rows int64, err error) { func (m *BaseQuery) Delete(tx *sqlx.Tx, table string, id string) (rows int64, err error) {
stmt, err := tx.Preparex("DELETE FROM " + table + " WHERE refid=?") result, err := tx.Exec("DELETE FROM "+table+" WHERE refid=?", id)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare delete of row in table %s", table))
return
}
result, err := stmt.Exec(id)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to delete row in table %s", table)) err = errors.Wrap(err, fmt.Sprintf("unable to delete row in table %s", table))
return return
@ -46,15 +38,8 @@ func (m *BaseQuery) Delete(tx *sqlx.Tx, table string, id string) (rows int64, er
// DeleteConstrained record constrained to Organization using refid. // DeleteConstrained record constrained to Organization using refid.
func (m *BaseQuery) DeleteConstrained(tx *sqlx.Tx, table string, orgID, id string) (rows int64, err error) { func (m *BaseQuery) DeleteConstrained(tx *sqlx.Tx, table string, orgID, id string) (rows int64, err error) {
stmt, err := tx.Preparex("DELETE FROM " + table + " WHERE orgid=? AND refid=?") result, err := tx.Exec("DELETE FROM "+table+" WHERE orgid=? AND refid=?", orgID, id)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare constrained delete of row in table %s", table))
return
}
result, err := stmt.Exec(orgID, id)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to delete row in table %s", table)) err = errors.Wrap(err, fmt.Sprintf("unable to delete row in table %s", table))
return return
@ -67,15 +52,8 @@ func (m *BaseQuery) DeleteConstrained(tx *sqlx.Tx, table string, orgID, id strin
// DeleteConstrainedWithID record constrained to Organization using non refid. // DeleteConstrainedWithID record constrained to Organization using non refid.
func (m *BaseQuery) DeleteConstrainedWithID(tx *sqlx.Tx, table string, orgID, id string) (rows int64, err error) { func (m *BaseQuery) DeleteConstrainedWithID(tx *sqlx.Tx, table string, orgID, id string) (rows int64, err error) {
stmt, err := tx.Preparex("DELETE FROM " + table + " WHERE orgid=? AND id=?") result, err := tx.Exec("DELETE FROM "+table+" WHERE orgid=? AND id=?", orgID, id)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare ConstrainedWithID delete of row in table %s", table))
return
}
result, err := stmt.Exec(orgID, id)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to delete row in table %s", table)) err = errors.Wrap(err, fmt.Sprintf("unable to delete row in table %s", table))
return return
@ -89,6 +67,7 @@ func (m *BaseQuery) DeleteConstrainedWithID(tx *sqlx.Tx, table string, orgID, id
// DeleteWhere free form query. // DeleteWhere free form query.
func (m *BaseQuery) DeleteWhere(tx *sqlx.Tx, statement string) (rows int64, err error) { func (m *BaseQuery) DeleteWhere(tx *sqlx.Tx, statement string) (rows int64, err error) {
result, err := tx.Exec(statement) result, err := tx.Exec(statement)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to delete rows: %s", statement)) err = errors.Wrap(err, fmt.Sprintf("unable to delete rows: %s", statement))
return return

View file

@ -18,10 +18,12 @@ import (
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/block" "github.com/documize/community/model/block"
"github.com/documize/community/model/category"
"github.com/documize/community/model/doc" "github.com/documize/community/model/doc"
"github.com/documize/community/model/link" "github.com/documize/community/model/link"
"github.com/documize/community/model/org" "github.com/documize/community/model/org"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/pin" "github.com/documize/community/model/pin"
"github.com/documize/community/model/search" "github.com/documize/community/model/search"
"github.com/documize/community/model/space" "github.com/documize/community/model/space"
@ -35,11 +37,13 @@ type Store struct {
Attachment AttachmentStorer Attachment AttachmentStorer
Audit AuditStorer Audit AuditStorer
Block BlockStorer Block BlockStorer
Category CategoryStorer
Document DocumentStorer Document DocumentStorer
Link LinkStorer Link LinkStorer
Organization OrganizationStorer Organization OrganizationStorer
Page PageStorer Page PageStorer
Pin PinStorer Pin PinStorer
Permission PermissionStorer
Search SearchStorer Search SearchStorer
Setting SettingStorer Setting SettingStorer
Space SpaceStorer Space SpaceStorer
@ -51,18 +55,45 @@ type SpaceStorer interface {
Add(ctx RequestContext, sp space.Space) (err error) Add(ctx RequestContext, sp space.Space) (err error)
Get(ctx RequestContext, id string) (sp space.Space, err error) Get(ctx RequestContext, id string) (sp space.Space, err error)
PublicSpaces(ctx RequestContext, orgID string) (sp []space.Space, err error) PublicSpaces(ctx RequestContext, orgID string) (sp []space.Space, err error)
GetViewable(ctx RequestContext) (sp []space.Space, err error)
GetAll(ctx RequestContext) (sp []space.Space, err error) GetAll(ctx RequestContext) (sp []space.Space, err error)
Update(ctx RequestContext, sp space.Space) (err error) Update(ctx RequestContext, sp space.Space) (err error)
ChangeOwner(ctx RequestContext, currentOwner, newOwner string) (err error)
Viewers(ctx RequestContext) (v []space.Viewer, err error)
Delete(ctx RequestContext, id string) (rows int64, err error) Delete(ctx RequestContext, id string) (rows int64, err error)
AddRole(ctx RequestContext, r space.Role) (err error) }
GetRoles(ctx RequestContext, labelID string) (r []space.Role, err error)
GetUserRoles(ctx RequestContext) (r []space.Role, err error) // CategoryStorer defines required methods for category and category membership management
DeleteRole(ctx RequestContext, roleID string) (rows int64, err error) type CategoryStorer interface {
DeleteSpaceRoles(ctx RequestContext, spaceID string) (rows int64, err error) Add(ctx RequestContext, c category.Category) (err error)
DeleteUserSpaceRoles(ctx RequestContext, spaceID, userID string) (rows int64, err error) Update(ctx RequestContext, c category.Category) (err error)
MoveSpaceRoles(ctx RequestContext, previousLabel, newLabel string) (err error) Get(ctx RequestContext, id string) (c category.Category, err error)
GetBySpace(ctx RequestContext, spaceID string) (c []category.Category, err error)
GetAllBySpace(ctx RequestContext, spaceID string) (c []category.Category, err error)
GetSpaceCategorySummary(ctx RequestContext, spaceID string) (c []category.SummaryModel, err error)
Delete(ctx RequestContext, id string) (rows int64, err error)
AssociateDocument(ctx RequestContext, m category.Member) (err error)
DisassociateDocument(ctx RequestContext, categoryID, documentID string) (rows int64, err error)
RemoveCategoryMembership(ctx RequestContext, categoryID string) (rows int64, err error)
DeleteBySpace(ctx RequestContext, spaceID string) (rows int64, err error)
GetDocumentCategoryMembership(ctx RequestContext, documentID string) (c []category.Category, err error)
GetSpaceCategoryMembership(ctx RequestContext, spaceID string) (c []category.Member, err error)
RemoveDocumentCategories(ctx RequestContext, documentID string) (rows int64, err error)
RemoveSpaceCategoryMemberships(ctx RequestContext, spaceID string) (rows int64, err error)
}
// PermissionStorer defines required methods for space/document permission management
type PermissionStorer interface {
AddPermission(ctx RequestContext, r permission.Permission) (err error)
AddPermissions(ctx RequestContext, r permission.Permission, actions ...permission.Action) (err error)
GetUserSpacePermissions(ctx RequestContext, spaceID string) (r []permission.Permission, err error)
GetSpacePermissions(ctx RequestContext, spaceID string) (r []permission.Permission, err error)
DeleteSpacePermissions(ctx RequestContext, spaceID string) (rows int64, err error)
DeleteUserSpacePermissions(ctx RequestContext, spaceID, userID string) (rows int64, err error)
DeleteUserPermissions(ctx RequestContext, userID string) (rows int64, err error)
DeleteCategoryPermissions(ctx RequestContext, categoryID string) (rows int64, err error)
DeleteSpaceCategoryPermissions(ctx RequestContext, spaceID string) (rows int64, err error)
GetCategoryPermissions(ctx RequestContext, catID string) (r []permission.Permission, err error)
GetCategoryUsers(ctx RequestContext, catID string) (u []user.User, err error)
GetUserCategoryPermissions(ctx RequestContext, userID string) (r []permission.Permission, err error)
} }
// UserStorer defines required methods for user management // UserStorer defines required methods for user management
@ -75,8 +106,8 @@ type UserStorer interface {
GetBySerial(ctx RequestContext, serial string) (u user.User, err error) GetBySerial(ctx RequestContext, serial string) (u user.User, err error)
GetActiveUsersForOrganization(ctx RequestContext) (u []user.User, err error) GetActiveUsersForOrganization(ctx RequestContext) (u []user.User, err error)
GetUsersForOrganization(ctx RequestContext) (u []user.User, err error) GetUsersForOrganization(ctx RequestContext) (u []user.User, err error)
GetSpaceUsers(ctx RequestContext, folderID string) (u []user.User, err error) GetSpaceUsers(ctx RequestContext, spaceID string) (u []user.User, err error)
GetVisibleUsers(ctx RequestContext) (u []user.User, err error) GetUsersForSpaces(ctx RequestContext, spaces []string) (u []user.User, err error)
UpdateUser(ctx RequestContext, u user.User) (err error) UpdateUser(ctx RequestContext, u user.User) (err error)
UpdateUserPassword(ctx RequestContext, userID, salt, password string) (err error) UpdateUserPassword(ctx RequestContext, userID, salt, password string) (err error)
DeactiveUser(ctx RequestContext, userID string) (err error) DeactiveUser(ctx RequestContext, userID string) (err error)
@ -130,8 +161,7 @@ type DocumentStorer interface {
Add(ctx RequestContext, document doc.Document) (err error) Add(ctx RequestContext, document doc.Document) (err error)
Get(ctx RequestContext, id string) (document doc.Document, err error) Get(ctx RequestContext, id string) (document doc.Document, err error)
GetAll() (ctx RequestContext, documents []doc.Document, err error) GetAll() (ctx RequestContext, documents []doc.Document, err error)
GetBySpace(ctx RequestContext, folderID string) (documents []doc.Document, err error) GetBySpace(ctx RequestContext, spaceID string) (documents []doc.Document, err error)
GetByTag(ctx RequestContext, tag string) (documents []doc.Document, err error)
DocumentList(ctx RequestContext) (documents []doc.Document, err error) DocumentList(ctx RequestContext) (documents []doc.Document, err error)
Templates(ctx RequestContext) (documents []doc.Document, err error) Templates(ctx RequestContext) (documents []doc.Document, err error)
TemplatesBySpace(ctx RequestContext, spaceID string) (documents []doc.Document, err error) TemplatesBySpace(ctx RequestContext, spaceID string) (documents []doc.Document, err error)
@ -141,6 +171,7 @@ type DocumentStorer interface {
ChangeDocumentSpace(ctx RequestContext, document, space string) (err error) ChangeDocumentSpace(ctx RequestContext, document, space string) (err error)
MoveDocumentSpace(ctx RequestContext, id, move string) (err error) MoveDocumentSpace(ctx RequestContext, id, move string) (err error)
Delete(ctx RequestContext, documentID string) (rows int64, err error) Delete(ctx RequestContext, documentID string) (rows int64, err error)
DeleteBySpace(ctx RequestContext, spaceID string) (rows int64, err error)
} }
// SettingStorer defines required methods for persisting global and user level settings // SettingStorer defines required methods for persisting global and user level settings
@ -214,7 +245,6 @@ type PageStorer interface {
Add(ctx RequestContext, model page.NewPage) (err error) Add(ctx RequestContext, model page.NewPage) (err error)
Get(ctx RequestContext, pageID string) (p page.Page, err error) Get(ctx RequestContext, pageID string) (p page.Page, err error)
GetPages(ctx RequestContext, documentID string) (p []page.Page, err error) GetPages(ctx RequestContext, documentID string) (p []page.Page, err error)
GetPagesWhereIn(ctx RequestContext, documentID, inPages string) (p []page.Page, err error)
GetPagesWithoutContent(ctx RequestContext, documentID string) (pages []page.Page, err error) GetPagesWithoutContent(ctx RequestContext, documentID string) (pages []page.Page, err error)
Update(ctx RequestContext, page page.Page, refID, userID string, skipRevision bool) (err error) Update(ctx RequestContext, page page.Page, refID, userID string, skipRevision bool) (err error)
UpdateMeta(ctx RequestContext, meta page.Meta, updateUserID bool) (err error) UpdateMeta(ctx RequestContext, meta page.Meta, updateUserID bool) (err error)

View file

@ -27,12 +27,13 @@ import (
"github.com/documize/community/core/stringutil" "github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/document" "github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search" indexer "github.com/documize/community/domain/search"
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/doc" "github.com/documize/community/model/doc"
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
pm "github.com/documize/community/model/permission"
"github.com/documize/community/model/template" "github.com/documize/community/model/template"
uuid "github.com/nu7hatch/gouuid" uuid "github.com/nu7hatch/gouuid"
) )
@ -112,21 +113,21 @@ func (h *Handler) SaveAs(w http.ResponseWriter, r *http.Request) {
return return
} }
if !document.CanChangeDocument(ctx, *h.Store, model.DocumentID) { // Duplicate document
response.WriteForbiddenError(w) doc, err := h.Store.Document.Get(ctx, model.DocumentID)
return
}
// DB transaction
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil { if err != nil {
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)
return return
} }
// Duplicate document if !permission.HasPermission(ctx, *h.Store, doc.LabelID, pm.DocumentTemplate) {
doc, err := h.Store.Document.Get(ctx, model.DocumentID) response.WriteForbiddenError(w)
return
}
// DB transaction
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil { if err != nil {
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)

View file

@ -35,7 +35,6 @@ import (
"github.com/documize/community/domain/organization" "github.com/documize/community/domain/organization"
"github.com/documize/community/model/account" "github.com/documize/community/model/account"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/space"
"github.com/documize/community/model/user" "github.com/documize/community/model/user"
) )
@ -244,7 +243,6 @@ func (h *Handler) GetOrganizationUsers(w http.ResponseWriter, r *http.Request) {
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)
return return
} }
} else { } else {
u, err = h.Store.User.GetUsersForOrganization(ctx) u, err = h.Store.User.GetUsersForOrganization(ctx)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
@ -273,45 +271,43 @@ func (h *Handler) GetSpaceUsers(w http.ResponseWriter, r *http.Request) {
var u []user.User var u []user.User
var err error var err error
folderID := request.Param(r, "folderID") spaceID := request.Param(r, "spaceID")
if len(folderID) == 0 { if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "folderID") response.WriteMissingDataError(w, method, "spaceID")
return return
} }
// check to see space type as it determines user selection criteria // Get user account as we need to know if user can see all users.
folder, err := h.Store.Space.Get(ctx, folderID) account, err := h.Store.Account.GetUserAccount(ctx, ctx.UserID)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
response.WriteJSON(w, u) response.WriteJSON(w, u)
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)
return return
} }
switch folder.Type { // account.users == false means we restrict viewing to just space users
case space.ScopePublic: if account.Users {
// can see all users
u, err = h.Store.User.GetActiveUsersForOrganization(ctx) u, err = h.Store.User.GetActiveUsersForOrganization(ctx)
break if err != nil && err != sql.ErrNoRows {
case space.ScopePrivate: response.WriteJSON(w, u)
// just me h.Runtime.Log.Error(method, err)
var me user.User return
me, err = h.Store.User.Get(ctx, ctx.UserID) }
u = append(u, me) } else {
break // send back existing space users
case space.ScopeRestricted: u, err = h.Store.User.GetSpaceUsers(ctx, spaceID)
u, err = h.Store.User.GetSpaceUsers(ctx, folderID) if err != nil && err != sql.ErrNoRows {
break response.WriteJSON(w, u)
h.Runtime.Log.Error(method, err)
return
}
} }
if len(u) == 0 { if len(u) == 0 {
u = []user.User{} u = []user.User{}
} }
if err != nil && err != sql.ErrNoRows {
response.WriteJSON(w, u)
h.Runtime.Log.Error(method, err)
return
}
response.WriteJSON(w, u) response.WriteJSON(w, u)
} }
@ -377,7 +373,8 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
err = h.Store.Space.ChangeOwner(ctx, userID, ctx.UserID) // remove all associated roles for this user
_, err = h.Store.Permission.DeleteUserPermissions(ctx, userID)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
@ -467,6 +464,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
a.Editor = u.Editor a.Editor = u.Editor
a.Admin = u.Admin a.Admin = u.Admin
a.Active = u.Active a.Active = u.Active
a.Users = u.ViewUsers
err = h.Store.Account.UpdateAccount(ctx, a) err = h.Store.Account.UpdateAccount(ctx, a)
if err != nil { if err != nil {
@ -537,31 +535,6 @@ func (h *Handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
response.WriteEmpty(w) response.WriteEmpty(w)
} }
// UserSpacePermissions returns folder permission for authenticated user.
func (h *Handler) UserSpacePermissions(w http.ResponseWriter, r *http.Request) {
method := "user.UserSpacePermissions"
ctx := domain.GetRequestContext(r)
userID := request.Param(r, "userID")
if userID != ctx.UserID {
response.WriteForbiddenError(w)
return
}
roles, err := h.Store.Space.GetUserRoles(ctx)
if err == sql.ErrNoRows {
err = nil
roles = []space.Role{}
}
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
response.WriteJSON(w, roles)
}
// ForgotPassword initiates the change password procedure. // ForgotPassword initiates the change password procedure.
// Generates a reset token and sends email to the user. // Generates a reset token and sends email to the user.
// User has to click link in email and then provide a new password. // User has to click link in email and then provide a new password.

View file

@ -18,9 +18,9 @@ import (
"time" "time"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/model/user" "github.com/documize/community/model/user"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -34,18 +34,11 @@ func (s Scope) Add(ctx domain.RequestContext, u user.User) (err error) {
u.Created = time.Now().UTC() u.Created = time.Now().UTC()
u.Revised = time.Now().UTC() u.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO user (refid, firstname, lastname, email, initials, password, salt, reset, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") _, err = ctx.Transaction.Exec("INSERT INTO user (refid, firstname, lastname, email, initials, password, salt, reset, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
defer streamutil.Close(stmt) u.RefID, u.Firstname, u.Lastname, strings.ToLower(u.Email), u.Initials, u.Password, u.Salt, "", u.Created, u.Revised)
if err != nil {
err = errors.Wrap(err, "prepare user insert")
return
}
_, err = stmt.Exec(u.RefID, u.Firstname, u.Lastname, strings.ToLower(u.Email), u.Initials, u.Password, u.Salt, "", u.Created, u.Revised)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute user insert") err = errors.Wrap(err, "execute user insert")
return
} }
return return
@ -53,18 +46,10 @@ func (s Scope) Add(ctx domain.RequestContext, u user.User) (err error) {
// Get returns the user record for the given id. // Get returns the user record for the given id.
func (s Scope) Get(ctx domain.RequestContext, id string) (u user.User, err error) { func (s Scope) Get(ctx domain.RequestContext, id string) (u user.User, err error) {
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE refid=?") err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE refid=?", id)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to prepare select for user %s", id))
return
}
err = stmt.Get(&u, id)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select for user %s", id)) err = errors.Wrap(err, fmt.Sprintf("unable to execute select for user %s", id))
return
} }
return return
@ -74,18 +59,11 @@ func (s Scope) Get(ctx domain.RequestContext, id string) (u user.User, err error
func (s Scope) GetByDomain(ctx domain.RequestContext, domain, email string) (u user.User, err error) { func (s Scope) GetByDomain(ctx domain.RequestContext, domain, email string) (u user.User, err error) {
email = strings.TrimSpace(strings.ToLower(email)) email = strings.TrimSpace(strings.ToLower(email))
stmt, err := s.Runtime.Db.Preparex("SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.global, u.password, u.salt, u.reset, u.created, u.revised FROM user u, account a, organization o WHERE TRIM(LOWER(u.email))=? AND u.refid=a.userid AND a.orgid=o.refid AND TRIM(LOWER(o.domain))=?") err = s.Runtime.Db.Get(&u, "SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.global, u.password, u.salt, u.reset, u.created, u.revised FROM user u, account a, organization o WHERE TRIM(LOWER(u.email))=? AND u.refid=a.userid AND a.orgid=o.refid AND TRIM(LOWER(o.domain))=?",
defer streamutil.Close(stmt) email, domain)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("Unable to prepare GetUserByDomain %s %s", domain, email))
return
}
err = stmt.Get(&u, email, domain)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, fmt.Sprintf("Unable to execute GetUserByDomain %s %s", domain, email)) err = errors.Wrap(err, fmt.Sprintf("Unable to execute GetUserByDomain %s %s", domain, email))
return
} }
return return
@ -95,18 +73,10 @@ func (s Scope) GetByDomain(ctx domain.RequestContext, domain, email string) (u u
func (s Scope) GetByEmail(ctx domain.RequestContext, email string) (u user.User, err error) { func (s Scope) GetByEmail(ctx domain.RequestContext, email string) (u user.User, err error) {
email = strings.TrimSpace(strings.ToLower(email)) email = strings.TrimSpace(strings.ToLower(email))
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE TRIM(LOWER(email))=?") err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE TRIM(LOWER(email))=?", email)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare select user by email %s", email))
return
}
err = stmt.Get(&u, email)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, fmt.Sprintf("execute select user by email %s", email)) err = errors.Wrap(err, fmt.Sprintf("execute select user by email %s", email))
return
} }
return return
@ -114,18 +84,10 @@ func (s Scope) GetByEmail(ctx domain.RequestContext, email string) (u user.User,
// GetByToken returns a user record given a reset token value. // GetByToken returns a user record given a reset token value.
func (s Scope) GetByToken(ctx domain.RequestContext, token string) (u user.User, err error) { func (s Scope) GetByToken(ctx domain.RequestContext, token string) (u user.User, err error) {
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE reset=?") err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE reset=?", token)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare user select by token %s", token))
return
}
err = stmt.Get(&u, token)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute user select by token %s", token)) err = errors.Wrap(err, fmt.Sprintf("execute user select by token %s", token))
return
} }
return return
@ -135,18 +97,10 @@ func (s Scope) GetByToken(ctx domain.RequestContext, token string) (u user.User,
// This occurs when we you share a folder with a new user and they have to complete // This occurs when we you share a folder with a new user and they have to complete
// the onboarding process. // the onboarding process.
func (s Scope) GetBySerial(ctx domain.RequestContext, serial string) (u user.User, err error) { func (s Scope) GetBySerial(ctx domain.RequestContext, serial string) (u user.User, err error) {
stmt, err := s.Runtime.Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE salt=?") err = s.Runtime.Db.Get("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE salt=?", serial)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare user select by serial %s", serial))
return
}
err = stmt.Get(&u, serial)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute user select by serial %s", serial)) err = errors.Wrap(err, fmt.Sprintf("execute user select by serial %s", serial))
return
} }
return return
@ -156,14 +110,15 @@ func (s Scope) GetBySerial(ctx domain.RequestContext, serial string) (u user.Use
// identified in the Persister. // identified in the Persister.
func (s Scope) GetActiveUsersForOrganization(ctx domain.RequestContext) (u []user.User, err error) { func (s Scope) GetActiveUsersForOrganization(ctx domain.RequestContext) (u []user.User, err error) {
err = s.Runtime.Db.Select(&u, err = s.Runtime.Db.Select(&u,
`SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised `SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised,
FROM user u u.global, a.active, a.editor, a.admin, a.users as viewusers
WHERE u.refid IN (SELECT userid FROM account WHERE orgid = ? AND active=1) ORDER BY u.firstname,u.lastname`, FROM user u, account a
WHERE u.refid=a.userid AND a.orgid=? AND a.active=1
ORDER BY u.firstname,u.lastname`,
ctx.OrgID) ctx.OrgID)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("get active users by org %s", ctx.OrgID)) err = errors.Wrap(err, fmt.Sprintf("get active users by org %s", ctx.OrgID))
return
} }
return return
@ -173,114 +128,72 @@ func (s Scope) GetActiveUsersForOrganization(ctx domain.RequestContext) (u []use
// identified in the Persister. // identified in the Persister.
func (s Scope) GetUsersForOrganization(ctx domain.RequestContext) (u []user.User, err error) { func (s Scope) GetUsersForOrganization(ctx domain.RequestContext) (u []user.User, err error) {
err = s.Runtime.Db.Select(&u, err = s.Runtime.Db.Select(&u,
"SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, created, revised FROM user WHERE refid IN (SELECT userid FROM account where orgid = ?) ORDER BY firstname,lastname", ctx.OrgID) `SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised,
u.global, a.active, a.editor, a.admin, a.users as viewusers
FROM user u, account a
WHERE u.refid=a.userid AND a.orgid=?
ORDER BY u.firstname, u.lastname`, ctx.OrgID)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf(" get users for org %s", ctx.OrgID)) err = errors.Wrap(err, fmt.Sprintf(" get users for org %s", ctx.OrgID))
return
} }
return return
} }
// GetSpaceUsers returns a slice containing all user records for given folder. // GetSpaceUsers returns a slice containing all user records for given space.
func (s Scope) GetSpaceUsers(ctx domain.RequestContext, folderID string) (u []user.User, err error) { func (s Scope) GetSpaceUsers(ctx domain.RequestContext, spaceID string) (u []user.User, err error) {
err = s.Runtime.Db.Select(&u, err = s.Runtime.Db.Select(&u, `
`SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised, u.global,
a.active, a.users AS viewusers, a.editor, a.admin
FROM user u, account a FROM user u, account a
WHERE u.refid IN (SELECT userid from labelrole WHERE orgid=? AND labelid=?) WHERE a.orgid=? AND u.refid = a.userid AND a.active=1 AND u.refid IN (
AND a.orgid=? AND u.refid = a.userid AND a.active=1 SELECT whoid from permission WHERE orgid=? AND who='user' AND scope='object' AND location='space' AND refid=? UNION ALL
ORDER BY u.firstname, u.lastname`, SELECT r.userid from rolemember r LEFT JOIN permission p ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.scope='object' AND p.location='space' AND p.refid=?
ctx.OrgID, folderID, ctx.OrgID) )
ORDER BY u.firstname, u.lastname
`, ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, spaceID)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("get space users for org %s", ctx.OrgID)) err = errors.Wrap(err, fmt.Sprintf("get space users for org %s", ctx.OrgID))
return
} }
return return
} }
// GetVisibleUsers returns all users that can be "seen" by a user. // GetUsersForSpaces returns users with access to specified spaces.
// "Seen" means users who share at least one space in common. func (s Scope) GetUsersForSpaces(ctx domain.RequestContext, spaces []string) (u []user.User, err error) {
// Explicit access must be provided to a user in order to associate them query, args, err := sqlx.In(`
// as having access to a space. Simply marking a space as vieewable by "everyone" is not enough. SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised, u.global,
func (s Scope) GetVisibleUsers(ctx domain.RequestContext) (u []user.User, err error) { a.active, a.users AS viewusers, a.editor, a.admin
err = s.Runtime.Db.Select(&u, FROM user u, account a
`SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, created, revised WHERE a.orgid=? AND u.refid = a.userid AND a.active=1 AND u.refid IN (
FROM user SELECT whoid from permission WHERE orgid=? AND who='user' AND scope='object' AND location='space' AND refid IN(?) UNION ALL
WHERE SELECT r.userid from rolemember r LEFT JOIN permission p ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.scope='object' AND p.location='space' AND p.refid IN(?)
refid IN (SELECT userid FROM account WHERE orgid = ?)
AND refid IN
(SELECT userid FROM labelrole where userid != '' AND orgid=?
AND labelid IN (
SELECT refid FROM label WHERE orgid=? AND type=2 AND userid=?
UNION ALL
SELECT refid FROM label a WHERE orgid=? AND type=1 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
UNION ALL
SELECT refid FROM label a WHERE orgid=? AND type=3 AND refid IN (SELECT labelid FROM labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))
) )
GROUP BY userid) ORDER BY u.firstname, u.lastname
ORDER BY firstname, lastname`, `, ctx.OrgID, ctx.OrgID, spaces, ctx.OrgID, spaces)
ctx.OrgID,
ctx.OrgID, query = s.Runtime.Db.Rebind(query)
ctx.OrgID, err = s.Runtime.Db.Select(&u, query, args...)
ctx.UserID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("get visible users for org %s user %s", ctx.OrgID, ctx.UserID)) err = errors.Wrap(err, fmt.Sprintf("get users for spaces for user %s", ctx.UserID))
return
} }
return return
} }
/*
`SELECT
id, refid, firstname, lastname, email, initials, password, salt, reset, created, revised
FROM
user
WHERE
refid IN (SELECT userid FROM account where orgid = '4Tec34w8')
AND refid IN
(SELECT userid FROM labelrole where userid != '' AND orgid='4Tec34w8'
AND labelid IN (
SELECT refid FROM label WHERE orgid='4Tec34w8' AND type=2 AND userid='iJdf6qUW'
UNION ALL
SELECT refid FROM label a WHERE orgid='4Tec34w8' AND type=1 AND refid IN (SELECT labelid FROM labelrole WHERE orgid='4Tec34w8' AND userid='' AND (canedit=1 OR canview=1))
UNION ALL
SELECT refid FROM label a WHERE orgid='4Tec34w8' AND type=3 AND refid IN (SELECT labelid FROM labelrole WHERE orgid='4Tec34w8' AND userid='iJdf6qUW' AND (canedit=1 OR canview=1))
)
GROUP BY userid)
ORDER BY
firstname, lastname`
*/
// UpdateUser updates the user table using the given replacement user record. // UpdateUser updates the user table using the given replacement user record.
func (s Scope) UpdateUser(ctx domain.RequestContext, u user.User) (err error) { func (s Scope) UpdateUser(ctx domain.RequestContext, u user.User) (err error) {
u.Revised = time.Now().UTC() u.Revised = time.Now().UTC()
u.Email = strings.ToLower(u.Email) u.Email = strings.ToLower(u.Email)
stmt, err := ctx.Transaction.PrepareNamed( _, err = ctx.Transaction.NamedExec(
"UPDATE user SET firstname=:firstname, lastname=:lastname, email=:email, revised=:revised, initials=:initials WHERE refid=:refid") "UPDATE user SET firstname=:firstname, lastname=:lastname, email=:email, revised=:revised, initials=:initials WHERE refid=:refid", &u)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare user update %s", u.RefID))
return
}
_, err = stmt.Exec(&u)
if err != nil { if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute user update %s", u.RefID)) err = errors.Wrap(err, fmt.Sprintf("execute user update %s", u.RefID))
return
} }
return return
@ -288,18 +201,11 @@ func (s Scope) UpdateUser(ctx domain.RequestContext, u user.User) (err error) {
// UpdateUserPassword updates a user record with new password and salt values. // UpdateUserPassword updates a user record with new password and salt values.
func (s Scope) UpdateUserPassword(ctx domain.RequestContext, userID, salt, password string) (err error) { func (s Scope) UpdateUserPassword(ctx domain.RequestContext, userID, salt, password string) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE user SET salt=?, password=?, reset='' WHERE refid=?") _, err = ctx.Transaction.Exec("UPDATE user SET salt=?, password=?, reset='' WHERE refid=?",
defer streamutil.Close(stmt) salt, password, userID)
if err != nil {
err = errors.Wrap(err, "prepare user update")
return
}
_, err = stmt.Exec(salt, password, userID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute user update") err = errors.Wrap(err, "execute user update")
return
} }
return return
@ -307,19 +213,10 @@ func (s Scope) UpdateUserPassword(ctx domain.RequestContext, userID, salt, passw
// DeactiveUser deletes the account record for the given userID and persister.Context.OrgID. // DeactiveUser deletes the account record for the given userID and persister.Context.OrgID.
func (s Scope) DeactiveUser(ctx domain.RequestContext, userID string) (err error) { func (s Scope) DeactiveUser(ctx domain.RequestContext, userID string) (err error) {
stmt, err := ctx.Transaction.Preparex("DELETE FROM account WHERE userid=? and orgid=?") _, err = ctx.Transaction.Exec("DELETE FROM account WHERE userid=? and orgid=?", userID, ctx.OrgID)
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "prepare user deactivation")
return
}
_, err = stmt.Exec(userID, ctx.OrgID)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute user deactivation") err = errors.Wrap(err, "execute user deactivation")
return
} }
return return
@ -327,18 +224,10 @@ func (s Scope) DeactiveUser(ctx domain.RequestContext, userID string) (err error
// ForgotUserPassword sets the password to '' and the reset field to token, for a user identified by email. // ForgotUserPassword sets the password to '' and the reset field to token, for a user identified by email.
func (s Scope) ForgotUserPassword(ctx domain.RequestContext, email, token string) (err error) { func (s Scope) ForgotUserPassword(ctx domain.RequestContext, email, token string) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE user SET reset=?, password='' WHERE LOWER(email)=?") _, err = ctx.Transaction.Exec("UPDATE user SET reset=?, password='' WHERE LOWER(email)=?", token, strings.ToLower(email))
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "prepare password reset")
return
}
_, err = stmt.Exec(token, strings.ToLower(email))
if err != nil { if err != nil {
err = errors.Wrap(err, "execute password reset") err = errors.Wrap(err, "execute password reset")
return
} }
return return

View file

@ -39,12 +39,14 @@ func AttachUserAccounts(ctx domain.RequestContext, s domain.Store, orgID string,
u.Editor = false u.Editor = false
u.Admin = false u.Admin = false
u.Active = false u.Active = false
u.ViewUsers = false
for _, account := range u.Accounts { for _, account := range u.Accounts {
if account.OrgID == orgID { if account.OrgID == orgID {
u.Admin = account.Admin u.Admin = account.Admin
u.Editor = account.Editor u.Editor = account.Editor
u.Active = account.Active u.Active = account.Active
u.ViewUsers = account.Users
break break
} }
} }

View file

@ -20,10 +20,12 @@ import (
attachment "github.com/documize/community/domain/attachment/mysql" attachment "github.com/documize/community/domain/attachment/mysql"
audit "github.com/documize/community/domain/audit/mysql" audit "github.com/documize/community/domain/audit/mysql"
block "github.com/documize/community/domain/block/mysql" block "github.com/documize/community/domain/block/mysql"
category "github.com/documize/community/domain/category/mysql"
doc "github.com/documize/community/domain/document/mysql" doc "github.com/documize/community/domain/document/mysql"
link "github.com/documize/community/domain/link/mysql" link "github.com/documize/community/domain/link/mysql"
org "github.com/documize/community/domain/organization/mysql" org "github.com/documize/community/domain/organization/mysql"
page "github.com/documize/community/domain/page/mysql" page "github.com/documize/community/domain/page/mysql"
permission "github.com/documize/community/domain/permission/mysql"
pin "github.com/documize/community/domain/pin/mysql" pin "github.com/documize/community/domain/pin/mysql"
search "github.com/documize/community/domain/search/mysql" search "github.com/documize/community/domain/search/mysql"
setting "github.com/documize/community/domain/setting/mysql" setting "github.com/documize/community/domain/setting/mysql"
@ -38,11 +40,13 @@ func StoreMySQL(r *env.Runtime, s *domain.Store) {
s.Attachment = attachment.Scope{Runtime: r} s.Attachment = attachment.Scope{Runtime: r}
s.Audit = audit.Scope{Runtime: r} s.Audit = audit.Scope{Runtime: r}
s.Block = block.Scope{Runtime: r} s.Block = block.Scope{Runtime: r}
s.Category = category.Scope{Runtime: r}
s.Document = doc.Scope{Runtime: r} s.Document = doc.Scope{Runtime: r}
s.Link = link.Scope{Runtime: r} s.Link = link.Scope{Runtime: r}
s.Organization = org.Scope{Runtime: r} s.Organization = org.Scope{Runtime: r}
s.Page = page.Scope{Runtime: r} s.Page = page.Scope{Runtime: r}
s.Pin = pin.Scope{Runtime: r} s.Pin = pin.Scope{Runtime: r}
s.Permission = permission.Scope{Runtime: r}
s.Search = search.Scope{Runtime: r} s.Search = search.Scope{Runtime: r}
s.Setting = setting.Scope{Runtime: r} s.Setting = setting.Scope{Runtime: r}
s.Space = space.Scope{Runtime: r} s.Space = space.Scope{Runtime: r}

View file

@ -41,8 +41,8 @@ func main() {
// product details // product details
rt.Product = env.ProdInfo{} rt.Product = env.ProdInfo{}
rt.Product.Major = "1" rt.Product.Major = "1"
rt.Product.Minor = "53" rt.Product.Minor = "54"
rt.Product.Patch = "6" rt.Product.Patch = "0"
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch) rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
rt.Product.Edition = "Community" rt.Product.Edition = "Community"
rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition) rt.Product.Title = fmt.Sprintf("%s Edition", rt.Product.Edition)

File diff suppressed because one or more lines are too long

View file

@ -11,11 +11,12 @@
import Ember from 'ember'; import Ember from 'ember';
import AuthProvider from '../../mixins/auth'; import AuthProvider from '../../mixins/auth';
import DropdownMixin from '../../mixins/dropdown';
export default Ember.Component.extend(AuthProvider, { export default Ember.Component.extend(AuthProvider, DropdownMixin, {
editUser: null, editUser: null,
deleteUser: null, deleteUser: null,
drop: null, dropdown: null,
password: {}, password: {},
filter: '', filter: '',
filteredUsers: [], filteredUsers: [],
@ -23,20 +24,22 @@ export default Ember.Component.extend(AuthProvider, {
hasSelectedUsers: false, hasSelectedUsers: false,
didReceiveAttrs() { didReceiveAttrs() {
this.users.forEach(user => { this._super(...arguments);
let users = this.get('users');
users.forEach(user => {
user.set('me', user.get('id') === this.get('session.session.authenticated.user.id')); user.set('me', user.get('id') === this.get('session.session.authenticated.user.id'));
user.set('selected', false); user.set('selected', false);
}); });
this.set('filteredUsers', this.users); this.set('users', users);
this.set('filteredUsers', users);
}, },
willDestroyElement() { willDestroyElement() {
let drop = this.get('drop'); this._super(...arguments);
this.destroyDropdown();
if (is.not.null(drop)) {
drop.destroy();
}
}, },
onKeywordChange: function () { onKeywordChange: function () {
@ -90,11 +93,17 @@ export default Ember.Component.extend(AuthProvider, {
this.attrs.onSave(user); this.attrs.onSave(user);
}, },
toggleUsers(id) {
let user = this.users.findBy("id", id);
user.set('viewUsers', !user.get('viewUsers'));
this.attrs.onSave(user);
},
edit(id) { edit(id) {
let self = this; let self = this;
let user = this.users.findBy("id", id); let user = this.users.findBy("id", id);
let userCopy = user.getProperties('id', 'created', 'revised', 'firstname', 'lastname', 'email', 'initials', 'active', 'editor', 'admin', 'accounts'); let userCopy = user.getProperties('id', 'created', 'revised', 'firstname', 'lastname', 'email', 'initials', 'active', 'editor', 'admin', 'viewUsers', 'accounts');
this.set('editUser', userCopy); this.set('editUser', userCopy);
this.set('password', { this.set('password', {
password: "", password: "",
@ -103,6 +112,8 @@ export default Ember.Component.extend(AuthProvider, {
$(".edit-user-dialog").css("display", "block"); $(".edit-user-dialog").css("display", "block");
$("input").removeClass("error"); $("input").removeClass("error");
this.closeDropdown();
let drop = new Drop({ let drop = new Drop({
target: $(".edit-button-" + id)[0], target: $(".edit-button-" + id)[0],
content: $(".edit-user-dialog")[0], content: $(".edit-user-dialog")[0],
@ -116,7 +127,7 @@ export default Ember.Component.extend(AuthProvider, {
remove: false remove: false
}); });
self.set('drop', drop); self.set('dropdown', drop);
drop.on('open', function () { drop.on('open', function () {
self.$("#edit-firstname").focus(); self.$("#edit-firstname").focus();
@ -128,6 +139,8 @@ export default Ember.Component.extend(AuthProvider, {
this.set('deleteUser', user); this.set('deleteUser', user);
$(".delete-user-dialog").css("display", "block"); $(".delete-user-dialog").css("display", "block");
this.closeDropdown();
let drop = new Drop({ let drop = new Drop({
target: $(".delete-button-" + id)[0], target: $(".delete-button-" + id)[0],
content: $(".delete-user-dialog")[0], content: $(".delete-user-dialog")[0],
@ -141,12 +154,11 @@ export default Ember.Component.extend(AuthProvider, {
remove: false remove: false
}); });
this.set('drop', drop); this.set('dropdown', drop);
}, },
cancel() { cancel() {
let drop = this.get('drop'); this.closeDropdown();
drop.close();
}, },
save() { save() {
@ -166,8 +178,7 @@ export default Ember.Component.extend(AuthProvider, {
return; return;
} }
let drop = this.get('drop'); this.closeDropdown();
drop.close();
this.attrs.onSave(user); this.attrs.onSave(user);

View file

@ -17,7 +17,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
documentService: Ember.inject.service('document'), documentService: Ember.inject.service('document'),
appMeta: Ember.inject.service(), appMeta: Ember.inject.service(),
drop: null, drop: null,
emptyState: Ember.computed.empty('files'), hasAttachments: Ember.computed.notEmpty('files'),
deleteAttachment: { deleteAttachment: {
id: "", id: "",
name: "", name: "",
@ -32,7 +32,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);
if (!this.get('isEditor')) { if (!this.get('permissions.documentEdit')) {
return; return;
} }
@ -104,7 +104,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
target: $(".delete-attachment-" + id)[0], target: $(".delete-attachment-" + id)[0],
content: $(".delete-attachment-dialog")[0], content: $(".delete-attachment-dialog")[0],
classes: 'drop-theme-basic', classes: 'drop-theme-basic',
position: "bottom left", position: "bottom right",
openOn: "always", openOn: "always",
tetherOptions: { tetherOptions: {
offset: "5px 0", offset: "5px 0",

View file

@ -10,9 +10,9 @@
// https://documize.com // https://documize.com
import Ember from 'ember'; import Ember from 'ember';
import tocUtil from '../../utils/toc';
import NotifierMixin from '../../mixins/notifier'; import NotifierMixin from '../../mixins/notifier';
import TooltipMixin from '../../mixins/tooltip'; import TooltipMixin from '../../mixins/tooltip';
import tocUtil from '../../utils/toc';
export default Ember.Component.extend(NotifierMixin, TooltipMixin, { export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
document: {}, document: {},
@ -30,7 +30,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
return this.get('pages.length') === 0; return this.get('pages.length') === 0;
}), }),
didReceiveAttrs: function () { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
this.set('showToc', is.not.undefined(this.get('pages')) && this.get('pages').get('length') > 0); this.set('showToc', is.not.undefined(this.get('pages')) && this.get('pages').get('length') > 0);
@ -40,7 +40,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
} }
}, },
didRender: function () { didRender() {
this._super(...arguments); this._super(...arguments);
if (this.session.authenticated) { if (this.session.authenticated) {
@ -77,7 +77,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
let page = _.findWhere(toc, { id: pageId }); let page = _.findWhere(toc, { id: pageId });
let state = tocUtil.getState(toc, page); let state = tocUtil.getState(toc, page);
if (!this.get('isEditor') || is.empty(pageId)) { if (!this.get('permissions.documentEdit') || is.empty(pageId)) {
state.actionablePage = false; state.actionablePage = false;
state.upDisabled = state.downDisabled = state.indentDisabled = state.outdentDisabled = true; state.upDisabled = state.downDisabled = state.indentDisabled = state.outdentDisabled = true;
} }

View file

@ -33,7 +33,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
} }
this.set('meta', meta); this.set('meta', meta);
if (this.get('toEdit') === this.get('page.id') && this.get('isEditor')) { if (this.get('toEdit') === this.get('page.id') && this.get('permissions.documentEdit')) {
this.send('onEdit'); this.send('onEdit');
} }
}); });

View file

@ -0,0 +1,50 @@
// 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
import Ember from 'ember';
import NotifierMixin from '../../mixins/notifier';
import TooltipMixin from '../../mixins/tooltip';
export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
tab: 'index',
didRender() {
this._super(...arguments);
if (this.get('permissions.documentEdit')) {
this.addTooltip(document.getElementById("document-index-button"));
this.addTooltip(document.getElementById("document-activity-button"));
}
},
willDestroyElement() {
this._super(...arguments);
this.destroyTooltips();
},
actions: {
onTabSwitch(tab) {
this.set('tab', tab);
},
onPageSequenceChange(changes) {
this.attrs.onPageSequenceChange(changes);
},
onPageLevelChange(changes) {
this.attrs.onPageLevelChange(changes);
},
onGotoPage(id) {
this.attrs.onGotoPage(id);
}
}
});

View file

@ -31,14 +31,9 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
name: "", name: "",
description: "" description: ""
}, },
tab: '',
init() { init() {
this._super(...arguments); this._super(...arguments);
if (is.empty(this.get('tab')) || is.undefined(this.get('tab'))) {
this.set('tab', 'index');
}
}, },
didReceiveAttrs() { didReceiveAttrs() {
@ -49,20 +44,18 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
this.set('pinState.pinId', this.get('pinned').isDocumentPinned(this.get('document.id'))); this.set('pinState.pinId', this.get('pinned').isDocumentPinned(this.get('document.id')));
this.set('pinState.isPinned', this.get('pinState.pinId') !== ''); this.set('pinState.isPinned', this.get('pinState.pinId') !== '');
this.set('pinState.newName', this.get('document.name').substring(0,3).toUpperCase()); this.set('pinState.newName', this.get('document.name'));
},
didRender() {
this.destroyTooltips();
if (this.get('permissions.documentEdit')) {
this.addTooltip(document.getElementById("document-activity-button"));
}
}, },
actions: { actions: {
onChangeTab(tab) {
this.set('tab', tab);
},
onTagChange(tags) {
let doc = this.get('document');
doc.set('tags', tags);
this.get('documentService').save(doc);
},
onMenuOpen() { onMenuOpen() {
this.set('menuOpen', !this.get('menuOpen')); this.set('menuOpen', !this.get('menuOpen'));
}, },
@ -140,7 +133,10 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
onLayoutChange(layout) { onLayoutChange(layout) {
let doc = this.get('document'); let doc = this.get('document');
doc.set('layout', layout); doc.set('layout', layout);
if (this.get('permissions.documentEdit')) {
this.get('documentService').save(doc); this.get('documentService').save(doc);
}
return true; return true;
} }

View file

@ -72,6 +72,13 @@ export default Ember.Component.extend(TooltipMixin, {
return `move-dialog-${id}`; return `move-dialog-${id}`;
}), }),
hasMenuPermissions: computed('permissions', function() {
let permissions = this.get('permissions');
return permissions.get('documentDelete') || permissions.get('documentCopy') ||
permissions.get('documentMove') || permissions.get('documentTemplate');
}),
didRender() { didRender() {
$("#" + this.get('blockTitleId')).removeClass('error'); $("#" + this.get('blockTitleId')).removeClass('error');
$("#" + this.get('blockExcerptId')).removeClass('error'); $("#" + this.get('blockExcerptId')).removeClass('error');

View file

@ -0,0 +1,97 @@
// 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
import Ember from 'ember';
import TooltipMixin from '../../mixins/tooltip';
import NotifierMixin from '../../mixins/notifier';
export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
documentService: Ember.inject.service('document'),
categoryService: Ember.inject.service('category'),
sessionService: Ember.inject.service('session'),
categories: [],
hasCategories: Ember.computed('categories', function() {
return this.get('categories').length > 0;
}),
canSelectCategory: Ember.computed('categories', function() {
return (this.get('categories').length > 0 && this.get('permissions.documentEdit'));
}),
canAddCategory: Ember.computed('categories', function() {
return this.get('permissions.spaceOwner') || this.get('permissions.spaceManage');
}),
init() {
this._super(...arguments);
},
didReceiveAttrs() {
this._super(...arguments);
this.load();
},
load() {
this.get('categoryService').getUserVisible(this.get('folder.id')).then((categories) => {
this.set('categories', categories);
this.get('categoryService').getDocumentCategories(this.get('document.id')).then((selected) => {
this.set('selectedCategories', selected);
selected.forEach((s) => {
let cats = this.set('categories', categories);
let cat = categories.findBy('id', s.id);
if (is.not.undefined(cat)) {
cat.set('selected', true);
this.set('categories', cats);
}
});
});
});
},
actions: {
onSave() {
let docId = this.get('document.id');
let folderId = this.get('folder.id');
let link = this.get('categories').filterBy('selected', true);
let unlink = this.get('categories').filterBy('selected', false);
let toLink = [];
let toUnlink = [];
// prepare links associated with document
link.forEach((l) => {
let t = {
folderId: folderId,
documentId: docId,
categoryId: l.get('id')
};
toLink.push(t);
});
// prepare links no longer associated with document
unlink.forEach((l) => {
let t = {
folderId: folderId,
documentId: docId,
categoryId: l.get('id')
};
toUnlink.pushObject(t);
});
this.get('categoryService').setCategoryMembership(toUnlink, 'unlink').then(() => {
this.get('categoryService').setCategoryMembership(toLink, 'link').then(() => {
this.load();
});
});
return true;
}
}
});

View file

@ -73,6 +73,13 @@ export default Ember.Component.extend(TooltipMixin, {
return `move-dialog-${id}`; return `move-dialog-${id}`;
}), }),
hasMenuPermissions: computed('permissions', function() {
let permissions = this.get('permissions');
return permissions.get('documentDelete') || permissions.get('documentCopy') ||
permissions.get('documentMove') || permissions.get('documentTemplate');
}),
didRender() { didRender() {
$("#" + this.get('blockTitleId')).removeClass('error'); $("#" + this.get('blockTitleId')).removeClass('error');
$("#" + this.get('blockExcerptId')).removeClass('error'); $("#" + this.get('blockExcerptId')).removeClass('error');

View file

@ -14,10 +14,12 @@ import Ember from 'ember';
export default Ember.Component.extend({ export default Ember.Component.extend({
documentTags: [], documentTags: [],
tagz: [], tagz: [],
isEditor: false,
newTag: "", newTag: "",
maxTags: 3, maxTags: 3,
canAdd: false, canAdd: false,
emptyState: Ember.computed('tagz', function() {
return (this.get('tagz').length === 0 && !this.get('permissions.documentEdit'));
}),
init() { init() {
this._super(...arguments); this._super(...arguments);
@ -33,18 +35,17 @@ export default Ember.Component.extend({
} }
this.set('tagz', tagz); this.set('tagz', tagz);
this.set('canAdd', this.get('isEditor') && this.get('tagz').get('length') < 3); this.set('canAdd', this.get('permissions.documentEdit') && this.get('tagz').get('length') < 3);
}, },
didUpdateAttrs() { didUpdateAttrs() {
this.set('canAdd', this.get('isEditor') && this.get('tagz').get('length') < 3); this._super(...arguments);
this.set('canAdd', this.get('permissions.documentEdit') && this.get('tagz').get('length') < 3);
}, },
didInsertElement() {
},
willDestroyElement() { willDestroyElement() {
this._super(...arguments);
$("#add-tag-field").off("keydown"); $("#add-tag-field").off("keydown");
}, },

View file

@ -0,0 +1,210 @@
// 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
import Ember from 'ember';
import NotifierMixin from '../../mixins/notifier';
import TooltipMixin from '../../mixins/tooltip';
import DropdownMixin from '../../mixins/dropdown';
const {
inject: { service }
} = Ember;
export default Ember.Component.extend(NotifierMixin, TooltipMixin, DropdownMixin, {
userService: service('user'),
categoryService: service('category'),
appMeta: service(),
store: service(),
newCategory: '',
dropdown: null,
users: [],
didReceiveAttrs() {
this._super(...arguments);
this.load();
},
willDestroyElement() {
this._super(...arguments);
this.destroyDropdown();
},
load() {
// get categories
this.get('categoryService').getAll(this.get('folder.id')).then((c) => {
this.set('category', c);
// get summary of documents and users for each category in space
this.get('categoryService').getSummary(this.get('folder.id')).then((s) => {
c.forEach((cat) => {
let docs = _.findWhere(s, {categoryId: cat.get('id'), type: 'documents'});
let docCount = is.not.undefined(docs) ? docs.count : 0;
let users = _.findWhere(s, {categoryId: cat.get('id'), type: 'users'});
let userCount = is.not.undefined(users) ? users.count : 0;
cat.set('documents', docCount);
cat.set('users', userCount);
});
});
// get users that this space admin user can see
this.get('userService').getSpaceUsers(this.get('folder.id')).then((users) => {
// set up Everyone user
let u = {
orgId: this.get('folder.orgId'),
folderId: this.get('folder.id'),
userId: '',
firstname: 'Everyone',
lastname: '',
};
let data = this.get('store').normalize('user', u)
users.pushObject(this.get('store').push(data));
users = users.sortBy('firstname', 'lastname');
this.set('users', users);
});
});
},
setEdit(id, val) {
let cats = this.get('category');
let cat = cats.findBy('id', id);
if (is.not.undefined(cat)) {
cat.set('editMode', val);
}
return cat;
},
actions: {
onAdd() {
let cat = this.get('newCategory');
if (cat === '') {
$('#new-category-name').addClass('error').focus();
return;
}
$('#new-category-name').removeClass('error').focus();
this.set('newCategory', '');
let c = {
category: cat,
folderId: this.get('folder.id')
};
this.get('categoryService').add(c).then(() => {
this.load();
});
},
onDelete(id) {
this.get('categoryService').delete(id).then(() => {
this.load();
});
},
onEdit(id) {
this.setEdit(id, true);
},
onEditCancel(id) {
this.setEdit(id, false);
this.load();
},
onSave(id) {
let cat = this.setEdit(id, true);
if (cat.get('category') === '') {
$('#edit-category-' + cat.get('id')).addClass('error').focus();
return;
}
cat = this.setEdit(id, false);
$('#edit-category-' + cat.get('id')).removeClass('error');
this.get('categoryService').save(cat).then(() => {
this.load();
});
},
onShowAccessPicker(catId) {
this.closeDropdown();
let users = this.get('users');
let category = this.get('category').findBy('id', catId);
this.get('categoryService').getPermissions(category.get('id')).then((viewers) => {
// mark those users as selected that have already been given permission
// to see the current category;
users.forEach((user) => {
let userId = user.get('id');
let selected = viewers.isAny('whoId', userId);
user.set('selected', selected);
});
this.set('categoryUsers', users);
this.set('currentCategory', category);
$(".category-access-dialog").css("display", "block");
let drop = new Drop({
target: $("#category-access-button-" + catId)[0],
content: $(".category-access-dialog")[0],
classes: 'drop-theme-basic',
position: "bottom right",
openOn: "always",
tetherOptions: {
offset: "5px 0",
targetOffset: "10px 0"
},
remove: false
});
this.set('dropdown', drop);
});
},
onGrantCancel() {
this.closeDropdown();
},
onGrantAccess() {
let folder = this.get('folder');
let category = this.get('currentCategory');
let users = this.get('categoryUsers').filterBy('selected', true);
let viewers = [];
users.forEach((user) => {
let userId = user.get('id');
let v = {
orgId: this.get('folder.orgId'),
folderId: this.get('folder.id'),
categoryId: category.get('id'),
userId: userId
};
viewers.push(v);
});
this.get('categoryService').setViewers(folder.get('id'), category.get('id'), viewers).then(() => {
this.load();
});
this.closeDropdown();
}
}
});

View file

@ -12,45 +12,6 @@
import Ember from 'ember'; import Ember from 'ember';
export default Ember.Component.extend({ export default Ember.Component.extend({
folderService: Ember.inject.service('folder'),
moveTarget: null,
emptyState: Ember.computed('documents', function() {
return this.get('documents.length') === 0;
}),
didReceiveAttrs() {
this._super(...arguments);
this.set('canCreate', this.get('folderService').get('canEditCurrentFolder'));
this.set('deleteTargets', this.get('folders').rejectBy('id', this.get('folder.id')));
},
didUpdateAttrs() {
this._super(...arguments);
this.setupAddWizard();
},
didInsertElement() {
this._super(...arguments);
this.setupAddWizard();
},
setupAddWizard() {
Ember.run.schedule('afterRender', () => {
$('.start-document:not(.start-document-empty-state)').off('.hoverIntent');
$('.start-document:not(.start-document-empty-state)').hoverIntent({interval: 100, over: function() {
// in
$(this).find('.start-button').velocity("transition.slideDownIn", {duration: 300});
}, out: function() {
// out
$(this).find('.start-button').velocity("transition.slideUpOut", {duration: 300});
} });
});
},
actions: { actions: {
selectDocument(documentId) { selectDocument(documentId) {
let doc = this.get('documents').findBy('id', documentId); let doc = this.get('documents').findBy('id', documentId);
@ -65,39 +26,6 @@ export default Ember.Component.extend({
} }
this.set('selectedDocuments', list); this.set('selectedDocuments', list);
},
onDelete() {
this.get("onDeleteSpace")();
},
onImport() {
this.get('onImport')();
},
onShowDocumentWizard(docId) {
if ($("#new-document-wizard").is(':visible') && this.get('docId') === docId) {
this.send('onHideDocumentWizard');
return;
}
this.set('docId', docId);
if (docId === '') {
$("#new-document-wizard").insertAfter('#wizard-placeholder');
} else {
$("#new-document-wizard").insertAfter(`#document-${docId}`);
}
$("#new-document-wizard").velocity("transition.slideDownIn", { duration: 300, complete:
function() {
$("#new-document-name").focus();
}});
},
onHideDocumentWizard() {
$("#new-document-wizard").insertAfter('#wizard-placeholder');
$("#new-document-wizard").velocity("transition.slideUpOut", { duration: 300 });
} }
} }
}); });

View file

@ -1,91 +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
import Ember from 'ember';
import NotifierMixin from '../../mixins/notifier';
import TooltipMixin from '../../mixins/tooltip';
import AuthMixin from '../../mixins/auth';
const {
computed
} = Ember;
export default Ember.Component.extend(NotifierMixin, TooltipMixin, AuthMixin, {
folderService: Ember.inject.service('folder'),
session: Ember.inject.service(),
appMeta: Ember.inject.service(),
showToolbar: false,
folder: {},
busy: false,
isFolderOwner: computed.equal('folder.userId', 'session.user.id'),
moveFolderId: "",
drop: null,
didReceiveAttrs() {
this.set('isFolderOwner', this.get('folder.userId') === this.get("session.user.id"));
let show = this.get('session.authenticated') || this.get('isFolderOwner') || this.get('hasSelectedDocuments') || this.get('folderService').get('canEditCurrentFolder');
this.set('showToolbar', show);
let targets = _.reject(this.get('folders'), {
id: this.get('folder').get('id')
});
this.set('movedFolderOptions', targets);
},
didRender() {
if (this.get('hasSelectedDocuments')) {
this.addTooltip(document.getElementById("move-documents-button"));
this.addTooltip(document.getElementById("delete-documents-button"));
} else {
if (this.get('isFolderOwner')) {
this.addTooltip(document.getElementById("folder-share-button"));
this.addTooltip(document.getElementById("folder-settings-button"));
}
}
},
willDestroyElement() {
if (is.not.null(this.get('drop'))) {
this.get('drop').destroy();
this.set('drop', null);
}
this.destroyTooltips();
},
actions: {
deleteDocuments() {
this.attrs.onDeleteDocument();
},
setMoveFolder(folderId) {
this.set('moveFolderId', folderId);
let folders = this.get('folders');
folders.forEach(folder => {
folder.set('selected', folder.id === folderId);
});
},
moveDocuments() {
if (this.get("moveFolderId") === "") {
return false;
}
this.attrs.onMoveDocument(this.get('moveFolderId'));
return true;
}
}
});

View file

@ -23,7 +23,7 @@ export default Ember.Component.extend(NotifierMixin, {
inviteMessage: '', inviteMessage: '',
getDefaultInvitationMessage() { getDefaultInvitationMessage() {
return "Hey there, I am sharing the " + this.folder.get('name') + " space (in " + this.get("appMeta.title") + ") with you so we can both access the same documents."; return "Hey there, I am sharing the " + this.folder.get('name') + " space (in " + this.get("appMeta.title") + ") with you so we can both collaborate on documents.";
}, },
willRender() { willRender() {
@ -67,7 +67,8 @@ export default Ember.Component.extend(NotifierMixin, {
this.set('inviteEmail', ''); this.set('inviteEmail', '');
this.get('folderService').share(this.folder.get('id'), result).then(() => { this.get('folderService').share(this.folder.get('id'), result).then(() => {
this.showNotification('Shared'); this.showNotification('Invietd co-workers');
$('#inviteEmail').removeClass('error');
}); });
} }
} }

View file

@ -0,0 +1,121 @@
// 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
import Ember from 'ember';
import NotifierMixin from '../../mixins/notifier';
const {
inject: { service }
} = Ember;
export default Ember.Component.extend(NotifierMixin, {
folderService: service('folder'),
userService: service('user'),
appMeta: service(),
store: service(),
didReceiveAttrs() {
this.get('userService').getSpaceUsers(this.get('folder.id')).then((users) => {
this.set('users', users);
// set up users
let folderPermissions = [];
users.forEach((user) => {
let u = {
orgId: this.get('folder.orgId'),
folderId: this.get('folder.id'),
userId: user.get('id'),
fullname: user.get('fullname'),
spaceView: false,
spaceManage: false,
spaceOwner: false,
documentAdd: false,
documentEdit: false,
documentDelete: false,
documentMove: false,
documentCopy: false,
documentTemplate: false
};
let data = this.get('store').normalize('space-permission', u)
folderPermissions.pushObject(this.get('store').push(data));
});
// set up Everyone user
let u = {
orgId: this.get('folder.orgId'),
folderId: this.get('folder.id'),
userId: '0',
fullname: ' Everyone',
spaceView: false,
spaceManage: false,
spaceOwner: false,
documentAdd: false,
documentEdit: false,
documentDelete: false,
documentMove: false,
documentCopy: false,
documentTemplate: false
};
let data = this.get('store').normalize('space-permission', u)
folderPermissions.pushObject(this.get('store').push(data));
this.get('folderService').getPermissions(this.get('folder.id')).then((permissions) => {
permissions.forEach((permission, index) => { // eslint-disable-line no-unused-vars
let record = folderPermissions.findBy('userId', permission.get('userId'));
if (is.not.undefined(record)) {
record = Ember.setProperties(record, permission);
}
});
this.set('permissions', folderPermissions.sortBy('fullname'));
});
});
},
getDefaultInvitationMessage() {
return "Hey there, I am sharing the " + this.get('folder.name') + " space (in " + this.get("appMeta.title") + ") with you so we can both collaborate on documents.";
},
actions: {
setPermissions() {
let message = this.getDefaultInvitationMessage();
let permissions = this.get('permissions');
let folder = this.get('folder');
let payload = { Message: message, Permissions: permissions };
let hasEveryone = _.find(permissions, function (permission) {
return permission.get('userId') === "0" &&
(permission.get('spaceView') || permission.get('documentAdd') || permission.get('documentEdit') || permission.get('documentDelete') ||
permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate'));
});
this.get('folderService').savePermissions(folder.get('id'), payload).then(() => {
this.showNotification('Saved permissions');
});
if (is.not.undefined(hasEveryone)) {
folder.markAsPublic();
this.showNotification('Marked space as public');
} else {
if (permissions.length > 1) {
folder.markAsRestricted();
this.showNotification('Marked space as protected');
} else {
folder.markAsPrivate();
this.showNotification('Marked space as private');
}
}
}
}
});

View file

@ -32,30 +32,25 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, AuthMixin, {
didReceiveAttrs() { didReceiveAttrs() {
let folders = this.get('folders'); let folders = this.get('folders');
let publicFolders = [];
// clear out state let protectedFolders = [];
this.set('publicFolders', []); let privateFolders = [];
this.set('protectedFolders', []);
this.set('privateFolders', []);
_.each(folders, folder => { _.each(folders, folder => {
if (folder.get('folderType') === constants.FolderType.Public) { if (folder.get('folderType') === constants.FolderType.Public) {
let folders = this.get('publicFolders'); publicFolders.pushObject(folder);
folders.pushObject(folder);
this.set('publicFolders', folders);
} }
if (folder.get('folderType') === constants.FolderType.Private) { if (folder.get('folderType') === constants.FolderType.Private) {
let folders = this.get('privateFolders'); protectedFolders.pushObject(folder);
folders.pushObject(folder);
this.set('privateFolders', folders);
} }
if (folder.get('folderType') === constants.FolderType.Protected) { if (folder.get('folderType') === constants.FolderType.Protected) {
let folders = this.get('protectedFolders'); privateFolders.pushObject(folder);
folders.pushObject(folder);
this.set('protectedFolders', folders);
} }
}); });
this.set('publicFolders', publicFolders);
this.set('protectedFolders', protectedFolders);
this.set('privateFolders', privateFolders);
this.set('hasPublicFolders', this.get('publicFolders.length') > 0); this.set('hasPublicFolders', this.get('publicFolders.length') > 0);
this.set('hasPrivateFolders', this.get('privateFolders.length') > 0); this.set('hasPrivateFolders', this.get('privateFolders.length') > 0);
this.set('hasProtectedFolders', this.get('protectedFolders.length') > 0); this.set('hasProtectedFolders', this.get('protectedFolders.length') > 0);

View file

@ -1,135 +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
import Ember from 'ember';
import NotifierMixin from '../../mixins/notifier';
const {
inject: { service }
} = Ember;
export default Ember.Component.extend(NotifierMixin, {
folderService: service('folder'),
userService: service('user'),
appMeta: service(),
store: service(),
didReceiveAttrs() {
this.get('userService').getAll().then((users) => {
this.set('users', users);
var folderPermissions = [];
users.forEach((user) => {
let isActive = user.get('active');
let u = {
userId: user.get('id'),
fullname: user.get('fullname'),
orgId: this.get('folder.orgId'),
folderId: this.get('folder.id'),
canEdit: false,
canView: false,
canViewPrevious: false
};
if (isActive) {
folderPermissions.pushObject(u);
}
});
var u = {
userId: "",
fullname: " Everyone",
orgId: this.get('folder.orgId'),
folderId: this.get('folder.id'),
canEdit: false,
canView: false
};
folderPermissions.pushObject(u);
this.get('folderService').getPermissions(this.get('folder.id')).then((permissions) => {
permissions.forEach((permission, index) => { // eslint-disable-line no-unused-vars
var folderPermission = folderPermissions.findBy('userId', permission.get('userId'));
if (is.not.undefined(folderPermission)) {
Ember.setProperties(folderPermission, {
orgId: permission.get('orgId'),
folderId: permission.get('folderId'),
canEdit: permission.get('canEdit'),
canView: permission.get('canView'),
canViewPrevious: permission.get('canView')
});
}
});
folderPermissions.map((permission) => {
let data = this.get('store').normalize('folder-permission', permission);
return this.get('store').push(data);
});
this.set('permissions', folderPermissions.sortBy('fullname'));
});
});
},
getDefaultInvitationMessage() {
return "Hey there, I am sharing the " + this.get('folder.name') + " space (in " + this.get("appMeta.title") + ") with you so we can both access the same documents.";
},
actions: {
setPermissions() {
let message = this.getDefaultInvitationMessage();
let folder = this.get('folder');
let permissions = this.get('permissions');
this.get('permissions').forEach((permission, index) => { // eslint-disable-line no-unused-vars
Ember.set(permission, 'canView', $("#canView-" + permission.userId).prop('checked'));
Ember.set(permission, 'canEdit', $("#canEdit-" + permission.userId).prop('checked'));
});
var data = permissions.map((obj) => {
let permission = {
'orgId': obj.orgId,
'folderId': obj.folderId,
'userId': obj.userId,
'canEdit': obj.canEdit,
'canView': obj.canView
};
return permission;
});
var payload = { Message: message, Roles: data };
this.get('folderService').savePermissions(folder.get('id'), payload).then(() => {
});
var hasEveryone = _.find(data, function (permission) {
return permission.userId === "" && (permission.canView || permission.canEdit);
});
if (is.not.undefined(hasEveryone)) {
folder.markAsPublic();
} else {
if (data.length > 1) {
folder.markAsRestricted();
} else {
folder.markAsPrivate();
}
}
this.get('folderService').save(folder).then(function () {
// window.location.href = "/folder/" + folder.get('id') + "/" + folder.get('slug');
});
}
}
});

View file

@ -14,28 +14,7 @@ import TooltipMixin from '../../mixins/tooltip';
import NotifierMixin from '../../mixins/notifier'; import NotifierMixin from '../../mixins/notifier';
import AuthMixin from '../../mixins/auth'; import AuthMixin from '../../mixins/auth';
const {
inject: { service }
} = Ember;
export default Ember.Component.extend(TooltipMixin, NotifierMixin, AuthMixin, { export default Ember.Component.extend(TooltipMixin, NotifierMixin, AuthMixin, {
folderService: service('folder'),
templateService: service('template'),
appMeta: service(),
pinned: service(),
publicFolders: [],
protectedFolders: [],
privateFolders: [],
hasPublicFolders: false,
hasProtectedFolders: false,
hasPrivateFolders: false,
newFolder: "",
menuOpen: false,
pinState : {
isPinned: false,
pinId: '',
newName: '',
},
tab: '', tab: '',
init() { init() {
@ -46,15 +25,6 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, AuthMixin, {
} }
}, },
didReceiveAttrs() {
if (!this.get('noFolder')) {
let folder = this.get('folder');
this.set('pinState.pinId', this.get('pinned').isSpacePinned(folder.get('id')));
this.set('pinState.isPinned', this.get('pinState.pinId') !== '');
this.set('pinState.newName', folder.get('name').substring(0,3).toUpperCase());
}
},
actions: { actions: {
onAddSpace(m) { onAddSpace(m) {
this.attrs.onAddSpace(m); this.attrs.onAddSpace(m);
@ -64,38 +34,5 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, AuthMixin, {
onChangeTab(tab) { onChangeTab(tab) {
this.set('tab', tab); this.set('tab', tab);
}, },
onMenuOpen() {
this.set('menuOpen', !this.get('menuOpen'));
},
onUnpin() {
this.get('pinned').unpinItem(this.get('pinState.pinId')).then(() => {
this.set('pinState.isPinned', false);
this.set('pinState.pinId', '');
this.eventBus.publish('pinChange');
});
},
onPin() {
let pin = {
pin: this.get('pinState.newName'),
documentId: '',
folderId: this.get('folder.id')
};
if (is.empty(pin.pin)) {
$('#pin-space-name').addClass('error').focus();
return false;
}
this.get('pinned').pinItem(pin).then((pin) => {
this.set('pinState.isPinned', true);
this.set('pinState.pinId', pin.get('id'));
this.eventBus.publish('pinChange');
});
return true;
},
} }
}); });

View file

@ -22,7 +22,6 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
folderName: '', folderName: '',
hasNameError: computed.empty('folderName'), hasNameError: computed.empty('folderName'),
editMode: false, editMode: false,
isEditor: false,
keyUp(e) { keyUp(e) {
if (e.keyCode === 27) { // escape key if (e.keyCode === 27) { // escape key

View file

@ -0,0 +1,180 @@
// 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
import Ember from 'ember';
import NotifierMixin from '../../mixins/notifier';
import TooltipMixin from '../../mixins/tooltip';
import AuthMixin from '../../mixins/auth';
const {
inject: { service }
} = Ember;
export default Ember.Component.extend(NotifierMixin, TooltipMixin, AuthMixin, {
folderService: service('folder'),
session: service(),
appMeta: service(),
pinned: service(),
showToolbar: false,
folder: {},
busy: false,
moveFolderId: "",
drop: null,
pinState : {
isPinned: false,
pinId: '',
newName: ''
},
deleteSpaceName: '',
didReceiveAttrs() {
this._super(...arguments);
let folder = this.get('folder');
let targets = _.reject(this.get('folders'), {id: folder.get('id')});
this.get('pinned').isSpacePinned(folder.get('id')).then((pinId) => {
this.set('pinState.pinId', pinId);
this.set('pinState.isPinned', pinId !== '');
this.set('pinState.newName', folder.get('name'));
});
this.set('movedFolderOptions', targets);
},
didRender() {
this._super(...arguments);
this.renderTooltips();
},
renderTooltips() {
this.destroyTooltips();
if (this.get('hasSelectedDocuments')) {
if (this.get('permissions.documentMove')) {
this.addTooltip(document.getElementById("move-documents-button"));
}
if (this.get('permissions.documentDelete')) {
this.addTooltip(document.getElementById("delete-documents-button"));
}
} else {
if (this.get('permissions.spaceOwner')) {
this.addTooltip(document.getElementById("space-delete-button"));
}
if (this.get('permissions.spaceManage')) {
this.addTooltip(document.getElementById("space-settings-button"));
}
if (this.get('pinState.isPinned')) {
this.addTooltip(document.getElementById("space-unpin-button"));
} else {
this.addTooltip(document.getElementById("space-pin-button"));
}
if (this.get('permissions.documentAdd')) {
this.addTooltip(document.getElementById("document-add-button"));
}
}
},
willDestroyElement() {
this._super(...arguments);
if (this.get('isDestroyed') || this.get('isDestroying')) return;
if (is.not.null(this.get('drop'))) {
this.get('drop').destroy();
this.set('drop', null);
}
this.destroyTooltips();
},
actions: {
onUnpin() {
this.get('pinned').unpinItem(this.get('pinState.pinId')).then(() => {
this.set('pinState.isPinned', false);
this.set('pinState.pinId', '');
this.eventBus.publish('pinChange');
this.renderTooltips();
});
},
onPin() {
let pin = {
pin: this.get('pinState.newName'),
documentId: '',
folderId: this.get('folder.id')
};
if (is.empty(pin.pin)) {
$('#pin-space-name').addClass('error').focus();
return false;
}
this.get('pinned').pinItem(pin).then((pin) => {
this.set('pinState.isPinned', true);
this.set('pinState.pinId', pin.get('id'));
this.eventBus.publish('pinChange');
this.renderTooltips();
});
return true;
},
deleteDocuments() {
this.attrs.onDeleteDocument();
},
deleteSpace() {
let spaceName = this.get('folder').get('name');
let spaceNameTyped = this.get('deleteSpaceName');
if (spaceNameTyped !== spaceName || spaceNameTyped === '' || spaceName === '') {
$("#delete-space-name").addClass("error").focus();
return false;
}
this.set('deleteSpaceName', '');
$("#delete-space-name").removeClass("error");
this.attrs.onDeleteSpace();
return true;
},
setMoveFolder(folderId) {
this.set('moveFolderId', folderId);
let folders = this.get('folders');
folders.forEach(folder => {
folder.set('selected', folder.id === folderId);
});
},
moveDocuments() {
if (this.get("moveFolderId") === "") {
return false;
}
this.attrs.onMoveDocument(this.get('moveFolderId'));
return true;
},
onStartDocument() {
this.attrs.onStartDocument();
}
}
});

View file

@ -0,0 +1,188 @@
// 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
import Ember from 'ember';
import NotifierMixin from '../../mixins/notifier';
import TooltipMixin from '../../mixins/tooltip';
import AuthMixin from '../../mixins/auth';
const {
inject: { service }
} = Ember;
export default Ember.Component.extend(NotifierMixin, TooltipMixin, AuthMixin, {
router: service(),
documentService: service('document'),
folderService: service('folder'),
localStorage: service('localStorage'),
selectedDocuments: [],
hasSelectedDocuments: Ember.computed.gt('selectedDocuments.length', 0),
hasCategories: Ember.computed.gt('categories.length', 0),
showStartDocument: false,
filteredDocs: [],
didReceiveAttrs() {
this._super(...arguments);
this.setup();
},
didUpdateAttrs() {
this._super(...arguments);
this.set('selectedDocuments', []);
this.set('filteredDocs', []);
},
didRender() {
this._super(...arguments);
if (this.get('rootDocCount') > 0) {
this.addTooltip(document.getElementById("uncategorized-button"));
}
},
willDestroyElement() {
this._super(...arguments);
this.destroyTooltips();
},
setup() {
let categories = this.get('categories');
let categorySummary = this.get('categorySummary');
let selectedCategory = '';
categories.forEach((cat)=> {
let summary = _.findWhere(categorySummary, {type: "documents", categoryId: cat.get('id')});
let docCount = is.not.undefined(summary) ? summary.count : 0;
cat.set('docCount', docCount);
if (docCount > 0 && selectedCategory === '') {
selectedCategory = cat.get('id');
}
});
this.set('categories', categories);
Ember.run.schedule('afterRender', () => {
if (this.get('rootDocCount') > 0) {
this.send('onDocumentFilter', 'space', this.get('folder.id'));
} else if (selectedCategory !== '') {
this.send('onDocumentFilter', 'category', selectedCategory);
}
});
},
actions: {
onMoveDocument(folder) {
let self = this;
let documents = this.get('selectedDocuments');
documents.forEach(function (documentId) {
self.get('documentService').getDocument(documentId).then(function (doc) {
doc.set('folderId', folder);
doc.set('selected', !doc.get('selected'));
self.get('documentService').save(doc).then(function () {
self.attrs.onRefresh();
});
});
});
this.set('selectedDocuments', []);
this.send("showNotification", "Moved");
},
onDeleteDocument() {
let documents = this.get('selectedDocuments');
let self = this;
let promises = [];
documents.forEach(function (document, index) {
promises[index] = self.get('documentService').deleteDocument(document);
});
Ember.RSVP.all(promises).then(() => {
let documents = this.get('documents');
documents.forEach(function (document) {
document.set('selected', false);
});
this.set('documents', documents);
this.set('selectedDocuments', []);
this.send("showNotification", "Deleted");
this.attrs.onRefresh();
});
},
onDeleteSpace() {
this.get('folderService').delete(this.get('folder.id')).then(() => { /* jshint ignore:line */
this.showNotification("Deleted");
this.get('localStorage').clearSessionItem('folder');
this.get('router').transitionTo('application');
});
},
onImport() {
this.attrs.onRefresh();
},
onStartDocument() {
this.set('showStartDocument', !this.get('showStartDocument'));
},
onHideStartDocument() {
this.set('showStartDocument', false);
},
onDocumentFilter(filter, id) {
let docs = this.get('documents');
let categories = this.get('categories');
let categoryMembers = this.get('categoryMembers');
let filtered = [];
let allowed = [];
switch (filter) {
case 'category':
allowed = _.pluck(_.where(categoryMembers, {'categoryId': id}), 'documentId');
docs.forEach((d) => {
if (_.contains(allowed, d.get('id'))) {
filtered.pushObject(d);
}
});
this.set('spaceSelected', false);
break;
case 'uncategorized':
this.set('uncategorizedSelected', true);
allowed = _.pluck(categoryMembers, 'documentId');
docs.forEach((d) => {
if (!_.contains(allowed, d.get('id'))) {
filtered.pushObject(d);
}
});
break;
case 'space':
this.set('spaceSelected', true);
allowed = _.pluck(categoryMembers, 'documentId');
docs.forEach((d) => {
filtered.pushObject(d);
});
break;
}
categories.forEach((cat)=> {
cat.set('selected', cat.get('id') === id);
});
this.set('categories', categories);
this.set('filteredDocs', filtered);
}
}
});

View file

@ -14,54 +14,66 @@ import NotifierMixin from '../../mixins/notifier';
const { const {
computed, computed,
inject: { service }
} = Ember; } = Ember;
export default Ember.Component.extend(NotifierMixin, { export default Ember.Component.extend(NotifierMixin, {
localStorage: Ember.inject.service(), localStorage: service(),
appMeta: Ember.inject.service(), appMeta: service(),
templateService: Ember.inject.service('template'), templateService: service('template'),
canEditTemplate: "",
importedDocuments: [], importedDocuments: [],
savedTemplates: [], savedTemplates: [],
drop: null, importStatus: [],
newDocumentName: 'New Document', dropzone: null,
newDocumentName: '',
newDocumentNameMissing: computed.empty('newDocumentName'), newDocumentNameMissing: computed.empty('newDocumentName'),
didInsertElement() {
this.setupImport();
},
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments);
this.setupTemplates(); this.setupTemplates();
Ember.run.schedule('afterRender', ()=> {
this.setupImport();
});
}, },
willDestroyElement() { willDestroyElement() {
if (is.not.null(this.get('drop'))) { this._super(...arguments);
this.get('drop').destroy();
this.set('drop', null); if (is.not.null(this.get('dropzone'))) {
this.get('dropzone').destroy();
this.set('dropzone', null);
} }
}, },
setupTemplates() { setupTemplates() {
let templates = this.get('templates'); let templates = this.get('templates');
if (is.undefined(templates.findBy('id', '0'))) {
let emptyTemplate = { let emptyTemplate = {
id: "0", id: "0",
title: "Empty", title: "Blank",
description: "An empty canvas for your words", description: "An empty canvas for your words",
layout: "doc", layout: "doc",
locked: true locked: true
}; };
templates.unshiftObject(emptyTemplate); templates.unshiftObject(emptyTemplate);
}
this.set('savedTemplates', templates); this.set('savedTemplates', templates);
Ember.run.schedule('afterRender', () => {
$('#new-document-name').select();
});
}, },
setupImport() { setupImport() {
// already done init? // already done init?
if (is.not.null(this.get('drop'))) { if (is.not.null(this.get('dropzone'))) {
this.get('drop').destroy(); this.get('dropzone').destroy();
this.set('drop', null); this.set('dropzone', null);
} }
let self = this; let self = this;
@ -70,9 +82,7 @@ export default Ember.Component.extend(NotifierMixin, {
let importUrl = `${url}/import/folder/${folderId}`; let importUrl = `${url}/import/folder/${folderId}`;
let dzone = new Dropzone("#import-document-button", { let dzone = new Dropzone("#import-document-button", {
headers: { headers: { 'Authorization': 'Bearer ' + self.get('session.session.content.authenticated.token') },
'Authorization': 'Bearer ' + self.get('session.session.content.authenticated.token')
},
url: importUrl, url: importUrl,
method: "post", method: "post",
paramName: 'attachment', paramName: 'attachment',
@ -90,10 +100,10 @@ export default Ember.Component.extend(NotifierMixin, {
}); });
this.on("error", function (x) { this.on("error", function (x) {
console.log("Conversion failed for ", x.name, " obj ", x); // eslint-disable-line no-console console.log("Conversion failed for", x.name, x); // eslint-disable-line no-console
}); });
this.on("queuecomplete", function () {}); // this.on("queuecomplete", function () {});
this.on("addedfile", function (file) { this.on("addedfile", function (file) {
self.send('onDocumentImporting', file.name); self.send('onDocumentImporting', file.name);
@ -105,12 +115,12 @@ export default Ember.Component.extend(NotifierMixin, {
dzone.removeFile(file); dzone.removeFile(file);
}); });
this.set('drop', dzone); this.set('dropzone', dzone);
}, },
actions: { actions: {
onHideDocumentWizard() { onHideStartDocument() {
this.get('onHideDocumentWizard')(); this.get('onHideStartDocument')();
}, },
editTemplate(template) { editTemplate(template) {
@ -120,6 +130,12 @@ export default Ember.Component.extend(NotifierMixin, {
}, },
startDocument(template) { startDocument(template) {
if (this.get('newDocumentNameMissing')) {
this.$("#new-document-name").addClass('error').focus();
return;
}
this.$("#new-document-name").removeClass('error');
this.send("showNotification", "Creating"); this.send("showNotification", "Creating");
this.get('templateService').importSavedTemplate(this.folder.get('id'), template.id, this.get('newDocumentName')).then((document) => { this.get('templateService').importSavedTemplate(this.folder.get('id'), template.id, this.get('newDocumentName')).then((document) => {
@ -130,25 +146,29 @@ export default Ember.Component.extend(NotifierMixin, {
}, },
onDocumentImporting(filename) { onDocumentImporting(filename) {
this.send("showNotification", `Importing ${filename}`); let status = this.get('importStatus');
this.get('onHideDocumentWizard')();
let documents = this.get('importedDocuments'); let documents = this.get('importedDocuments');
status.pushObject(`Converting ${filename}...`);
documents.push(filename); documents.push(filename);
this.set('importStatus', status);
this.set('importedDocuments', documents); this.set('importedDocuments', documents);
}, },
onDocumentImported(filename /*, document*/ ) { onDocumentImported(filename /*, document*/ ) {
this.send("showNotification", `${filename} ready`); let status = this.get('importStatus');
let documents = this.get('importedDocuments'); let documents = this.get('importedDocuments');
status.pushObject(`Successfully converted ${filename}`);
documents.pop(filename); documents.pop(filename);
this.set('importStatus', status);
this.set('importedDocuments', documents); this.set('importedDocuments', documents);
this.get('onImport')();
if (documents.length === 0) { if (documents.length === 0) {
// this.get('showDocument')(this.get('folder'), document); this.get('onHideStartDocument')();
this.get('onImport')();
} }
}, },
} }

View file

@ -1,163 +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
import Ember from 'ember';
import netUtil from '../../utils/net';
import constants from '../../utils/constants';
import TooltipMixin from '../../mixins/tooltip';
const {
inject: { service }
} = Ember;
export default Ember.Component.extend(TooltipMixin, {
folderService: service('folder'),
appMeta: service(),
session: service(),
store: service(),
folder: null,
view: {
folder: false,
search: false,
settings: false,
profile: false
},
pinned: service(),
pins: [],
enableLogout: true,
init() {
this._super(...arguments);
if (this.get("session.authenticated") && this.get("session.user.id") !== '0') {
this.get("session.accounts").forEach((account) => {
// TODO: do not mutate account.active here
account.active = account.orgId === this.get("appMeta.orgId");
});
}
this.set('pins', this.get('pinned').get('pins'));
if (this.get('appMeta.authProvider') === constants.AuthProvider.Keycloak) {
let config = this.get('appMeta.authConfig');
config = JSON.parse(config);
this.set('enableLogout', !config.disableLogout);
}
},
didReceiveAttrs() {
if (this.get('folder') === null) {
this.set("folder", this.get('folderService.currentFolder'));
}
let route = this.get('router.currentRouteName');
this.set('view.folder', (is.startWith(route, 'folder')) ? true : false);
this.set('view.settings', (is.startWith(route, 'customize')) ? true : false);
this.set('view.profile', (route === 'profile') ? true : false);
this.set('view.search', (route === 'search') ? true : false);
},
didInsertElement() {
this._super(...arguments);
// Size the pinned items zone
if (this.get("session.authenticated")) {
this.eventBus.subscribe('resized', this, 'sizePinnedZone');
this.eventBus.subscribe('pinChange', this, 'setupPins');
this.sizePinnedZone();
this.setupPins();
let self = this;
var sortable = Sortable.create(document.getElementById('pinned-zone'), {
animation: 150,
onEnd: function () {
self.get('pinned').updateSequence(this.toArray()).then((pins) => {
self.set('pins', pins);
});
}
});
this.set('sortable', sortable);
}
},
didRender() {
if (this.get('session.isAdmin')) {
this.addTooltip(document.getElementById("workspace-settings"));
}
if (this.get("session.authenticated") && this.get('enableLogout')) {
this.addTooltip(document.getElementById("workspace-logout"));
} else {
this.addTooltip(document.getElementById("workspace-login"));
}
},
setupPins() {
if (this.get('isDestroyed') || this.get('isDestroying')) {
return;
}
this.get('pinned').getUserPins().then((pins) => {
if (this.get('isDestroyed') || this.get('isDestroying')) {
return;
}
this.set('pins', pins);
pins.forEach((pin) => {
this.addTooltip(document.getElementById(`pin-${pin.id}`));
});
});
},
// set height for pinned zone so ti scrolls on spill
sizePinnedZone() {
let topofBottomZone = parseInt($('#bottom-zone').css("top").replace("px", ""));
let heightOfTopZone = parseInt($('#top-zone').css("height").replace("px", ""));
let size = topofBottomZone - heightOfTopZone - 40;
$('#pinned-zone').css('height', size + "px");
},
willDestroyElement() {
let sortable = this.get('sortable');
if (!_.isUndefined(sortable)) {
sortable.destroy();
}
this.eventBus.unsubscribe('resized');
this.eventBus.unsubscribe('pinChange');
this.destroyTooltips();
},
actions: {
switchAccount(domain) {
window.location.href = netUtil.getAppUrl(domain);
},
jumpToPin(pin) {
let folderId = pin.get('folderId');
let documentId = pin.get('documentId');
if (_.isEmpty(documentId)) {
// jump to space
let folder = this.get('store').peekRecord('folder', folderId);
this.get('router').transitionTo('folder', folderId, folder.get('slug'));
} else {
// jump to doc
let folder = this.get('store').peekRecord('folder', folderId);
this.get('router').transitionTo('document', folderId, folder.get('slug'), documentId, 'document');
}
}
}
});

View file

@ -10,6 +10,74 @@
// https://documize.com // https://documize.com
import Ember from 'ember'; import Ember from 'ember';
import constants from '../../utils/constants';
const {
computed,
inject: { service }
} = Ember;
export default Ember.Component.extend({ export default Ember.Component.extend({
folderService: service('folder'),
appMeta: service(),
session: service(),
store: service(),
pinned: service(),
enableLogout: true,
pins: [],
hasPins: computed.notEmpty('pins'),
init() {
this._super(...arguments);
if (this.get('appMeta.authProvider') === constants.AuthProvider.Keycloak) {
let config = this.get('appMeta.authConfig');
config = JSON.parse(config);
this.set('enableLogout', !config.disableLogout);
}
},
didInsertElement() {
this._super(...arguments);
if (this.get("session.authenticated")) {
this.eventBus.subscribe('pinChange', this, 'setupPins');
this.setupPins();
}
},
setupPins() {
if (this.get('isDestroyed') || this.get('isDestroying')) {
return;
}
this.get('pinned').getUserPins().then((pins) => {
if (this.get('isDestroyed') || this.get('isDestroying')) {
return;
}
this.set('pins', pins);
});
},
willDestroyElement() {
this.eventBus.unsubscribe('pinChange');
},
actions: {
jumpToPin(pin) {
let folderId = pin.get('folderId');
let documentId = pin.get('documentId');
if (_.isEmpty(documentId)) {
// jump to space
let folder = this.get('store').peekRecord('folder', folderId);
this.get('router').transitionTo('folder', folderId, folder.get('slug'));
} else {
// jump to doc
let folder = this.get('store').peekRecord('folder', folderId);
this.get('router').transitionTo('document', folderId, folder.get('slug'), documentId, 'document');
}
}
}
}); });

View file

@ -9,11 +9,15 @@
// //
// https://documize.com // https://documize.com
import { Factory, faker } from 'ember-cli-mirage'; import Ember from 'ember';
export default Factory.extend({ export default Ember.Component.extend({
"folderId": faker.list.cycle("VzMuyEw_3WqiafcG", "VzMygEw_3WrtFzto"), nameField: 'category',
"userId": faker.list.cycle("VzMuyEw_3WqiafcE", "VzMuyEw_3WqiafcE"), items: [],
"canView": true,
"canEdit": true actions: {
onToggle(item) {
Ember.set(item, 'selected', !item.get('selected'));
}
}
}); });

View file

@ -0,0 +1,30 @@
// 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
import Ember from 'ember';
export default Ember.Mixin.create({
closeDropdown() {
let drop = this.get('dropdown');
if (is.not.null(drop) && is.not.null(drop.drop)) {
drop.close();
}
},
destroyDropdown() {
let drop = this.get('dropdown');
if (is.not.null(drop) && is.not.null(drop.drop)) {
drop.destroy();
}
}
});

View file

@ -15,7 +15,6 @@ export default Ember.Mixin.create({
tooltips: [], tooltips: [],
addTooltip(elem) { addTooltip(elem) {
if (elem == null) { if (elem == null) {
return; return;
} }
@ -26,9 +25,19 @@ export default Ember.Mixin.create({
let tt = this.get('tooltips'); let tt = this.get('tooltips');
tt.push(t); tt.push(t);
return t;
},
destroyTooltip(t) {
t.destroy();
}, },
destroyTooltips() { destroyTooltips() {
if (this.get('isDestroyed') || this.get('isDestroying')) {
return;
}
let tt = this.get('tooltips'); let tt = this.get('tooltips');
tt.forEach(t => { tt.forEach(t => {

View file

@ -11,13 +11,15 @@
import Model from 'ember-data/model'; import Model from 'ember-data/model';
import attr from 'ember-data/attr'; import attr from 'ember-data/attr';
// import { belongsTo, hasMany } from 'ember-data/relationships';
export default Model.extend({ export default Model.extend({
orgId: attr('string'), orgId: attr('string'),
folderId: attr('string'), folderId: attr('string'),
userId: attr('string'), category: attr('string'),
fullname: attr('string'), created: attr(),
canView: attr('boolean', { defaultValue: false }), revised: attr(),
canEdit: attr('boolean', { defaultValue: false })
// fields used by UI only
documents: attr(),
users: attr(),
}); });

View file

@ -0,0 +1,31 @@
// 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
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
// import { belongsTo, hasMany } from 'ember-data/relationships';
export default Model.extend({
orgId: attr('string'),
folderId: attr('string'),
userId: attr('string'),
fullname: attr('string'), // client-side usage only, not from API
spaceView: attr('boolean'),
spaceManage: attr('boolean'),
spaceOwner: attr('boolean'),
documentAdd: attr('boolean'),
documentEdit: attr('boolean'),
documentDelete: attr('boolean'),
documentMove: attr('boolean'),
documentCopy: attr('boolean'),
documentTemplate: attr('boolean')
});

View file

@ -22,6 +22,7 @@ export default Model.extend({
active: attr('boolean', { defaultValue: false }), active: attr('boolean', { defaultValue: false }),
editor: attr('boolean', { defaultValue: false }), editor: attr('boolean', { defaultValue: false }),
admin: attr('boolean', { defaultValue: false }), admin: attr('boolean', { defaultValue: false }),
viewUsers: attr('boolean', { defaultValue: false }),
global: attr('boolean', { defaultValue: false }), global: attr('boolean', { defaultValue: false }),
accounts: attr(), accounts: attr(),
created: attr(), created: attr(),

View file

@ -11,10 +11,16 @@
import Ember from 'ember'; import Ember from 'ember';
import NotifierMixin from '../../../mixins/notifier'; import NotifierMixin from '../../../mixins/notifier';
import DropdownMixin from '../../../mixins/dropdown';
export default Ember.Controller.extend(NotifierMixin, { export default Ember.Controller.extend(NotifierMixin, DropdownMixin, {
folderService: Ember.inject.service('folder'), folderService: Ember.inject.service('folder'),
folders: [], folders: [],
dropdown: null,
deleteSpace: {
id: '',
name: ''
},
label: function () { label: function () {
switch (this.get('folders').length) { switch (this.get('folders').length) {
@ -25,16 +31,65 @@ export default Ember.Controller.extend(NotifierMixin, {
} }
}.property('folders'), }.property('folders'),
willDestroyElement() {
this.destroyDropdown();
},
actions: { actions: {
changeOwner: function (folderId, userId) { onShow(spaceId) {
this.get('folderService').getFolder(folderId).then((folder) => { this.set('deleteSpace.id', spaceId);
folder.set('userId', userId); this.set('deleteSpace.name', '');
$(".delete-space-dialog").css("display", "block");
$('#delete-space-name').removeClass('error');
this.get('folderService').save(folder).then(() => { let drop = new Drop({
this.showNotification("Changed"); target: $("#delete-space-button-" + spaceId)[0],
content: $(".delete-space-dialog")[0],
classes: 'drop-theme-basic',
position: "bottom right",
openOn: "always",
tetherOptions: {
offset: "5px 0",
targetOffset: "10px 0"
},
remove: false
}); });
this.send('onChangeOwner'); this.set('dropdown', drop);
},
onCancel() {
this.closeDropdown();
},
onDelete() {
let deleteSpace = this.get('deleteSpace');
let spaceId = deleteSpace.id;
let spaceNameTyped = deleteSpace.name;
let space = this.get('folders').findBy('id', spaceId);
let spaceName = space.get('name');
if (spaceNameTyped !== spaceName || spaceNameTyped === '' || spaceName === '') {
$('#delete-space-name').addClass('error').focus();
return;
}
this.closeDropdown();
this.get('folderService').delete(spaceId).then(() => { /* jshint ignore:line */
this.set('deleteSpace.id', '');
this.set('deleteSpace.name', '');
this.showNotification("Deleted");
this.get('folderService').adminList().then((folders) => {
let nonPrivateFolders = folders.rejectBy('folderType', 2);
if (is.empty(nonPrivateFolders) || is.null(folders) || is.undefined(folders)) {
nonPrivateFolders = [];
}
this.set('folders', nonPrivateFolders);
});
}); });
} }
} }

View file

@ -22,7 +22,7 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
}, },
model() { model() {
return this.get('folderService').getAll(); return this.get('folderService').adminList();
}, },
setupController(controller, model) { setupController(controller, model) {
@ -30,33 +30,12 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
if (is.empty(nonPrivateFolders) || is.null(model) || is.undefined(model)) { if (is.empty(nonPrivateFolders) || is.null(model) || is.undefined(model)) {
nonPrivateFolders = []; nonPrivateFolders = [];
} }
controller.set('folders', nonPrivateFolders); controller.set('folders', nonPrivateFolders);
this.get('folderService').getProtectedFolderInfo().then((people) => {
people.forEach((person) => {
person.set('isEveryone', person.get('userId') === '');
person.set('isOwner', false);
});
nonPrivateFolders.forEach(function (folder) {
let shared = people.filterBy('folderId', folder.get('id'));
let person = shared.findBy('userId', folder.get('userId'));
if (is.not.undefined(person)) {
person.set('isOwner', true);
}
folder.set('sharedWith', shared);
});
});
}, },
activate() { activate() {
document.title = "Spaces | Documize"; document.title = "Spaces | Documize";
},
actions: {
onChangeOwner() {
this.refresh();
}
} }
}); });

View file

@ -1,50 +1,51 @@
<div class="page-customize">
<div class="space-admin">
{{#if folders}} {{#if folders}}
<div class="global-folder-settings">
<div class="form-header"> <div class="form-header">
<div class="title">{{folders.length}} shared {{label}}</div> <div class="title">{{folders.length}} shared {{label}}</div>
<div class="tip">View and change shared space ownership</div> <div class="tip">View and change shared space ownership</div>
</div> </div>
<div class="input-control"> <div class="input-control manage-space-list">
<table class="basic-table">
<thead>
<tr>
<th class="bordered">Space</th>
<th class="bordered">Participants</th>
</tr>
</thead>
<tbody>
{{#each folders as |folder|}} {{#each folders as |folder|}}
<tr> <div class="space pull-left width-80">
<td class="bordered">
{{#link-to 'folder' folder.id folder.slug class="alt"}}{{folder.name}}{{/link-to}} {{#link-to 'folder' folder.id folder.slug class="alt"}}{{folder.name}}{{/link-to}}
</td> </div>
<td class="bordered"> <div class="pull-right">
{{#each folder.sharedWith as |person|}} <div id="delete-space-button-{{folder.id}}" class="round-button-mono" title="Delete" {{action "onShow" folder.id}}>
{{#if person.isEveryone}} <i class="material-icons">delete</i>
Everyone </div>
</div>
<div class="clearfix" />
{{/each}}
</div>
<div class="dropdown-dialog delete-space-dialog">
<div class="content">
<p>Are you sure you want to delete this space and all associated documents?</p>
<div class="input-control">
<div class="tip">Please type the space name to confirm</div>
{{input type='text' id="delete-space-name" value=deleteSpace.name}}
</div>
</div>
<div class="actions">
<div class="flat-button" {{action 'onCancel'}}>
cancel
</div>
<div class="flat-button flat-red" {{action 'onDelete'}}>
delete
</div>
</div>
<div class="clearfix"></div>
</div>
{{else}} {{else}}
{{#if person.isOwner}}
<span class="bold">{{person.firstname}} {{person.lastname}} (owner)</span>
{{else}}
{{person.firstname}} {{person.lastname}}
<a class="action-link" {{action "changeOwner" folder.id person.userId}}>make owner</a>
{{/if}}
{{/if}}
<br/>
{{/each}}
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
{{else}}
<div class="global-folder-settings">
<div class="form-header"> <div class="form-header">
<div class="title">{{folders.length}} shared {{label}}</div> <div class="title">{{folders.length}} shared {{label}}</div>
<div class="tip">There are no spaces to maintain</div> <div class="tip">There are no spaces to maintain</div>
</div> </div>
</div>
{{/if}} {{/if}}
</div>
</div>

View file

@ -1,8 +1,5 @@
{{layout/zone-navigation}}
{{#layout/zone-container}} {{#layout/zone-container}}
{{#layout/zone-sidebar}} {{#layout/zone-sidebar}}
<div class="sidebar-toolbar">
</div>
<div class="sidebar-common"> <div class="sidebar-common">
{{layout/sidebar-intro title='Settings' message='Documize application settings'}} {{layout/sidebar-intro title='Settings' message='Documize application settings'}}
</div> </div>

View file

@ -18,7 +18,7 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
global: Ember.inject.service('global'), global: Ember.inject.service('global'),
appMeta: Ember.inject.service(), appMeta: Ember.inject.service(),
beforeModel: function () { beforeModel () {
if (!this.session.isAdmin) { if (!this.session.isAdmin) {
this.transitionTo('auth.login'); this.transitionTo('auth.login');
} }
@ -40,7 +40,7 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
}); });
}, },
activate: function () { activate() {
document.title = "Users | Documize"; document.title = "Users | Documize";
} }
}); });

View file

@ -1,5 +1,3 @@
{{customize/user-settings add=(action 'add')}} {{customize/user-settings add=(action 'add')}}
<div class="clearfix" />
{{customize/user-admin users=model onDelete=(action "onDelete") onSave=(action "onSave") onPassword=(action "onPassword")}} {{customize/user-admin users=model onDelete=(action "onDelete") onSave=(action "onSave") onPassword=(action "onPassword")}}

View file

@ -7,7 +7,7 @@
<i class="material-icons">arrow_back</i>&nbsp;{{model.document.name}} <i class="material-icons">arrow_back</i>&nbsp;{{model.document.name}}
{{/link-to}} {{/link-to}}
</div> </div>
{{document/document-heading document=model.document isEditor=false}} {{document/document-heading document=model.document}}
{{document/block-editor document=model.document folder=model.folder block=model.block onCancel=(action 'onCancel') onAction=(action 'onAction')}} {{document/block-editor document=model.document folder=model.folder block=model.block onCancel=(action 'onCancel') onAction=(action 'onAction')}}
</div> </div>
</div> </div>

View file

@ -7,7 +7,7 @@
<i class="material-icons">arrow_back</i>&nbsp;{{model.document.name}} <i class="material-icons">arrow_back</i>&nbsp;{{model.document.name}}
{{/link-to}} {{/link-to}}
</div> </div>
{{document/document-heading document=model.document isEditor=false}} {{document/document-heading document=model.document}}
{{#if hasRevisions}} {{#if hasRevisions}}
{{document/document-history document=model.document folder=model.folder pages=model.pages {{document/document-history document=model.document folder=model.folder pages=model.pages
revisions=model.revisions diff=model.diff onFetchDiff=(action 'onFetchDiff') onRollback=(action 'onRollback')}} revisions=model.revisions diff=model.diff onFetchDiff=(action 'onFetchDiff') onRollback=(action 'onRollback')}}

View file

@ -11,8 +11,9 @@
import Ember from 'ember'; import Ember from 'ember';
import NotifierMixin from '../../../mixins/notifier'; import NotifierMixin from '../../../mixins/notifier';
import TooltipMixin from '../../../mixins/tooltip';
export default Ember.Controller.extend(NotifierMixin, { export default Ember.Controller.extend(NotifierMixin, TooltipMixin, {
documentService: Ember.inject.service('document'), documentService: Ember.inject.service('document'),
templateService: Ember.inject.service('template'), templateService: Ember.inject.service('template'),
sectionService: Ember.inject.service('section'), sectionService: Ember.inject.service('section'),
@ -25,10 +26,6 @@ export default Ember.Controller.extend(NotifierMixin, {
tab: 'index', tab: 'index',
actions: { actions: {
toggleSidebar() {
this.set('toggled', !this.get('toggled'));
},
onSaveDocument(doc) { onSaveDocument(doc) {
this.get('documentService').save(doc); this.get('documentService').save(doc);
this.showNotification('Saved'); this.showNotification('Saved');
@ -226,6 +223,12 @@ export default Ember.Controller.extend(NotifierMixin, {
if (this.get('pageId') !== id && id !== '') { if (this.get('pageId') !== id && id !== '') {
this.set('pageId', id); this.set('pageId', id);
} }
},
onTagChange(tags) {
let doc = this.get('model.document');
doc.set('tags', tags);
this.get('documentService').save(doc);
} }
} }
}); });

View file

@ -30,7 +30,7 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
pages: this.get('documentService').getPages(this.modelFor('document').document.get('id')), pages: this.get('documentService').getPages(this.modelFor('document').document.get('id')),
links: this.modelFor('document').links, links: this.modelFor('document').links,
sections: this.modelFor('document').sections, sections: this.modelFor('document').sections,
isEditor: this.get('folderService').get('canEditCurrentFolder') permissions: this.modelFor('document').permissions
}); });
} }
}); });

View file

@ -1,26 +1,46 @@
{{#layout/zone-container}} {{#layout/zone-container}}
{{#layout/zone-sidebar}} {{#layout/zone-sidebar}}
{{document/sidebar-zone folders=model.folders folder=model.folder document=model.document {{document/document-sidebar tab=tab
pages=model.pages sections=model.section links=model.links isEditor=model.isEditor tab=tab document=model.document folder=model.folder pages=model.pages page=model.page permissions=model.permissions
onDocumentDelete=(action 'onDocumentDelete') onSaveTemplate=(action 'onSaveTemplate') onPageSequenceChange=(action 'onPageSequenceChange') onPageLevelChange=(action 'onPageLevelChange')
onPageSequenceChange=(action 'onPageSequenceChange') onPageLevelChange=(action 'onPageLevelChange') onGotoPage=(action 'onGotoPage')}} onGotoPage=(action 'onGotoPage')}}
{{/layout/zone-sidebar}} {{/layout/zone-sidebar}}
{{#layout/zone-content}} {{#layout/zone-content}}
<div id="zone-document-content" class="zone-document-content"> <div id="zone-document-content" class="zone-document-content">
<div class="back-to-space"> <div class="document-header-zone">
{{#link-to 'folder' model.folder.id model.folder.slug}} <div class="pull-left">
<div class="regular-button button-gray"> {{document/space-category document=model.document folder=model.folder folders=model.folders permissions=model.permissions}}
<i class="material-icons">arrow_back</i>
<div class="name">{{model.folder.name}}</div>
</div> </div>
{{/link-to}}
<div class="pull-right">
{{document/document-toolbar
document=model.document folder=model.folder folders=model.folders permissions=model.permissions
onDocumentDelete=(action 'onDocumentDelete') onSaveTemplate=(action 'onSaveTemplate')
onPageSequenceChange=(action 'onPageSequenceChange') onPageLevelChange=(action 'onPageLevelChange')
onGotoPage=(action 'onGotoPage')}}
</div> </div>
{{document/document-heading document=model.document isEditor=model.isEditor onSaveDocument=(action 'onSaveDocument')}} <div class="clearfix"/>
{{document/document-view document=model.document links=model.links pages=model.pages
folder=model.folder folders=model.folders sections=model.sections isEditor=model.isEditor pageId=pageId {{document/tag-editor documentTags=model.document.tags permissions=model.permissions onChange=(action 'onTagChange')}}
</div>
{{document/document-heading document=model.document permissions=model.permissions onSaveDocument=(action 'onSaveDocument')}}
{{#if model.document.template}}
<div class="document-template-header">Template</div>
{{/if}}
{{document/document-view
document=model.document links=model.links pages=model.pages
folder=model.folder folders=model.folders sections=model.sections permissions=model.permissions pageId=pageId
onSavePage=(action 'onSavePage') onInsertSection=(action 'onInsertSection') onSavePage=(action 'onSavePage') onInsertSection=(action 'onInsertSection')
onSavePageAsBlock=(action 'onSavePageAsBlock') onDeleteBlock=(action 'onDeleteBlock') onGotoPage=(action 'onGotoPage') onSavePageAsBlock=(action 'onSavePageAsBlock') onDeleteBlock=(action 'onDeleteBlock') onGotoPage=(action 'onGotoPage')
onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onPageDeleted')}} onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onPageDeleted')}}
</div> </div>
{{/layout/zone-content}} {{/layout/zone-content}}
{{/layout/zone-container}} {{/layout/zone-container}}

View file

@ -24,22 +24,31 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
this.set('documentId', this.paramsFor('document').document_id); this.set('documentId', this.paramsFor('document').document_id);
return new Ember.RSVP.Promise((resolve) => { return new Ember.RSVP.Promise((resolve) => {
this.get('documentService').getDocument(this.get('documentId')).then((document) => { this.get('documentService').fetchDocumentData(this.get('documentId')).then((data) => {
this.set('document', document); this.set('document', data.document);
this.set('folders', data.folders);
this.get('folderService').getAll().then((folders) => { this.set('folder', data.folder);
this.set('folders', folders); this.set('permissions', data.permissions);
this.set('links', data.links);
this.get('folderService').getFolder(this.get('folderId')).then((folder) => {
this.set('folder', folder);
this.get('folderService').setCurrentFolder(folder).then(() => {
this.set('isEditor', this.get('folderService').get('canEditCurrentFolder'));
resolve(); resolve();
}); });
});
}); // this.get('documentService').getDocument(this.get('documentId')).then((document) => {
}); // this.set('document', document);
// this.get('folderService').getAll().then((folders) => {
// this.set('folders', folders);
// this.get('folderService').getFolder(this.get('folderId')).then((folder) => {
// this.set('folder', folder);
// this.get('folderService').setCurrentFolder(folder).then(() => {
// this.set('permissions', this.get('folderService').get('permissions'));
// resolve();
// });
// });
// });
// });
}); });
}, },
@ -49,8 +58,8 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
folder: this.get('folder'), folder: this.get('folder'),
document: this.get('document'), document: this.get('document'),
page: this.get('pageId'), page: this.get('pageId'),
isEditor: this.get('isEditor'), permissions: this.get('permissions'),
links: this.get('linkService').getDocumentLinks(this.get('documentId')), links: this.get('links'),
sections: this.get('sectionService').getAll() sections: this.get('sectionService').getAll()
}); });
}, },

View file

@ -22,7 +22,7 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
folders: this.modelFor('document').folders, folders: this.modelFor('document').folders,
folder: this.modelFor('document').folder, folder: this.modelFor('document').folder,
document: this.modelFor('document').document, document: this.modelFor('document').document,
isEditor: this.get('folderService').get('canEditCurrentFolder'), permissions: this.get('folderService').get('permissions'),
links: this.modelFor('document').links, links: this.modelFor('document').links,
sections: this.modelFor('document').sections, sections: this.modelFor('document').sections,
page: this.get('documentService').getPage(this.modelFor('document').document.get('id'), params.page_id), page: this.get('documentService').getPage(this.modelFor('document').document.get('id'), params.page_id),

View file

@ -7,7 +7,7 @@
<i class="material-icons">arrow_back</i>&nbsp;{{model.document.name}} <i class="material-icons">arrow_back</i>&nbsp;{{model.document.name}}
{{/link-to}} {{/link-to}}
</div> </div>
{{document/document-heading document=model.document isEditor=false}} {{document/document-heading document=model.document permissions=model.permissions}}
{{document/document-editor document=model.document folder=model.folder page=model.page meta=model.meta onCancel=(action 'onCancel') onAction=(action 'onAction')}} {{document/document-editor document=model.document folder=model.folder page=model.page meta=model.meta onCancel=(action 'onCancel') onAction=(action 'onAction')}}
</div> </div>
</div> </div>

View file

@ -1,2 +1 @@
{{layout/zone-navigation}}
{{outlet}} {{outlet}}

View file

@ -10,78 +10,6 @@
// https://documize.com // https://documize.com
import Ember from 'ember'; import Ember from 'ember';
import NotifierMixin from '../../mixins/notifier';
export default Ember.Controller.extend(NotifierMixin, { export default Ember.Controller.extend({
documentService: Ember.inject.service('document'),
folderService: Ember.inject.service('folder'),
localStorage: Ember.inject.service('localStorage'),
selectedDocuments: [],
hasSelectedDocuments: Ember.computed.gt('selectedDocuments.length', 0),
queryParams: ['tab'],
tab: 'index',
actions: {
onMoveDocument(folder) {
let self = this;
let documents = this.get('selectedDocuments');
documents.forEach(function (documentId) {
self.get('documentService').getDocument(documentId).then(function (doc) {
doc.set('folderId', folder);
doc.set('selected', !doc.get('selected'));
self.get('documentService').save(doc).then(function () {
self.get('target._routerMicrolib').refresh();
});
});
});
this.set('selectedDocuments', []);
this.send("showNotification", "Moved");
},
onDeleteDocument() {
let documents = this.get('selectedDocuments');
let self = this;
let promises = [];
documents.forEach(function (document, index) {
promises[index] = self.get('documentService').deleteDocument(document);
});
Ember.RSVP.all(promises).then(() => {
let documents = this.get('model.documents');
documents.forEach(function (document) {
document.set('selected', false);
});
this.set('model.documents', documents);
this.set('selectedDocuments', []);
this.send("showNotification", "Deleted");
this.get('target._routerMicrolib').refresh();
});
},
onAddSpace(payload) {
let self = this;
this.showNotification("Added");
this.get('folderService').add(payload).then(function (newFolder) {
self.get('folderService').setCurrentFolder(newFolder);
self.transitionToRoute('folder', newFolder.get('id'), newFolder.get('slug'));
});
},
onDeleteSpace() {
this.get('folderService').delete(this.get('model.folder.id')).then(() => { /* jshint ignore:line */
this.showNotification("Deleted");
this.get('localStorage').clearSessionItem('folder');
this.transitionToRoute('application');
});
},
onImport() {
this.get('target._routerMicrolib').refresh();
}
}
}); });

View file

@ -0,0 +1,40 @@
// 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
import Ember from 'ember';
const {
inject: { service }
} = Ember;
export default Ember.Controller.extend({
documentService: service('document'),
folderService: service('folder'),
localStorage: service('localStorage'),
queryParams: ['tab'],
tab: 'index',
actions: {
onAddSpace(payload) {
let self = this;
this.showNotification("Added");
this.get('folderService').add(payload).then(function (newFolder) {
self.get('folderService').setCurrentFolder(newFolder);
self.transitionToRoute('folder', newFolder.get('id'), newFolder.get('slug'));
});
},
onRefresh() {
this.get('target._routerMicrolib').refresh();
}
}
});

View file

@ -0,0 +1,73 @@
// 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
import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
categoryService: Ember.inject.service('category'),
beforeModel() {
return new Ember.RSVP.Promise((resolve) => {
this.get('categoryService').fetchSpaceData(this.modelFor('folder').folder.get('id')).then((data) => {
this.set('categories', data.category);
this.set('categorySummary', data.summary);
this.set('categoryMembers', data.membership);
resolve(data);
});
});
},
model() {
this.get('browser').setTitle(this.modelFor('folder').folder.get('name'));
return Ember.RSVP.hash({
folder: this.modelFor('folder').folder,
permissions: this.modelFor('folder').permissions,
folders: this.modelFor('folder').folders,
documents: this.modelFor('folder').documents,
templates: this.modelFor('folder').templates,
showStartDocument: false,
rootDocCount: 0,
categories: this.get('categories'),
categorySummary: this.get('categorySummary'),
categoryMembers: this.get('categoryMembers'),
// categories: this.get('categoryService').getUserVisible(this.modelFor('folder').folder.get('id')),
// categorySummary: this.get('categoryService').getSummary(this.modelFor('folder').folder.get('id')),
// categoryMembers: this.get('categoryService').getSpaceCategoryMembership(this.modelFor('folder').folder.get('id')),
});
},
afterModel(model, transition) { // eslint-disable-line no-unused-vars
// model.folder = this.modelFor('folder').folder;
// model.permissions = this.modelFor('folder').permissions;
// model.folders = this.modelFor('folder').folders;
// model.documents = this.modelFor('folder').documents;
// model.templates = this.modelFor('folder').templates;
// model.showStartDocument = false;
// model.rootDocCount = 0;
let docs = model.documents;
let categoryMembers = model.categoryMembers;
let rootDocCount = 0;
// get documentId's from category members
let withCat = _.pluck(categoryMembers, 'documentId');
// calculate documents without category;
docs.forEach((d) => {
if (!withCat.includes(d.get('id'))) rootDocCount+=1;
});
model.rootDocCount = rootDocCount;
}
});

View file

@ -0,0 +1,26 @@
{{#layout/zone-container}}
{{#layout/zone-sidebar}}
{{folder/sidebar-zone
folders=model.folders
folder=model.folder
permissions=model.permissions
tab=tab
onAddSpace=(action 'onAddSpace')}}
{{/layout/zone-sidebar}}
{{#layout/zone-content}}
{{folder/space-view
folders=model.folders
folder=model.folder
templates=model.templates
permissions=model.permissions
documents=model.documents
categories=model.categories
categorySummary=model.categorySummary
categoryMembers=model.categoryMembers
rootDocCount=model.rootDocCount
onRefresh=(action 'onRefresh')}}
{{/layout/zone-content}}
{{/layout/zone-container}}

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