1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-24 15:49:44 +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 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
v1.53.6
v1.54.0
## OS Support
## OS support
Documize runs on the following:
- Windows
- Linux
- Windows
- macOS
## Tech stack
## Technology stack
Documize is built with the following technologies:
- EmberJS (v2.15.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.
@ -34,7 +79,7 @@ Connect and authenticate with LDAP, Active Directory and more.
<https://docs.documize.com>
## Auth0 Compatible
### Auth0 Compatible
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
## Legal
## The legal bit at the end
<https://documize.com>

View file

@ -1,5 +1,8 @@
#! /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)
echo "Build process started $NOW"

View file

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

View file

@ -161,7 +161,7 @@ func setupAccount(rt *env.Runtime, completion onboardRequest, serial string) (er
return err
}
// Set up default labels for main collection.
// create space
labelID := uniqueid.Generate()
sql = fmt.Sprintf("insert into label (refid, orgid, label, type, userid) values (\"%s\", \"%s\", \"My Project\", 2, \"%s\")", labelID, orgID, userID)
_, err = runSQL(rt, sql)
@ -170,12 +170,14 @@ func setupAccount(rt *env.Runtime, completion onboardRequest, serial string) (er
rt.Log.Error("insert into label failed", err)
}
labelRoleID := uniqueid.Generate()
sql = fmt.Sprintf("insert into labelrole (refid, labelid, orgid, userid, canview, canedit) values (\"%s\", \"%s\", \"%s\", \"%s\", 1, 1)", labelRoleID, labelID, orgID, userID)
_, err = runSQL(rt, sql)
if err != nil {
rt.Log.Error("insert into labelrole failed", err)
// assign permissions to space
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)
if err != nil {
rt.Log.Error("insert into permission failed", err)
}
}
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"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/account"
@ -34,19 +33,11 @@ func (s Scope) Add(ctx domain.RequestContext, account account.Account) (err erro
account.Created = 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 (?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
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)
_, err = ctx.Transaction.Exec("INSERT INTO account (refid, orgid, userid, admin, editor, users, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
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 execute insert for account")
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.
func (s Scope) GetUserAccount(ctx domain.RequestContext, userID string) (account account.Account, err error) {
stmt, err := s.Runtime.Db.Preparex(`
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
err = s.Runtime.Db.Get(&account, `
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
WHERE b.refid=a.orgid and a.orgid=? and a.userid=?`)
defer streamutil.Close(stmt)
WHERE b.refid=a.orgid AND a.orgid=? AND a.userid=?`, ctx.OrgID, userID)
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 {
err = errors.Wrap(err, fmt.Sprintf("execute select for account by user %s", userID))
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.
func (s Scope) GetUserAccounts(ctx domain.RequestContext, userID string) (t []account.Account, err error) {
err = s.Runtime.Db.Select(&t,
`SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.active, a.created, a.revised,
err = s.Runtime.Db.Select(&t, `
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
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.
func (s Scope) GetAccountsByOrg(ctx domain.RequestContext) (t []account.Account, err error) {
err = s.Runtime.Db.Select(&t,
`SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.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
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 {
return 0
}
if err != nil {
err = errors.Wrap(err, "count org accounts")
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) {
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")
defer streamutil.Close(stmt)
_, 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)
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 {
err = errors.Wrap(err, fmt.Sprintf("execute update for account %s", account.RefID))
return
}
return

View file

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

View file

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

View file

@ -20,7 +20,6 @@ import (
"github.com/pkg/errors"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"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, ".")
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("INSERT INTO attachment (refid, orgid, documentid, job, fileid, filename, data, extension, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
a.RefID, a.OrgID, a.DocumentID, a.Job, a.FileID, a.Filename, a.Data, a.Extension, a.Created, a.Revised)
if err != nil {
err = errors.Wrap(err, "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 {
err = errors.Wrap(err, "execute insert attachment")
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.
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=?")
defer streamutil.Close(stmt)
err = s.Runtime.Db.Get(&a, "SELECT id, refid, orgid, documentid, job, fileid, filename, data, extension, created, revised FROM attachment WHERE orgid=? and refid=?",
orgID, attachmentID)
if err != nil {
err = errors.Wrap(err, "prepare select attachment")
return
}
err = stmt.Get(&a, orgID, attachmentID)
if err != nil {
err = errors.Wrap(err, "execute select attachment")
return
}
return
@ -79,7 +64,6 @@ func (s Scope) GetAttachments(ctx domain.RequestContext, docID string) (a []atta
if err != nil {
err = errors.Wrap(err, "execute select attachments")
return
}
return
@ -91,7 +75,6 @@ func (s Scope) GetAttachmentsWithData(ctx domain.RequestContext, docID string) (
if err != nil {
err = errors.Wrap(err, "execute select attachments with data")
return
}
return

View file

@ -40,21 +40,15 @@ func (s Scope) Record(ctx domain.RequestContext, t audit.EventType) {
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 {
tx.Rollback()
s.Runtime.Log.Error("prepare audit insert", err)
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()
return

View file

@ -110,7 +110,7 @@ func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
return
}
h.Runtime.Log.Info("login " + email + " @ " + dom)
h.Runtime.Log.Info("logged in " + email + " @ " + dom)
authModel := auth.AuthenticationModel{}
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/uniqueid"
"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/block"
)
@ -57,7 +57,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanUploadDocument(ctx, *h.Store, b.LabelID) {
if !permission.CanUploadDocument(ctx, *h.Store, b.LabelID) {
response.WriteForbiddenError(w)
return
}
@ -165,7 +165,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
b.RefID = blockID
if !document.CanUploadDocument(ctx, *h.Store, b.LabelID) {
if !permission.CanUploadDocument(ctx, *h.Store, b.LabelID) {
response.WriteForbiddenError(w)
return
}

View file

@ -16,11 +16,9 @@ import (
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/block"
"github.com/jmoiron/sqlx"
"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.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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("INSERT INTO block (refid, orgid, labelid, userid, contenttype, pagetype, title, body, excerpt, rawbody, config, externalsource, used, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
b.RefID, b.OrgID, b.LabelID, b.UserID, b.ContentType, b.PageType, b.Title, b.Body, b.Excerpt, b.RawBody, b.Config, b.ExternalSource, b.Used, b.Created, b.Revised)
if err != nil {
err = errors.Wrap(err, "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 {
err = errors.Wrap(err, "execute insert block")
return
}
return
@ -55,18 +46,11 @@ func (s Scope) Add(ctx domain.RequestContext, b block.Block) (err error) {
// Get returns requested reusable content block.
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=?")
defer streamutil.Close(stmt)
err = s.Runtime.Db.Get(&b, "SELECT a.id, a.refid, a.orgid, a.labelid, a.userid, a.contenttype, a.pagetype, a.title, a.body, a.excerpt, a.rawbody, a.config, a.externalsource, a.used, a.created, a.revised, b.firstname, b.lastname FROM block a LEFT JOIN user b ON a.userid = b.refid WHERE a.orgid=? AND a.refid=?",
ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "prepare select block")
return
}
err = stmt.Get(&b, ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "execute select block")
return
}
return
@ -78,7 +62,6 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (b []block.
if err != nil {
err = errors.Wrap(err, "select space blocks")
return
}
return
@ -86,18 +69,10 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (b []block.
// IncrementUsage increments usage counter for content block.
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=?")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("UPDATE block SET used=used+1, revised=? WHERE orgid=? AND refid=?", time.Now().UTC(), ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "prepare increment block usage")
return
}
_, err = stmt.Exec(time.Now().UTC(), ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "execute increment block usage")
return
}
return
@ -105,18 +80,10 @@ func (s Scope) IncrementUsage(ctx domain.RequestContext, id string) (err error)
// DecrementUsage decrements usage counter for content block.
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=?")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("UPDATE block SET used=used-1, revised=? WHERE orgid=? AND refid=?", time.Now().UTC(), ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "prepare decrement block usage")
return
}
_, err = stmt.Exec(time.Now().UTC(), ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "execute decrement block usage")
return
}
return
@ -124,23 +91,13 @@ func (s Scope) DecrementUsage(ctx domain.RequestContext, id string) (err error)
// RemoveReference clears page.blockid for given blockID.
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=?")
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)
_, err = ctx.Transaction.Exec("UPDATE page SET blockid='', revised=? WHERE orgid=? AND blockid=?", time.Now().UTC(), ctx.OrgID, id)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "execute remove block ref")
return
}
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) {
b.Revised = time.Now().UTC()
var stmt *sqlx.NamedStmt
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)
_, err = ctx.Transaction.NamedExec("UPDATE block SET title=:title, body=:body, excerpt=:excerpt, rawbody=:rawbody, config=:config, revised=:revised WHERE orgid=:orgid AND refid=:refid", b)
if err != nil {
err = errors.Wrap(err, "prepare update block")
return
}
_, err = stmt.Exec(&b)
if err != nil {
err = errors.Wrap(err, "execute update block")
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/domain"
"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/attachment"
"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")
if !document.CanUploadDocument(ctx, *h.Store, folderID) {
if !permission.CanUploadDocument(ctx, *h.Store, folderID) {
response.WriteForbiddenError(w)
return "", "", ""
}

View file

@ -23,13 +23,15 @@ import (
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/space"
"github.com/documize/community/model/activity"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/link"
pm "github.com/documize/community/model/permission"
"github.com/documize/community/model/search"
"github.com/documize/community/model/space"
)
// 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
}
if !CanViewDocumentInFolder(ctx, *h.Store, document.LabelID) {
if !permission.CanViewSpaceDocument(ctx, *h.Store, document.LabelID) {
response.WriteForbiddenError(w)
return
}
@ -135,62 +137,63 @@ func (h *Handler) DocumentLinks(w http.ResponseWriter, r *http.Request) {
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) {
method := "document.space"
method := "document.BySpace"
ctx := domain.GetRequestContext(r)
folderID := request.Query(r, "folder")
if len(folderID) == 0 {
response.WriteMissingDataError(w, method, "folder")
spaceID := request.Query(r, "space")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "space")
return
}
if !space.CanViewSpace(ctx, *h.Store, folderID) {
if !permission.CanViewSpace(ctx, *h.Store, spaceID) {
response.WriteForbiddenError(w)
return
}
documents, err := h.Store.Document.GetBySpace(ctx, folderID)
if len(documents) == 0 {
documents = []doc.Document{}
}
// get complete list of documents
documents, err := h.Store.Document.GetBySpace(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
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 {
documents = []doc.Document{}
}
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
// remove documents that cannot be seen due to lack of
// category view/access permission
filtered := []doc.Document{}
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
}
}
}
}
if !hasCategory || canSeeCategory {
filtered = append(filtered, doc)
}
}
response.WriteJSON(w, documents)
response.WriteJSON(w, filtered)
}
// Update updates an existing document using the
@ -205,12 +208,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
// if !ctx.Editor {
// response.WriteForbiddenError(w)
// return
// }
if !CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -240,6 +238,18 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
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)
if err != nil {
ctx.Transaction.Rollback()
@ -269,7 +279,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
if !CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanDeleteDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -363,3 +373,104 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
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"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/doc"
@ -35,19 +34,11 @@ func (s Scope) Add(ctx domain.RequestContext, document doc.Document) (err error)
document.Created = time.Now().UTC()
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
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)
_, err = ctx.Transaction.Exec("INSERT INTO document (refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
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, "execuet insert document")
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.
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=?")
defer streamutil.Close(stmt)
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=?",
ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "prepare select document")
return
}
err = stmt.Get(&document, ctx.OrgID, id)
if err != nil {
err = errors.Wrap(err, "execute select document")
return
}
return
@ -112,46 +96,30 @@ func (s Scope) GetAll() (ctx domain.RequestContext, documents []doc.Document, er
if err != nil {
err = errors.Wrap(err, "select documents")
return
}
return
}
// GetBySpace returns a slice containing the documents for a given space, most recient first.
func (s Scope) GetBySpace(ctx domain.RequestContext, folderID 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=? ORDER BY revised DESC", ctx.OrgID, folderID)
// GetBySpace returns a slice containing the documents for a given space.
// No attempt is made to hide documents that are protected
// 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 {
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
@ -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.
func (s Scope) Templates(ctx domain.RequestContext) (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=1 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)
`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 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=?
))
)
ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err != nil {
err = errors.Wrap(err, "select document templates")
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.
func (s Scope) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents,
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND labelid=? AND template=1 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,
spaceID,
ctx.OrgID,
ctx.UserID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID)
`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 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=?
))
)
ORDER BY title`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
@ -207,7 +169,6 @@ func (s Scope) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (docu
if err != nil {
err = errors.Wrap(err, "select space document templates")
return
}
return
@ -224,7 +185,6 @@ func (s Scope) PublicDocuments(ctx domain.RequestContext, orgID string) (documen
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute GetPublicDocuments for org %s%s", orgID))
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.
func (s Scope) DocumentList(ctx domain.RequestContext) (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 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)
`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 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 title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows {
err = nil
@ -254,7 +212,6 @@ func (s Scope) DocumentList(ctx domain.RequestContext) (documents []doc.Document
if err != nil {
err = errors.Wrap(err, "select documents list")
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) {
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")
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "prepare update document")
return
}
_, err = stmt.Exec(&document)
_, 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",
&document)
if err != nil {
err = errors.Wrap(err, "execute update document")
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) {
revised := time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("UPDATE document SET labelid=?, revised=? WHERE orgid=? AND refid=?")
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare change document space %s", document))
return
}
_, err = stmt.Exec(space, revised, ctx.OrgID, document)
_, err = ctx.Transaction.Exec("UPDATE document SET labelid=?, revised=? WHERE orgid=? AND refid=?",
space, revised, ctx.OrgID, document)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute change document space %s", document))
return
}
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".
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=?")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("UPDATE document SET labelid=? WHERE orgid=? AND labelid=?",
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 {
err = errors.Wrap(err, fmt.Sprintf("execute document space move %s", id))
return
}
return
}
// Delete delete the document pages in the database, updates the search subsystem, deletes the associated revisions and attachments,
// audits the deletion, then finally deletes the document itself.
// Delete removes the specified document.
// Remove document pages, revisions, attachments, updates the search subsystem.
func (s Scope) Delete(ctx domain.RequestContext, documentID string) (rows int64, err error) {
b := mysql.BaseQuery{}
rows, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE from page WHERE documentid=\"%s\" AND orgid=\"%s\"", documentID, ctx.OrgID))
@ -345,3 +279,26 @@ func (s Scope) Delete(ctx domain.RequestContext, documentID string) (rows int64,
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/uniqueid"
"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/link"
"github.com/documize/community/model/page"
@ -57,7 +57,7 @@ func (h *Handler) GetLinkCandidates(w http.ResponseWriter, r *http.Request) {
}
// permission check
if !document.CanViewDocument(ctx, *h.Store, documentID) {
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}

View file

@ -12,12 +12,12 @@
package mysql
import (
"database/sql"
"fmt"
"strings"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"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.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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("INSERT INTO link (refid, orgid, folderid, userid, sourcedocumentid, sourcepageid, targetdocumentid, targetid, linktype, orphan, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
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 {
err = errors.Wrap(err, "execute link insert")
return
}
return
@ -62,7 +55,8 @@ func (s Scope) GetDocumentOutboundLinks(ctx domain.RequestContext, documentID st
ctx.OrgID,
documentID)
if err != nil {
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "select document oubound links")
return
}
@ -83,7 +77,8 @@ func (s Scope) GetPageLinks(ctx domain.RequestContext, documentID, pageID string
documentID,
pageID)
if err != nil {
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "get page links")
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) {
revised := time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='document' AND orgid=? AND targetdocumentid=?")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("UPDATE link SET orphan=1, revised=? WHERE linktype='document' AND orgid=? AND targetdocumentid=?",
revised, ctx.OrgID, documentID)
if err != nil {
return
err = errors.Wrap(err, "mark link as orphan")
}
_, err = stmt.Exec(revised, ctx.OrgID, documentID)
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) {
revised := time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='section' AND orgid=? AND targetid=?")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("UPDATE link SET orphan=1, revised=? WHERE linktype='section' AND orgid=? AND targetid=?", revised, ctx.OrgID, pageID)
if err != nil {
return
err = errors.Wrap(err, "mark orphan page link")
}
_, err = stmt.Exec(revised, ctx.OrgID, pageID)
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) {
revised := time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='file' AND orgid=? AND targetid=?")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("UPDATE link SET orphan=1, revised=? WHERE linktype='file' AND orgid=? AND targetid=?",
revised, ctx.OrgID, attachmentID)
if err != nil {
return
err = errors.Wrap(err, "mark orphan attachment link")
}
_, err = stmt.Exec(revised, ctx.OrgID, attachmentID)
return
}
@ -169,21 +157,19 @@ func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (doc
keywords = strings.TrimSpace(strings.ToLower(keywords))
likeQuery := "LOWER(title) LIKE '%" + keywords + "%'"
err = s.Runtime.Db.Select(&temp,
`SELECT d.refid as documentid, d. labelid as folderid, d.title, l.label as context
FROM document d LEFT JOIN label l ON d.labelid=l.refid WHERE l.orgid=? AND `+likeQuery+` AND d.labelid IN
(SELECT refid FROM label WHERE orgid=? AND 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)
err = s.Runtime.Db.Select(&temp, `
SELECT d.refid as documentid, d. labelid as folderid, d.title, l.label as context
FROM document d LEFT JOIN label l ON d.labelid=l.refid WHERE l.orgid=? AND `+likeQuery+`
AND d.labelid IN
(
SELECT refid FROM label WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? 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 title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err != nil {
err = errors.Wrap(err, "execute search links 1")
@ -210,19 +196,17 @@ func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (doc
err = s.Runtime.Db.Select(&temp,
`SELECT p.refid as targetid, p.documentid as documentid, p.title as title, p.pagetype as linktype, d.title as context, d.labelid as folderid
FROM page p LEFT JOIN document d ON d.refid=p.documentid WHERE p.orgid=? AND `+likeQuery+` AND d.labelid IN
(SELECT refid FROM label WHERE orgid=? AND 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 p.title`,
ctx.OrgID,
ctx.OrgID,
ctx.UserID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID)
FROM page p LEFT JOIN document d ON d.refid=p.documentid WHERE p.orgid=? AND `+likeQuery+`
AND d.labelid IN
(
SELECT refid FROM label WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? 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 p.title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err != nil {
err = errors.Wrap(err, "execute search links 2")
@ -249,19 +233,17 @@ func (s Scope) SearchCandidates(ctx domain.RequestContext, keywords string) (doc
err = s.Runtime.Db.Select(&temp,
`SELECT a.refid as targetid, a.documentid as documentid, a.filename as title, a.extension as context, d.labelid as folderid
FROM attachment a LEFT JOIN document d ON d.refid=a.documentid WHERE a.orgid=? AND `+likeQuery+` AND d.labelid IN
(SELECT refid FROM label WHERE orgid=? AND 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 a.filename`,
ctx.OrgID,
ctx.OrgID,
ctx.UserID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID)
FROM attachment a LEFT JOIN document d ON d.refid=a.documentid WHERE a.orgid=? AND `+likeQuery+`
AND d.labelid IN
(
SELECT refid FROM label WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND whoid=? 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 a.filename`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err != nil {
err = errors.Wrap(err, "execute search links 3")

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.
func (m *Mailer) ShareFolderExistingUser(recipient, inviter, url, folder, intro string) {
method := "ShareFolderExistingUser"
// ShareSpaceExistingUser provides an existing user with a link to a newly shared space.
func (m *Mailer) ShareSpaceExistingUser(recipient, inviter, url, folder, intro string) {
method := "ShareSpaceExistingUser"
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 {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
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.
func (m *Mailer) ShareFolderNewUser(recipient, inviter, url, folder, invitationMessage string) {
method := "ShareFolderNewUser"
// ShareSpaceNewUser invites new user providing Credentials, explaining the product and stating who is inviting them.
func (m *Mailer) ShareSpaceNewUser(recipient, inviter, url, space, invitationMessage string) {
method := "ShareSpaceNewUser"
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 {
m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return
@ -236,7 +236,7 @@ func (m *Mailer) ShareFolderNewUser(recipient, inviter, url, folder, invitationM
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.From = m.Credentials.SMTPsender
@ -254,7 +254,7 @@ func (m *Mailer) ShareFolderNewUser(recipient, inviter, url, folder, invitationM
inviter,
url,
invitationMessage,
folder,
space,
}
buffer := new(bytes.Buffer)

View file

@ -22,7 +22,6 @@ import (
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/org"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
@ -32,25 +31,17 @@ type Scope struct {
}
// 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.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex(
"INSERT INTO organization (refid, company, title, message, url, domain, email, allowanonymousaccess, serial, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
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),
_, err = ctx.Transaction.Exec(
"INSERT INTO organization (refid, company, title, message, url, domain, email, allowanonymousaccess, serial, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
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)
if err != nil {
err = errors.Wrap(err, "unable to execute insert for org")
return err
}
return nil
@ -88,33 +79,18 @@ func (s Scope) GetOrganizationByDomain(subdomain string) (o org.Organization, er
return
}
var stmt *sqlx.Stmt
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")
defer streamutil.Close(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",
subdomain)
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 {
return
}
// 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")
defer streamutil.Close(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")
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 {
err = errors.Wrap(err, "unable to execute select for empty subdomain")
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) {
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")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.NamedExec("UPDATE organization SET title=:title, message=:message, service=:conversionendpoint, email=:email, allowanonymousaccess=:allowanonymousaccess, revised=:revised WHERE refid=:refid",
&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 {
err = errors.Wrap(err, fmt.Sprintf("unable to execute update for org %s", org.RefID))
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.
func (s Scope) RemoveOrganization(ctx domain.RequestContext, orgID string) (err error) {
stmt, err := ctx.Transaction.Preparex("UPDATE organization SET active=0 WHERE refid=?")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("UPDATE organization SET active=0 WHERE refid=?", orgID)
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 {
err = errors.Wrap(err, fmt.Sprintf("unable to execute soft delete for org %s", orgID))
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) {
org.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.PrepareNamed("UPDATE organization SET allowanonymousaccess=:allowanonymousaccess, authprovider=:authprovider, authconfig=:authconfig, revised=:revised WHERE refid=:refid")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.NamedExec("UPDATE organization SET allowanonymousaccess=:allowanonymousaccess, authprovider=:authprovider, authconfig=:authconfig, revised=:revised WHERE refid=:refid",
&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 {
err = errors.Wrap(err, fmt.Sprintf("unable to execute UpdateAuthConfig %s", org.RefID))
return
}
return

View file

@ -25,8 +25,8 @@ import (
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/document"
"github.com/documize/community/domain/link"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/section/provider"
"github.com/documize/community/model/activity"
@ -59,7 +59,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -165,7 +165,7 @@ func (h *Handler) GetPage(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanViewDocument(ctx, *h.Store, documentID) {
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -200,7 +200,7 @@ func (h *Handler) GetPages(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanViewDocument(ctx, *h.Store, documentID) {
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -227,47 +227,6 @@ func (h *Handler) GetPages(w http.ResponseWriter, r *http.Request) {
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.
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
method := "page.delete"
@ -290,7 +249,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -366,7 +325,7 @@ func (h *Handler) DeletePages(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -471,7 +430,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -617,7 +576,7 @@ func (h *Handler) ChangePageSequence(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -678,7 +637,7 @@ func (h *Handler) ChangePageLevel(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -740,7 +699,7 @@ func (h *Handler) GetMeta(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanViewDocument(ctx, *h.Store, documentID) {
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -816,7 +775,7 @@ func (h *Handler) Copy(w http.ResponseWriter, r *http.Request) {
}
// permission
if !document.CanViewDocument(ctx, *h.Store, documentID) {
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -922,7 +881,7 @@ func (h *Handler) GetDocumentRevisions(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanViewDocument(ctx, *h.Store, documentID) {
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -953,7 +912,7 @@ func (h *Handler) GetRevisions(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanViewDocument(ctx, *h.Store, documentID) {
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -1000,7 +959,7 @@ func (h *Handler) GetDiff(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanViewDocument(ctx, *h.Store, documentID) {
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -1064,7 +1023,7 @@ func (h *Handler) Rollback(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}

View file

@ -12,16 +12,14 @@
package mysql
import (
"database/sql"
"fmt"
"strings"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/page"
"github.com/jmoiron/sqlx"
"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
}
stmt, err := ctx.Transaction.Preparex("INSERT INTO page (refid, orgid, documentid, userid, contenttype, pagetype, level, title, body, revisions, sequence, blockid, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("INSERT INTO page (refid, orgid, documentid, userid, contenttype, pagetype, level, title, body, revisions, sequence, blockid, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
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, "prepare page insert")
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)
_, err = ctx.Transaction.Exec("INSERT INTO pagemeta (pageid, orgid, userid, documentid, rawbody, config, externalsource, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
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 {
err = errors.Wrap(err, "execute page meta insert")
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.
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=?")
defer streamutil.Close(stmt)
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=?",
ctx.OrgID, pageID)
if err != nil {
err = errors.Wrap(err, "prepare get page")
return
}
err = stmt.Get(&p, ctx.OrgID, pageID)
if err != nil {
err = errors.Wrap(err, "execute get page")
return
}
return
@ -113,59 +85,6 @@ func (s Scope) GetPages(ctx domain.RequestContext, documentID string) (p []page.
if err != nil {
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
@ -178,7 +97,6 @@ func (s Scope) GetPagesWithoutContent(ctx domain.RequestContext, documentID stri
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("Unable to execute select pages for org %s and document %s", ctx.OrgID, documentID))
return
}
return
@ -191,17 +109,9 @@ func (s Scope) Update(ctx domain.RequestContext, page page.Page, refID, userID s
// Store revision history
if !skipRevision {
var stmt *sqlx.Stmt
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")
_, 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",
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 {
err = errors.Wrap(err, "execute page revision insert")
return err
@ -209,16 +119,9 @@ func (s Scope) Update(ctx domain.RequestContext, page page.Page, refID, userID s
}
// Update page
var stmt2 *sqlx.NamedStmt
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")
defer streamutil.Close(stmt2)
_, 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",
&page)
if err != nil {
err = errors.Wrap(err, "prepare page insert")
return
}
_, err = stmt2.Exec(&page)
if err != nil {
err = errors.Wrap(err, "execute page insert")
return
@ -226,18 +129,10 @@ func (s Scope) Update(ctx domain.RequestContext, page page.Page, refID, userID s
// Update revisions counter
if !skipRevision {
stmt3, err := ctx.Transaction.Preparex("UPDATE page SET revisions=revisions+1 WHERE orgid=? AND refid=?")
defer streamutil.Close(stmt3)
_, err = ctx.Transaction.Exec("UPDATE page SET revisions=revisions+1 WHERE orgid=? AND refid=?", ctx.OrgID, page.RefID)
if err != nil {
err = errors.Wrap(err, "prepare page revision counter")
return err
}
_, err = stmt3.Exec(ctx.OrgID, page.RefID)
if err != nil {
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
}
var stmt *sqlx.NamedStmt
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")
defer streamutil.Close(stmt)
_, 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",
&meta)
if err != nil {
err = errors.Wrap(err, "prepare page meta update")
return
}
_, err = stmt.Exec(&meta)
if err != nil {
err = errors.Wrap(err, "execute page meta update")
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.
// 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) {
stmt, err := ctx.Transaction.Preparex("UPDATE page SET sequence=? WHERE orgid=? AND refid=?")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("UPDATE page SET sequence=? WHERE orgid=? AND refid=?", sequence, ctx.OrgID, pageID)
if err != nil {
err = errors.Wrap(err, "prepare page sequence update")
return
}
_, err = stmt.Exec(sequence, ctx.OrgID, pageID)
if err != nil {
err = errors.Wrap(err, "execute page sequence update")
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.
// 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) {
stmt, err := ctx.Transaction.Preparex("UPDATE page SET level=? WHERE orgid=? AND refid=?")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("UPDATE page SET level=? WHERE orgid=? AND refid=?", level, ctx.OrgID, pageID)
if err != nil {
err = errors.Wrap(err, "prepare page level update")
return
}
_, err = stmt.Exec(level, ctx.OrgID, pageID)
if err != nil {
err = errors.Wrap(err, "execute page level update")
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.
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=?")
defer streamutil.Close(stmt)
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=?",
ctx.OrgID, pageID)
if err != nil {
err = errors.Wrap(err, "prepare get page meta")
return
}
err = stmt.Get(&meta, ctx.OrgID, pageID)
if err != nil {
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "execute get page meta")
return
}
return
@ -353,7 +217,6 @@ func (s Scope) GetDocumentPageMeta(ctx domain.RequestContext, documentID string,
if err != nil {
err = errors.Wrap(err, "get document page meta")
return
}
return
@ -365,18 +228,11 @@ func (s Scope) GetDocumentPageMeta(ctx domain.RequestContext, documentID string,
// GetPageRevision returns the revisionID page revision record.
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=?")
defer streamutil.Close(stmt)
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=?",
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 {
err = errors.Wrap(err, "execute get page revisions")
return
}
return
@ -389,7 +245,6 @@ func (s Scope) GetPageRevisions(ctx domain.RequestContext, pageID string) (revis
if err != nil {
err = errors.Wrap(err, "get page revisions")
return
}
return
@ -400,22 +255,21 @@ func (s Scope) GetPageRevisions(ctx domain.RequestContext, pageID string) (revis
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)
if err != nil {
err = errors.Wrap(err, "get document revisions")
return
}
if len(revisions) == 0 {
revisions = []page.Revision{}
}
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "get document revisions")
}
return
}
// DeletePageRevisions deletes all of the page revision records for a given pageID.
func (s Scope) DeletePageRevisions(ctx domain.RequestContext, pageID string) (rows int64, err error) {
b := mysql.BaseQuery{}
rows, err =b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM revision WHERE orgid='%s' AND pageid='%s'", ctx.OrgID, pageID))
rows, err = b.DeleteWhere(ctx.Transaction, fmt.Sprintf("DELETE FROM revision WHERE orgid='%s' AND pageid='%s'", ctx.OrgID, pageID))
return
}
@ -432,4 +286,4 @@ func (s Scope) GetNextPageSequence(ctx domain.RequestContext, documentID string)
maxSeq = maxSeq * 2
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"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/pin"
"github.com/jmoiron/sqlx"
"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.Sequence = maxSeq + 1
stmt, err := ctx.Transaction.Preparex("INSERT INTO pin (refid, orgid, userid, labelid, documentid, pin, sequence, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("INSERT INTO pin (refid, orgid, userid, labelid, documentid, pin, sequence, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
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 {
err = errors.Wrap(err, "execute pin insert")
return
}
return
@ -62,18 +53,11 @@ func (s Scope) Add(ctx domain.RequestContext, pin pin.Pin) (err error) {
// GetPin returns requested pinned item.
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=?")
defer streamutil.Close(stmt)
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=?",
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 {
err = errors.Wrap(err, fmt.Sprintf("execute select for pin %s", id))
return
}
return
@ -85,7 +69,6 @@ func (s Scope) GetUserPins(ctx domain.RequestContext, userID string) (pins []pin
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute select pins for org %s and user %s", ctx.OrgID, userID))
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) {
pin.Revised = time.Now().UTC()
var stmt *sqlx.NamedStmt
stmt, err = ctx.Transaction.PrepareNamed("UPDATE pin SET labelid=:folderid, documentid=:documentid, pin=:pin, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.NamedExec("UPDATE pin SET labelid=:folderid, documentid=:documentid, pin=:pin, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid",
&pin)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare pin update %s", pin.RefID))
return
}
_, err = stmt.Exec(&pin)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute pin update %s", pin.RefID))
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
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=?")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("UPDATE pin SET sequence=?, revised=? WHERE orgid=? AND userid=? AND refid=?",
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 {
err = errors.Wrap(err, fmt.Sprintf("execute pin sequence update %s", pinID))
return
}
return

View file

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

View file

@ -20,7 +20,7 @@ import (
"github.com/documize/community/core/response"
"github.com/documize/community/core/uniqueid"
"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/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.
// Permission checks
if !document.CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
@ -86,7 +86,7 @@ func (h *Handler) RefreshSections(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanViewDocument(ctx, *h.Store, documentID) {
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}

View file

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

View file

@ -16,7 +16,6 @@ import (
"database/sql"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/pkg/errors"
)
@ -30,18 +29,13 @@ func (s Scope) Get(area, path string) (value string, err error) {
if path != "" {
path = "." + path
}
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)
err = stmt.Get(&item)
err = s.Runtime.Db.Get(&item, sql)
if err != nil {
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.
func (s Scope) Set(area, json string) error {
func (s Scope) Set(area, json string) (err error) {
if area == "" {
return errors.New("no area")
}
@ -64,15 +58,8 @@ func (s Scope) Set(area, json string) error {
"VALUES ('" + area + "','" + json +
"') ON DUPLICATE KEY UPDATE `config`='" + json + "';"
stmt, err := s.Runtime.Db.Preparex(sql)
defer streamutil.Close(stmt)
_, err = s.Runtime.Db.Exec(sql)
if err != nil {
err = errors.Wrap(err, "failed to save global config value")
return err
}
_, err = stmt.Exec()
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 +
"' 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)
err = stmt.Get(&item)
err = s.Runtime.Db.Get(&item, qry)
if err != nil && err != sql.ErrNoRows {
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.
func (s Scope) SetUser(orgID, userID, area, json string) error {
func (s Scope) SetUser(orgID, userID, area, json string) (err error) {
if 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 +
"') ON DUPLICATE KEY UPDATE `config`='" + json + "';"
stmt, err := s.Runtime.Db.Preparex(sql)
defer streamutil.Close(stmt)
if err != nil {
return err
}
_, err = stmt.Exec()
_, err = s.Runtime.Db.Exec(sql)
return err
}

View file

@ -35,8 +35,8 @@ import (
"github.com/documize/community/model/audit"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/page"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/space"
"github.com/documize/community/model/user"
uuid "github.com/nu7hatch/gouuid"
)
@ -48,7 +48,7 @@ type Handler struct {
// Add creates a new space.
func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
method := "space.Add"
method := "space.add"
ctx := domain.GetRequestContext(r)
if !h.Runtime.Product.License.IsValid() {
@ -106,15 +106,16 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
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()
perm := permission.Permission{}
perm.OrgID = sp.OrgID
perm.Who = "user"
perm.WhoID = ctx.UserID
perm.Scope = "object"
perm.Location = "space"
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 {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -138,7 +139,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return
}
spCloneRoles, err := h.Store.Space.GetRoles(ctx, model.CloneID)
spCloneRoles, err := h.Store.Permission.GetSpacePermissions(ctx, model.CloneID)
if err != nil {
response.WriteServerError(w, 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 {
for _, r := range spCloneRoles {
r.RefID = uniqueid.Generate()
r.LabelID = sp.RefID
r.RefID = sp.RefID
err = h.Store.Space.AddRole(ctx, r)
err = h.Store.Permission.AddPermission(ctx, r)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -276,12 +276,12 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
// Get returns the requested space.
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
method := "Get"
method := "space.get"
ctx := domain.GetRequestContext(r)
id := request.Param(r, "folderID")
id := request.Param(r, "spaceID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID")
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -300,11 +300,36 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, sp)
}
// GetAll returns spaces the user can see.
func (h *Handler) GetAll(w http.ResponseWriter, r *http.Request) {
method := "GetAll"
// GetAlGetViewablel returns spaces the user can see.
func (h *Handler) GetViewable(w http.ResponseWriter, r *http.Request) {
method := "space.GetViewable"
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)
if err != nil && err != sql.ErrNoRows {
@ -320,28 +345,9 @@ func (h *Handler) GetAll(w http.ResponseWriter, r *http.Request) {
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
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
method := "space.Update"
method := "space.update"
ctx := domain.GetRequestContext(r)
if !ctx.Editor {
@ -349,9 +355,9 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
folderID := request.Param(r, "folderID")
if len(folderID) == 0 {
response.WriteMissingDataError(w, method, "folderID")
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -377,7 +383,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
sp.RefID = folderID
sp.RefID = spaceID
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
@ -401,9 +407,9 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
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) {
method := "space.Remove"
method := "space.remove"
ctx := domain.GetRequestContext(r)
if !h.Runtime.Product.License.IsValid() {
@ -416,9 +422,9 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
return
}
id := request.Param(r, "folderID")
id := request.Param(r, "spaceID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID")
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -436,14 +442,6 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
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)
if err != nil {
ctx.Transaction.Rollback()
@ -452,7 +450,23 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
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 {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -475,9 +489,9 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
response.WriteEmpty(w)
}
// Delete deletes empty space.
// Delete removes space.
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
method := "space.Delete"
method := "space.delete"
ctx := domain.GetRequestContext(r)
if !h.Runtime.Product.License.IsValid() {
@ -490,9 +504,9 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
id := request.Param(r, "folderID")
id := request.Param(r, "spaceID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID")
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -504,7 +518,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
_, err = h.Store.Space.Delete(ctx, id)
_, err = h.Store.Document.DeleteBySpace(ctx, id)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -512,7 +526,16 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
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 {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -528,6 +551,23 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
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)
ctx.Transaction.Commit()
@ -535,222 +575,15 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
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.
func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
method := "space.AcceptInvitation"
ctx := domain.GetRequestContext(r)
ctx.Subdomain = organization.GetSubdomainFromHost(r)
folderID := request.Param(r, "folderID")
if len(folderID) == 0 {
response.WriteMissingDataError(w, method, "folderID")
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -831,14 +664,14 @@ func (h *Handler) AcceptInvitation(w http.ResponseWriter, r *http.Request) {
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) {
method := "space.Invite"
ctx := domain.GetRequestContext(r)
id := request.Param(r, "folderID")
id := request.Param(r, "spaceID")
if len(id) == 0 {
response.WriteMissingDataError(w, method, "folderID")
response.WriteMissingDataError(w, method, "spaceID")
return
}
@ -917,6 +750,7 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
a.OrgID = ctx.OrgID
a.Admin = false
a.Editor = false
a.Users = false
a.Active = true
accountID := uniqueid.Generate()
a.RefID = accountID
@ -931,18 +765,18 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
}
// 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{}
role.LabelID = sp.RefID
role.OrgID = ctx.OrgID
role.UserID = u.RefID
role.CanEdit = false
role.CanView = true
roleID := uniqueid.Generate()
role.RefID = roleID
perm := permission.Permission{}
perm.OrgID = sp.OrgID
perm.Who = "user"
perm.WhoID = u.RefID
perm.Scope = "object"
perm.Location = "space"
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.SpaceView)
if err != nil {
ctx.Transaction.Rollback()
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)))
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))
} 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 {
sp.Type = space.ScopeRestricted

View file

@ -13,12 +13,10 @@
package mysql
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/space"
@ -36,18 +34,11 @@ func (s Scope) Add(ctx domain.RequestContext, sp space.Space) (err error) {
sp.Created = time.Now().UTC()
sp.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO label (refid, label, orgid, userid, type, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("INSERT INTO label (refid, label, orgid, userid, type, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?)",
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 {
err = errors.Wrap(err, "unable to execute insert for label")
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.
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=?")
defer streamutil.Close(stmt)
err = s.Runtime.Db.Get(&sp, "SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label WHERE orgid=? and refid=?",
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 {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select for label %s", id))
return
}
return
@ -80,37 +64,50 @@ func (s Scope) PublicSpaces(ctx domain.RequestContext, orgID string) (sp []space
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("Unable to execute GetPublicFolders for org %s", orgID))
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.
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 := `
(SELECT id,refid,label as name,orgid,userid,type,created,revised from label WHERE orgid=? AND type=2 AND userid=?)
UNION ALL
(SELECT id,refid,label as name,orgid,userid,type,created,revised 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 id,refid,label as name,orgid,userid,type,created,revised 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 id,refid,label as name,orgid,userid,type,created,revised FROM label
WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' 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 name`
err = s.Runtime.Db.Select(&sp, sql,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("Unable to execute select labels for org %s", ctx.OrgID))
return
err = errors.Wrap(err, fmt.Sprintf("failed space.GetViewable org %s", ctx.OrgID))
}
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
@ -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) {
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")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.NamedExec("UPDATE label SET label=:name, type=:type, userid=:userid, revised=:revised WHERE orgid=:orgid AND refid=:refid", &sp)
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 {
err = errors.Wrap(err, fmt.Sprintf("unable to execute update for label %s", sp.RefID))
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.
func (s Scope) Delete(ctx domain.RequestContext, id string) (rows int64, err error) {
b := mysql.BaseQuery{}
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/mail"
"github.com/documize/community/model/account"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/space"
"github.com/documize/community/model/user"
)
// addSpace prepares and creates space record.
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.
// Invite new user to a space that someone has shared with them.
// We create the user account with default values and then take them
// 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,
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.Admin = false
a.Editor = false
a.Users = false
a.Active = true
accountID := uniqueid.Generate()
a.RefID = accountID
a.RefID = uniqueid.Generate()
err = s.Account.Add(ctx, a)
if err != nil {
return
}
role := space.Role{}
role.LabelID = sp.RefID
role.OrgID = ctx.OrgID
role.UserID = userID
role.CanEdit = false
role.CanView = true
roleID := uniqueid.Generate()
role.RefID = roleID
perm := permission.Permission{}
perm.OrgID = sp.OrgID
perm.Who = "user"
perm.WhoID = userID
perm.Scope = "object"
perm.Location = "space"
perm.RefID = sp.RefID
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 {
return
}
@ -101,7 +79,7 @@ func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *d
mailer := mail.Mailer{Runtime: rt, Store: s, Context: ctx}
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
}

View file

@ -1,7 +1,6 @@
package space
import (
"database/sql"
"testing"
"github.com/documize/community/core/uniqueid"
@ -39,17 +38,19 @@ func TestSpace(t *testing.T) {
t.Error("failed to add sp space")
}
r.RefID = uniqueid.Generate()
r.LabelID = spaceID
r.OrgID = ctx.OrgID
r.UserID = "testAddSpace"
r.CanView = true
r.CanEdit = true
perm := space.Permission{}
perm.OrgID = ctx.OrgID
perm.Who = "user"
perm.WhoID = ctx.UserID
perm.Scope = "object"
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 {
ctx.Transaction.Rollback()
t.Error("failed to add role r")
t.Error("failed to add permission")
}
ctx.Transaction.Commit()
@ -104,17 +105,19 @@ func TestSpace(t *testing.T) {
t.Error("failed to add sp2")
}
r2.RefID = uniqueid.Generate()
r2.LabelID = spaceID2
r2.OrgID = ctx.OrgID
r2.UserID = ctx.UserID
r2.CanView = true
r2.CanEdit = true
perm := space.Permission{}
perm.OrgID = ctx.OrgID
perm.Who = "user"
perm.WhoID = ctx.UserID
perm.Scope = "object"
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 {
ctx.Transaction.Rollback()
t.Error("failed to add role")
t.Error("failed to add permission")
}
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) {
ctx.Transaction, err = rt.Db.Beginx()
r3.CanView = true
r3.CanEdit = true
r3.RefID = uniqueid.Generate()
r3.LabelID = spaceID
r3.OrgID = ctx.OrgID
r3.UserID = "testAddRole"
perm := space.Permission{}
perm.OrgID = ctx.OrgID
perm.Who = "user"
perm.WhoID = ctx.UserID
perm.Scope = "object"
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 {
ctx.Transaction.Rollback()
t.Error("failed to add role")
return
t.Error("failed to add permission")
}
ctx.Transaction.Commit()
roles, err := s.Space.GetRoles(ctx, spaceID)
roles, err := s.Space.GetUserPermissions(ctx, spaceID)
if err != nil || roles == nil {
t.Error("Could not get any roles")
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?
})
t.Run("Get User Roles", func(t *testing.T) {
userRoles, err := s.Space.GetUserRoles(ctx)
t.Run("Get User Permissions", func(t *testing.T) {
userRoles, err := s.Space.GetUserPermissions(ctx, spaceID)
if err != nil || userRoles == nil {
t.Error("failed to get user roles")
return
}
})
t.Run("Move space Roles", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
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) {
// teardown
t.Run("Delete space", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
_, err = s.Space.Delete(ctx, spaceID)
@ -261,11 +213,7 @@ func TestSpace(t *testing.T) {
ctx.Transaction.Commit()
})
//
// teardown code goes here
//
t.Run("Delete sp2 Space", func(t *testing.T) {
t.Run("Delete space 2", func(t *testing.T) {
ctx.Transaction, err = rt.Db.Beginx()
_, err = s.Space.Delete(ctx, spaceID2)
@ -277,15 +225,4 @@ func TestSpace(t *testing.T) {
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 (
"fmt"
"github.com/documize/community/core/streamutil"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
@ -25,15 +24,8 @@ type BaseQuery struct {
// Delete record.
func (m *BaseQuery) Delete(tx *sqlx.Tx, table string, id string) (rows int64, err error) {
stmt, err := tx.Preparex("DELETE FROM " + table + " WHERE refid=?")
defer streamutil.Close(stmt)
result, err := tx.Exec("DELETE FROM "+table+" WHERE refid=?", id)
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 {
err = errors.Wrap(err, fmt.Sprintf("unable to delete row in table %s", table))
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.
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=?")
defer streamutil.Close(stmt)
result, err := tx.Exec("DELETE FROM "+table+" WHERE orgid=? AND refid=?", orgID, id)
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 {
err = errors.Wrap(err, fmt.Sprintf("unable to delete row in table %s", table))
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.
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=?")
defer streamutil.Close(stmt)
result, err := tx.Exec("DELETE FROM "+table+" WHERE orgid=? AND id=?", orgID, id)
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 {
err = errors.Wrap(err, fmt.Sprintf("unable to delete row in table %s", table))
return
@ -89,6 +67,7 @@ func (m *BaseQuery) DeleteConstrainedWithID(tx *sqlx.Tx, table string, orgID, id
// DeleteWhere free form query.
func (m *BaseQuery) DeleteWhere(tx *sqlx.Tx, statement string) (rows int64, err error) {
result, err := tx.Exec(statement)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to delete rows: %s", statement))
return

View file

@ -18,10 +18,12 @@ import (
"github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/block"
"github.com/documize/community/model/category"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/link"
"github.com/documize/community/model/org"
"github.com/documize/community/model/page"
"github.com/documize/community/model/permission"
"github.com/documize/community/model/pin"
"github.com/documize/community/model/search"
"github.com/documize/community/model/space"
@ -35,11 +37,13 @@ type Store struct {
Attachment AttachmentStorer
Audit AuditStorer
Block BlockStorer
Category CategoryStorer
Document DocumentStorer
Link LinkStorer
Organization OrganizationStorer
Page PageStorer
Pin PinStorer
Permission PermissionStorer
Search SearchStorer
Setting SettingStorer
Space SpaceStorer
@ -51,18 +55,45 @@ type SpaceStorer interface {
Add(ctx RequestContext, 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)
GetViewable(ctx RequestContext) (sp []space.Space, err error)
GetAll(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)
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)
DeleteRole(ctx RequestContext, roleID string) (rows int64, err error)
DeleteSpaceRoles(ctx RequestContext, spaceID string) (rows int64, err error)
DeleteUserSpaceRoles(ctx RequestContext, spaceID, userID string) (rows int64, err error)
MoveSpaceRoles(ctx RequestContext, previousLabel, newLabel string) (err error)
}
// CategoryStorer defines required methods for category and category membership management
type CategoryStorer interface {
Add(ctx RequestContext, c category.Category) (err error)
Update(ctx RequestContext, c category.Category) (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
@ -75,8 +106,8 @@ type UserStorer interface {
GetBySerial(ctx RequestContext, serial string) (u user.User, err error)
GetActiveUsersForOrganization(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)
GetVisibleUsers(ctx RequestContext) (u []user.User, err error)
GetSpaceUsers(ctx RequestContext, spaceID string) (u []user.User, err error)
GetUsersForSpaces(ctx RequestContext, spaces []string) (u []user.User, err error)
UpdateUser(ctx RequestContext, u user.User) (err error)
UpdateUserPassword(ctx RequestContext, userID, salt, password 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)
Get(ctx RequestContext, id string) (document doc.Document, err error)
GetAll() (ctx RequestContext, documents []doc.Document, err error)
GetBySpace(ctx RequestContext, folderID string) (documents []doc.Document, err error)
GetByTag(ctx RequestContext, tag string) (documents []doc.Document, err error)
GetBySpace(ctx RequestContext, spaceID string) (documents []doc.Document, err error)
DocumentList(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)
@ -141,6 +171,7 @@ type DocumentStorer interface {
ChangeDocumentSpace(ctx RequestContext, document, space string) (err error)
MoveDocumentSpace(ctx RequestContext, id, move string) (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
@ -214,7 +245,6 @@ type PageStorer interface {
Add(ctx RequestContext, model page.NewPage) (err error)
Get(ctx RequestContext, pageID 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)
Update(ctx RequestContext, page page.Page, refID, userID string, skipRevision 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/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/document"
"github.com/documize/community/domain/permission"
indexer "github.com/documize/community/domain/search"
"github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/page"
pm "github.com/documize/community/model/permission"
"github.com/documize/community/model/template"
uuid "github.com/nu7hatch/gouuid"
)
@ -112,21 +113,21 @@ func (h *Handler) SaveAs(w http.ResponseWriter, r *http.Request) {
return
}
if !document.CanChangeDocument(ctx, *h.Store, model.DocumentID) {
response.WriteForbiddenError(w)
return
}
// DB transaction
ctx.Transaction, err = h.Runtime.Db.Beginx()
// Duplicate document
doc, err := h.Store.Document.Get(ctx, model.DocumentID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Duplicate document
doc, err := h.Store.Document.Get(ctx, model.DocumentID)
if !permission.HasPermission(ctx, *h.Store, doc.LabelID, pm.DocumentTemplate) {
response.WriteForbiddenError(w)
return
}
// DB transaction
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, 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/model/account"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/space"
"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)
return
}
} else {
u, err = h.Store.User.GetUsersForOrganization(ctx)
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 err error
folderID := request.Param(r, "folderID")
if len(folderID) == 0 {
response.WriteMissingDataError(w, method, "folderID")
spaceID := request.Param(r, "spaceID")
if len(spaceID) == 0 {
response.WriteMissingDataError(w, method, "spaceID")
return
}
// check to see space type as it determines user selection criteria
folder, err := h.Store.Space.Get(ctx, folderID)
// Get user account as we need to know if user can see all users.
account, err := h.Store.Account.GetUserAccount(ctx, ctx.UserID)
if err != nil && err != sql.ErrNoRows {
response.WriteJSON(w, u)
h.Runtime.Log.Error(method, err)
return
}
switch folder.Type {
case space.ScopePublic:
// account.users == false means we restrict viewing to just space users
if account.Users {
// can see all users
u, err = h.Store.User.GetActiveUsersForOrganization(ctx)
break
case space.ScopePrivate:
// just me
var me user.User
me, err = h.Store.User.Get(ctx, ctx.UserID)
u = append(u, me)
break
case space.ScopeRestricted:
u, err = h.Store.User.GetSpaceUsers(ctx, folderID)
break
if err != nil && err != sql.ErrNoRows {
response.WriteJSON(w, u)
h.Runtime.Log.Error(method, err)
return
}
} else {
// send back existing space users
u, err = h.Store.User.GetSpaceUsers(ctx, spaceID)
if err != nil && err != sql.ErrNoRows {
response.WriteJSON(w, u)
h.Runtime.Log.Error(method, err)
return
}
}
if len(u) == 0 {
u = []user.User{}
}
if err != nil && err != sql.ErrNoRows {
response.WriteJSON(w, u)
h.Runtime.Log.Error(method, err)
return
}
response.WriteJSON(w, u)
}
@ -377,7 +373,8 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
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 {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
@ -467,6 +464,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
a.Editor = u.Editor
a.Admin = u.Admin
a.Active = u.Active
a.Users = u.ViewUsers
err = h.Store.Account.UpdateAccount(ctx, a)
if err != nil {
@ -537,31 +535,6 @@ func (h *Handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
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.
// Generates a reset token and sends email to the user.
// User has to click link in email and then provide a new password.

View file

@ -18,9 +18,9 @@ import (
"time"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/domain"
"github.com/documize/community/model/user"
"github.com/jmoiron/sqlx"
"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.Revised = time.Now().UTC()
stmt, err := ctx.Transaction.Preparex("INSERT INTO user (refid, firstname, lastname, email, initials, password, salt, reset, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("INSERT INTO user (refid, firstname, lastname, email, initials, password, salt, reset, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
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 {
err = errors.Wrap(err, "execute user insert")
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.
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=?")
defer streamutil.Close(stmt)
err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE refid=?", id)
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 {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select for user %s", id))
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) {
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))=?")
defer streamutil.Close(stmt)
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))=?",
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 {
err = errors.Wrap(err, fmt.Sprintf("Unable to execute GetUserByDomain %s %s", domain, email))
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) {
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))=?")
defer streamutil.Close(stmt)
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)
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 {
err = errors.Wrap(err, fmt.Sprintf("execute select user by email %s", email))
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.
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=?")
defer streamutil.Close(stmt)
err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE reset=?", token)
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 {
err = errors.Wrap(err, fmt.Sprintf("execute user select by token %s", token))
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
// the onboarding process.
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=?")
defer streamutil.Close(stmt)
err = s.Runtime.Db.Get("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE salt=?", serial)
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 {
err = errors.Wrap(err, fmt.Sprintf("execute user select by serial %s", serial))
return
}
return
@ -156,14 +110,15 @@ func (s Scope) GetBySerial(ctx domain.RequestContext, serial string) (u user.Use
// identified in the Persister.
func (s Scope) GetActiveUsersForOrganization(ctx domain.RequestContext) (u []user.User, err error) {
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
FROM user u
WHERE u.refid IN (SELECT userid FROM account WHERE orgid = ? AND active=1) ORDER BY u.firstname,u.lastname`,
`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=? AND a.active=1
ORDER BY u.firstname,u.lastname`,
ctx.OrgID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("get active users by org %s", ctx.OrgID))
return
}
return
@ -173,114 +128,72 @@ func (s Scope) GetActiveUsersForOrganization(ctx domain.RequestContext) (u []use
// identified in the Persister.
func (s Scope) GetUsersForOrganization(ctx domain.RequestContext) (u []user.User, err error) {
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 {
err = errors.Wrap(err, fmt.Sprintf(" get users for org %s", ctx.OrgID))
return
}
return
}
// GetSpaceUsers returns a slice containing all user records for given folder.
func (s Scope) GetSpaceUsers(ctx domain.RequestContext, folderID string) (u []user.User, err error) {
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
// GetSpaceUsers returns a slice containing all user records for given space.
func (s Scope) GetSpaceUsers(ctx domain.RequestContext, spaceID string) (u []user.User, err error) {
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, u.global,
a.active, a.users AS viewusers, a.editor, a.admin
FROM user u, account a
WHERE u.refid IN (SELECT userid from labelrole WHERE orgid=? AND labelid=?)
AND a.orgid=? AND u.refid = a.userid AND a.active=1
ORDER BY u.firstname, u.lastname`,
ctx.OrgID, folderID, ctx.OrgID)
WHERE a.orgid=? AND u.refid = a.userid AND a.active=1 AND u.refid IN (
SELECT whoid from permission WHERE orgid=? AND who='user' AND scope='object' AND location='space' 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.scope='object' AND p.location='space' AND p.refid=?
)
ORDER BY u.firstname, u.lastname
`, ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, spaceID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("get space users for org %s", ctx.OrgID))
return
}
return
}
// GetVisibleUsers returns all users that can be "seen" by a user.
// "Seen" means users who share at least one space in common.
// Explicit access must be provided to a user in order to associate them
// as having access to a space. Simply marking a space as vieewable by "everyone" is not enough.
func (s Scope) GetVisibleUsers(ctx domain.RequestContext) (u []user.User, err error) {
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 = ?)
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 firstname, lastname`,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID)
// GetUsersForSpaces returns users with access to specified spaces.
func (s Scope) GetUsersForSpaces(ctx domain.RequestContext, spaces []string) (u []user.User, err error) {
query, args, err := sqlx.In(`
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
WHERE a.orgid=? AND u.refid = a.userid AND a.active=1 AND u.refid IN (
SELECT whoid from permission WHERE orgid=? AND who='user' AND scope='object' AND location='space' AND refid IN(?) 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.scope='object' AND p.location='space' AND p.refid IN(?)
)
ORDER BY u.firstname, u.lastname
`, ctx.OrgID, ctx.OrgID, spaces, ctx.OrgID, spaces)
query = s.Runtime.Db.Rebind(query)
err = s.Runtime.Db.Select(&u, query, args...)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("get visible users for org %s user %s", ctx.OrgID, ctx.UserID))
return
err = errors.Wrap(err, fmt.Sprintf("get users for spaces for user %s", ctx.UserID))
}
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.
func (s Scope) UpdateUser(ctx domain.RequestContext, u user.User) (err error) {
u.Revised = time.Now().UTC()
u.Email = strings.ToLower(u.Email)
stmt, err := ctx.Transaction.PrepareNamed(
"UPDATE user SET firstname=:firstname, lastname=:lastname, email=:email, revised=:revised, initials=:initials WHERE refid=:refid")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.NamedExec(
"UPDATE user SET firstname=:firstname, lastname=:lastname, email=:email, revised=:revised, initials=:initials WHERE refid=:refid", &u)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("prepare user update %s", u.RefID))
return
}
_, err = stmt.Exec(&u)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute user update %s", u.RefID))
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.
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=?")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("UPDATE user SET salt=?, password=?, reset='' WHERE refid=?",
salt, password, userID)
if err != nil {
err = errors.Wrap(err, "prepare user update")
return
}
_, err = stmt.Exec(salt, password, userID)
if err != nil {
err = errors.Wrap(err, "execute user update")
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.
func (s Scope) DeactiveUser(ctx domain.RequestContext, userID string) (err error) {
stmt, err := ctx.Transaction.Preparex("DELETE FROM account WHERE userid=? and orgid=?")
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "prepare user deactivation")
return
}
_, err = stmt.Exec(userID, ctx.OrgID)
_, err = ctx.Transaction.Exec("DELETE FROM account WHERE userid=? and orgid=?", userID, ctx.OrgID)
if err != nil {
err = errors.Wrap(err, "execute user deactivation")
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.
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)=?")
defer streamutil.Close(stmt)
_, err = ctx.Transaction.Exec("UPDATE user SET reset=?, password='' WHERE LOWER(email)=?", token, strings.ToLower(email))
if err != nil {
err = errors.Wrap(err, "prepare password reset")
return
}
_, err = stmt.Exec(token, strings.ToLower(email))
if err != nil {
err = errors.Wrap(err, "execute password reset")
return
}
return

View file

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

View file

@ -20,10 +20,12 @@ import (
attachment "github.com/documize/community/domain/attachment/mysql"
audit "github.com/documize/community/domain/audit/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"
link "github.com/documize/community/domain/link/mysql"
org "github.com/documize/community/domain/organization/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"
search "github.com/documize/community/domain/search/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.Audit = audit.Scope{Runtime: r}
s.Block = block.Scope{Runtime: r}
s.Category = category.Scope{Runtime: r}
s.Document = doc.Scope{Runtime: r}
s.Link = link.Scope{Runtime: r}
s.Organization = org.Scope{Runtime: r}
s.Page = page.Scope{Runtime: r}
s.Pin = pin.Scope{Runtime: r}
s.Permission = permission.Scope{Runtime: r}
s.Search = search.Scope{Runtime: r}
s.Setting = setting.Scope{Runtime: r}
s.Space = space.Scope{Runtime: r}

View file

@ -41,8 +41,8 @@ func main() {
// product details
rt.Product = env.ProdInfo{}
rt.Product.Major = "1"
rt.Product.Minor = "53"
rt.Product.Patch = "6"
rt.Product.Minor = "54"
rt.Product.Patch = "0"
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
rt.Product.Edition = "Community"
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 AuthProvider from '../../mixins/auth';
import DropdownMixin from '../../mixins/dropdown';
export default Ember.Component.extend(AuthProvider, {
export default Ember.Component.extend(AuthProvider, DropdownMixin, {
editUser: null,
deleteUser: null,
drop: null,
dropdown: null,
password: {},
filter: '',
filteredUsers: [],
@ -23,20 +24,22 @@ export default Ember.Component.extend(AuthProvider, {
hasSelectedUsers: false,
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('selected', false);
});
this.set('filteredUsers', this.users);
this.set('users', users);
this.set('filteredUsers', users);
},
willDestroyElement() {
let drop = this.get('drop');
if (is.not.null(drop)) {
drop.destroy();
}
this._super(...arguments);
this.destroyDropdown();
},
onKeywordChange: function () {
@ -71,7 +74,7 @@ export default Ember.Component.extend(AuthProvider, {
this.set('selectedUsers', su);
this.set('hasSelectedUsers', su.length > 0);
},
toggleActive(id) {
let user = this.users.findBy("id", id);
user.set('active', !user.get('active'));
@ -90,11 +93,17 @@ export default Ember.Component.extend(AuthProvider, {
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) {
let self = this;
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('password', {
password: "",
@ -103,6 +112,8 @@ export default Ember.Component.extend(AuthProvider, {
$(".edit-user-dialog").css("display", "block");
$("input").removeClass("error");
this.closeDropdown();
let drop = new Drop({
target: $(".edit-button-" + id)[0],
content: $(".edit-user-dialog")[0],
@ -116,7 +127,7 @@ export default Ember.Component.extend(AuthProvider, {
remove: false
});
self.set('drop', drop);
self.set('dropdown', drop);
drop.on('open', function () {
self.$("#edit-firstname").focus();
@ -128,6 +139,8 @@ export default Ember.Component.extend(AuthProvider, {
this.set('deleteUser', user);
$(".delete-user-dialog").css("display", "block");
this.closeDropdown();
let drop = new Drop({
target: $(".delete-button-" + id)[0],
content: $(".delete-user-dialog")[0],
@ -141,12 +154,11 @@ export default Ember.Component.extend(AuthProvider, {
remove: false
});
this.set('drop', drop);
this.set('dropdown', drop);
},
cancel() {
let drop = this.get('drop');
drop.close();
this.closeDropdown();
},
save() {
@ -166,8 +178,7 @@ export default Ember.Component.extend(AuthProvider, {
return;
}
let drop = this.get('drop');
drop.close();
this.closeDropdown();
this.attrs.onSave(user);
@ -188,14 +199,14 @@ export default Ember.Component.extend(AuthProvider, {
onBulkDelete() {
let su = this.get('selectedUsers');
su.forEach(userId => {
this.attrs.onDelete(userId);
});
this.set('selectedUsers', []);
this.set('hasSelectedUsers', false);
return true;
}
}

View file

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

View file

@ -10,9 +10,9 @@
// https://documize.com
import Ember from 'ember';
import tocUtil from '../../utils/toc';
import NotifierMixin from '../../mixins/notifier';
import TooltipMixin from '../../mixins/tooltip';
import tocUtil from '../../utils/toc';
export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
document: {},
@ -30,9 +30,9 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
return this.get('pages.length') === 0;
}),
didReceiveAttrs: function () {
didReceiveAttrs() {
this._super(...arguments);
this.set('showToc', is.not.undefined(this.get('pages')) && this.get('pages').get('length') > 0);
if (is.not.null(this.get('currentPageId'))) {
@ -40,9 +40,9 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
}
},
didRender: function () {
didRender() {
this._super(...arguments);
if (this.session.authenticated) {
this.addTooltip(document.getElementById("toc-up-button"));
this.addTooltip(document.getElementById("toc-down-button"));
@ -53,13 +53,13 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
didInsertElement() {
this._super(...arguments);
this.eventBus.subscribe('documentPageAdded', this, 'onDocumentPageAdded');
},
willDestroyElement() {
this._super(...arguments);
this.eventBus.unsubscribe('documentPageAdded');
this.destroyTooltips();
},
@ -77,7 +77,7 @@ export default Ember.Component.extend(NotifierMixin, TooltipMixin, {
let page = _.findWhere(toc, { id: pageId });
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.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);
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');
}
});

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,38 +31,31 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
name: "",
description: ""
},
tab: '',
init() {
this._super(...arguments);
if (is.empty(this.get('tab')) || is.undefined(this.get('tab'))) {
this.set('tab', 'index');
}
},
didReceiveAttrs() {
this._super(...arguments);
this.set('saveTemplate.name', this.get('document.name'));
this.set('saveTemplate.description', this.get('document.excerpt'));
this.set('pinState.pinId', this.get('pinned').isDocumentPinned(this.get('document.id')));
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: {
onChangeTab(tab) {
this.set('tab', tab);
},
onTagChange(tags) {
let doc = this.get('document');
doc.set('tags', tags);
this.get('documentService').save(doc);
},
onMenuOpen() {
this.set('menuOpen', !this.get('menuOpen'));
},
@ -78,15 +71,15 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
onPageSequenceChange(changes) {
this.get('onPageSequenceChange')(changes);
},
},
onPageLevelChange(changes) {
this.get('onPageLevelChange')(changes);
},
},
onGotoPage(id) {
this.get('onGotoPage')(id);
},
},
onUnpin() {
this.get('pinned').unpinItem(this.get('pinState.pinId')).then(() => {
@ -136,12 +129,15 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, {
return true;
},
onLayoutChange(layout) {
let doc = this.get('document');
doc.set('layout', layout);
this.get('documentService').save(doc);
if (this.get('permissions.documentEdit')) {
this.get('documentService').save(doc);
}
return true;
}
}

View file

@ -72,6 +72,13 @@ export default Ember.Component.extend(TooltipMixin, {
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() {
$("#" + this.get('blockTitleId')).removeClass('error');
$("#" + this.get('blockExcerptId')).removeClass('error');
@ -131,7 +138,7 @@ export default Ember.Component.extend(TooltipMixin, {
};
this.attrs.onSavePageAsBlock(block);
this.set('menuOpen', false);
this.set('blockTitle', '');
this.set('blockExcerpt', '');

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}`;
}),
hasMenuPermissions: computed('permissions', function() {
let permissions = this.get('permissions');
return permissions.get('documentDelete') || permissions.get('documentCopy') ||
permissions.get('documentMove') || permissions.get('documentTemplate');
}),
didRender() {
$("#" + this.get('blockTitleId')).removeClass('error');
$("#" + this.get('blockExcerptId')).removeClass('error');

View file

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

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';
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: {
selectDocument(documentId) {
let doc = this.get('documents').findBy('id', documentId);
@ -65,39 +26,6 @@ export default Ember.Component.extend({
}
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: '',
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() {
@ -67,7 +67,8 @@ export default Ember.Component.extend(NotifierMixin, {
this.set('inviteEmail', '');
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() {
let folders = this.get('folders');
// clear out state
this.set('publicFolders', []);
this.set('protectedFolders', []);
this.set('privateFolders', []);
let publicFolders = [];
let protectedFolders = [];
let privateFolders = [];
_.each(folders, folder => {
if (folder.get('folderType') === constants.FolderType.Public) {
let folders = this.get('publicFolders');
folders.pushObject(folder);
this.set('publicFolders', folders);
publicFolders.pushObject(folder);
}
if (folder.get('folderType') === constants.FolderType.Private) {
let folders = this.get('privateFolders');
folders.pushObject(folder);
this.set('privateFolders', folders);
protectedFolders.pushObject(folder);
}
if (folder.get('folderType') === constants.FolderType.Protected) {
let folders = this.get('protectedFolders');
folders.pushObject(folder);
this.set('protectedFolders', folders);
privateFolders.pushObject(folder);
}
});
this.set('publicFolders', publicFolders);
this.set('protectedFolders', protectedFolders);
this.set('privateFolders', privateFolders);
this.set('hasPublicFolders', this.get('publicFolders.length') > 0);
this.set('hasPrivateFolders', this.get('privateFolders.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 AuthMixin from '../../mixins/auth';
const {
inject: { service }
} = Ember;
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: '',
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: {
onAddSpace(m) {
this.attrs.onAddSpace(m);
@ -64,38 +34,5 @@ export default Ember.Component.extend(TooltipMixin, NotifierMixin, AuthMixin, {
onChangeTab(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: '',
hasNameError: computed.empty('folderName'),
editMode: false,
isEditor: false,
keyUp(e) {
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 {
computed,
inject: { service }
} = Ember;
export default Ember.Component.extend(NotifierMixin, {
localStorage: Ember.inject.service(),
appMeta: Ember.inject.service(),
templateService: Ember.inject.service('template'),
canEditTemplate: "",
localStorage: service(),
appMeta: service(),
templateService: service('template'),
importedDocuments: [],
savedTemplates: [],
drop: null,
newDocumentName: 'New Document',
importStatus: [],
dropzone: null,
newDocumentName: '',
newDocumentNameMissing: computed.empty('newDocumentName'),
didInsertElement() {
this.setupImport();
},
didReceiveAttrs() {
this._super(...arguments);
this.setupTemplates();
Ember.run.schedule('afterRender', ()=> {
this.setupImport();
});
},
willDestroyElement() {
if (is.not.null(this.get('drop'))) {
this.get('drop').destroy();
this.set('drop', null);
this._super(...arguments);
if (is.not.null(this.get('dropzone'))) {
this.get('dropzone').destroy();
this.set('dropzone', null);
}
},
setupTemplates() {
let templates = this.get('templates');
let emptyTemplate = {
id: "0",
title: "Empty",
description: "An empty canvas for your words",
layout: "doc",
locked: true
};
if (is.undefined(templates.findBy('id', '0'))) {
let emptyTemplate = {
id: "0",
title: "Blank",
description: "An empty canvas for your words",
layout: "doc",
locked: true
};
templates.unshiftObject(emptyTemplate);
}
templates.unshiftObject(emptyTemplate);
this.set('savedTemplates', templates);
Ember.run.schedule('afterRender', () => {
$('#new-document-name').select();
});
},
setupImport() {
// already done init?
if (is.not.null(this.get('drop'))) {
this.get('drop').destroy();
this.set('drop', null);
if (is.not.null(this.get('dropzone'))) {
this.get('dropzone').destroy();
this.set('dropzone', null);
}
let self = this;
@ -70,9 +82,7 @@ export default Ember.Component.extend(NotifierMixin, {
let importUrl = `${url}/import/folder/${folderId}`;
let dzone = new Dropzone("#import-document-button", {
headers: {
'Authorization': 'Bearer ' + self.get('session.session.content.authenticated.token')
},
headers: { 'Authorization': 'Bearer ' + self.get('session.session.content.authenticated.token') },
url: importUrl,
method: "post",
paramName: 'attachment',
@ -90,10 +100,10 @@ export default Ember.Component.extend(NotifierMixin, {
});
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) {
self.send('onDocumentImporting', file.name);
@ -105,12 +115,12 @@ export default Ember.Component.extend(NotifierMixin, {
dzone.removeFile(file);
});
this.set('drop', dzone);
this.set('dropzone', dzone);
},
actions: {
onHideDocumentWizard() {
this.get('onHideDocumentWizard')();
onHideStartDocument() {
this.get('onHideStartDocument')();
},
editTemplate(template) {
@ -120,7 +130,13 @@ export default Ember.Component.extend(NotifierMixin, {
},
startDocument(template) {
this.send("showNotification", "Creating");
if (this.get('newDocumentNameMissing')) {
this.$("#new-document-name").addClass('error').focus();
return;
}
this.$("#new-document-name").removeClass('error');
this.send("showNotification", "Creating");
this.get('templateService').importSavedTemplate(this.folder.get('id'), template.id, this.get('newDocumentName')).then((document) => {
this.get('router').transitionTo('document', this.get('folder.id'), this.get('folder.slug'), document.get('id'), document.get('slug'));
@ -130,25 +146,29 @@ export default Ember.Component.extend(NotifierMixin, {
},
onDocumentImporting(filename) {
this.send("showNotification", `Importing ${filename}`);
this.get('onHideDocumentWizard')();
let status = this.get('importStatus');
let documents = this.get('importedDocuments');
status.pushObject(`Converting ${filename}...`);
documents.push(filename);
this.set('importStatus', status);
this.set('importedDocuments', documents);
},
onDocumentImported(filename /*, document*/ ) {
this.send("showNotification", `${filename} ready`);
let status = this.get('importStatus');
let documents = this.get('importedDocuments');
status.pushObject(`Successfully converted ${filename}`);
documents.pop(filename);
this.set('importStatus', status);
this.set('importedDocuments', documents);
this.get('onImport')();
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
import Ember from 'ember';
import constants from '../../utils/constants';
const {
computed,
inject: { service }
} = Ember;
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
import { Factory, faker } from 'ember-cli-mirage';
import Ember from 'ember';
export default Factory.extend({
"folderId": faker.list.cycle("VzMuyEw_3WqiafcG", "VzMygEw_3WrtFzto"),
"userId": faker.list.cycle("VzMuyEw_3WqiafcE", "VzMuyEw_3WqiafcE"),
"canView": true,
"canEdit": true
export default Ember.Component.extend({
nameField: 'category',
items: [],
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,24 +15,33 @@ export default Ember.Mixin.create({
tooltips: [],
addTooltip(elem) {
if(elem == null) {
if (elem == null) {
return;
}
}
let t = new Tooltip({
target: elem
});
let tt = this.get('tooltips');
tt.push(t);
tt.push(t);
return t;
},
destroyTooltip(t) {
t.destroy();
},
destroyTooltips() {
let tt = this.get('tooltips');
if (this.get('isDestroyed') || this.get('isDestroying')) {
return;
}
let tt = this.get('tooltips');
tt.forEach(t => {
t.destroy();
t.destroy();
});
tt.length = 0;

View file

@ -11,13 +11,15 @@
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'),
canView: attr('boolean', { defaultValue: false }),
canEdit: attr('boolean', { defaultValue: false })
category: attr('string'),
created: attr(),
revised: attr(),
// 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 }),
editor: attr('boolean', { defaultValue: false }),
admin: attr('boolean', { defaultValue: false }),
viewUsers: attr('boolean', { defaultValue: false }),
global: attr('boolean', { defaultValue: false }),
accounts: attr(),
created: attr(),

View file

@ -11,10 +11,16 @@
import Ember from 'ember';
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'),
folders: [],
dropdown: null,
deleteSpace: {
id: '',
name: ''
},
label: function () {
switch (this.get('folders').length) {
@ -25,16 +31,65 @@ export default Ember.Controller.extend(NotifierMixin, {
}
}.property('folders'),
willDestroyElement() {
this.destroyDropdown();
},
actions: {
changeOwner: function (folderId, userId) {
this.get('folderService').getFolder(folderId).then((folder) => {
folder.set('userId', userId);
onShow(spaceId) {
this.set('deleteSpace.id', spaceId);
this.set('deleteSpace.name', '');
$(".delete-space-dialog").css("display", "block");
$('#delete-space-name').removeClass('error');
this.get('folderService').save(folder).then(() => {
this.showNotification("Changed");
let drop = new Drop({
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.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);
});
this.send('onChangeOwner');
});
}
}

View file

@ -22,7 +22,7 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
},
model() {
return this.get('folderService').getAll();
return this.get('folderService').adminList();
},
setupController(controller, model) {
@ -30,33 +30,12 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
if (is.empty(nonPrivateFolders) || is.null(model) || is.undefined(model)) {
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() {
document.title = "Spaces | Documize";
},
actions: {
onChangeOwner() {
this.refresh();
}
}
});

View file

@ -1,50 +1,51 @@
{{#if folders}}
<div class="global-folder-settings">
<div class="form-header">
<div class="title">{{folders.length}} shared {{label}}</div>
<div class="tip">View and change shared space ownership</div>
</div>
<div class="input-control">
<table class="basic-table">
<thead>
<tr>
<th class="bordered">Space</th>
<th class="bordered">Participants</th>
</tr>
</thead>
<tbody>
{{#each folders as |folder|}}
<tr>
<td class="bordered">
{{#link-to 'folder' folder.id folder.slug class="alt"}}{{folder.name}}{{/link-to}}
</td>
<td class="bordered">
{{#each folder.sharedWith as |person|}}
{{#if person.isEveryone}}
Everyone
{{else}}
<div class="page-customize">
<div class="space-admin">
{{#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>
{{#if folders}}
<div class="form-header">
<div class="title">{{folders.length}} shared {{label}}</div>
<div class="tip">View and change shared space ownership</div>
</div>
<div class="input-control manage-space-list">
{{#each folders as |folder|}}
<div class="space pull-left width-80">
{{#link-to 'folder' folder.id folder.slug class="alt"}}{{folder.name}}{{/link-to}}
</div>
<div class="pull-right">
<div id="delete-space-button-{{folder.id}}" class="round-button-mono" title="Delete" {{action "onShow" folder.id}}>
<i class="material-icons">delete</i>
</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}}
<div class="form-header">
<div class="title">{{folders.length}} shared {{label}}</div>
<div class="tip">There are no spaces to maintain</div>
</div>
{{/if}}
</div>
{{else}}
<div class="global-folder-settings">
<div class="form-header">
<div class="title">{{folders.length}} shared {{label}}</div>
<div class="tip">There are no spaces to maintain</div>
</div>
</div>
{{/if}}
</div>

View file

@ -1,11 +1,11 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// 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>.
// by contacting <sales@documize.com>.
//
// https://documize.com
@ -18,4 +18,4 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
this.transitionTo('customize.general');
}
},
});
});

View file

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

View file

@ -1,11 +1,11 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// 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>.
// by contacting <sales@documize.com>.
//
// https://documize.com
@ -18,7 +18,7 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
global: Ember.inject.service('global'),
appMeta: Ember.inject.service(),
beforeModel: function () {
beforeModel () {
if (!this.session.isAdmin) {
this.transitionTo('auth.login');
}
@ -33,14 +33,14 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
});
});
} else {
this.get('userService').getComplete().then((users) =>{
this.get('userService').getComplete().then((users) => {
resolve(users);
});
}
});
},
activate: function () {
activate() {
document.title = "Users | Documize";
}
});
});

View file

@ -1,5 +1,3 @@
{{customize/user-settings add=(action 'add')}}
<div class="clearfix" />
{{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}}
{{/link-to}}
</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')}}
</div>
</div>

View file

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

View file

@ -11,8 +11,9 @@
import Ember from 'ember';
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'),
templateService: Ember.inject.service('template'),
sectionService: Ember.inject.service('section'),
@ -25,10 +26,6 @@ export default Ember.Controller.extend(NotifierMixin, {
tab: 'index',
actions: {
toggleSidebar() {
this.set('toggled', !this.get('toggled'));
},
onSaveDocument(doc) {
this.get('documentService').save(doc);
this.showNotification('Saved');
@ -226,6 +223,12 @@ export default Ember.Controller.extend(NotifierMixin, {
if (this.get('pageId') !== id && 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')),
links: this.modelFor('document').links,
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-sidebar}}
{{document/sidebar-zone folders=model.folders folder=model.folder document=model.document
pages=model.pages sections=model.section links=model.links isEditor=model.isEditor tab=tab
onDocumentDelete=(action 'onDocumentDelete') onSaveTemplate=(action 'onSaveTemplate')
onPageSequenceChange=(action 'onPageSequenceChange') onPageLevelChange=(action 'onPageLevelChange') onGotoPage=(action 'onGotoPage')}}
{{document/document-sidebar tab=tab
document=model.document folder=model.folder pages=model.pages page=model.page permissions=model.permissions
onPageSequenceChange=(action 'onPageSequenceChange') onPageLevelChange=(action 'onPageLevelChange')
onGotoPage=(action 'onGotoPage')}}
{{/layout/zone-sidebar}}
{{#layout/zone-content}}
<div id="zone-document-content" class="zone-document-content">
<div class="back-to-space">
{{#link-to 'folder' model.folder.id model.folder.slug}}
<div class="regular-button button-gray">
<i class="material-icons">arrow_back</i>
<div class="name">{{model.folder.name}}</div>
</div>
{{/link-to}}
<div class="document-header-zone">
<div class="pull-left">
{{document/space-category document=model.document folder=model.folder folders=model.folders permissions=model.permissions}}
</div>
<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 class="clearfix"/>
{{document/tag-editor documentTags=model.document.tags permissions=model.permissions onChange=(action 'onTagChange')}}
</div>
{{document/document-heading document=model.document isEditor=model.isEditor onSaveDocument=(action 'onSaveDocument')}}
{{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
onSavePage=(action 'onSavePage') onInsertSection=(action 'onInsertSection')
{{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')
onSavePageAsBlock=(action 'onSavePageAsBlock') onDeleteBlock=(action 'onDeleteBlock') onGotoPage=(action 'onGotoPage')
onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onPageDeleted')}}
</div>
{{/layout/zone-content}}
{{/layout/zone-container}}

View file

@ -17,29 +17,38 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
documentService: Ember.inject.service('document'),
folderService: Ember.inject.service('folder'),
linkService: Ember.inject.service('link'),
beforeModel(transition) {
this.set('pageId', is.not.undefined(transition.queryParams.page) ? transition.queryParams.page : "");
this.set('folderId', this.paramsFor('document').folder_id);
this.set('documentId', this.paramsFor('document').document_id);
return new Ember.RSVP.Promise((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('isEditor', this.get('folderService').get('canEditCurrentFolder'));
resolve();
});
});
});
this.get('documentService').fetchDocumentData(this.get('documentId')).then((data) => {
this.set('document', data.document);
this.set('folders', data.folders);
this.set('folder', data.folder);
this.set('permissions', data.permissions);
this.set('links', data.links);
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'),
document: this.get('document'),
page: this.get('pageId'),
isEditor: this.get('isEditor'),
links: this.get('linkService').getDocumentLinks(this.get('documentId')),
permissions: this.get('permissions'),
links: this.get('links'),
sections: this.get('sectionService').getAll()
});
},

View file

@ -16,13 +16,13 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
documentService: Ember.inject.service('document'),
folderService: Ember.inject.service('folder'),
userService: Ember.inject.service('user'),
model(params) {
return Ember.RSVP.hash({
folders: this.modelFor('document').folders,
folder: this.modelFor('document').folder,
document: this.modelFor('document').document,
isEditor: this.get('folderService').get('canEditCurrentFolder'),
permissions: this.get('folderService').get('permissions'),
links: this.modelFor('document').links,
sections: this.modelFor('document').sections,
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}}
{{/link-to}}
</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')}}
</div>
</div>

View file

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

View file

@ -10,78 +10,6 @@
// https://documize.com
import Ember from 'ember';
import NotifierMixin from '../../mixins/notifier';
export default Ember.Controller.extend(NotifierMixin, {
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();
}
}
export default Ember.Controller.extend({
});

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;
}
});

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