mirror of
https://github.com/documize/community.git
synced 2025-07-24 15:49:44 +02:00
Merge pull request #144 from documize/content-analytics
Content analytics and reporting
This commit is contained in:
commit
2dce8a89a4
60 changed files with 1300 additions and 924 deletions
|
@ -52,9 +52,9 @@ Space view.
|
|||
|
||||
## Latest version
|
||||
|
||||
[Community edition: v1.60.0](https://github.com/documize/community/releases)
|
||||
[Community edition: v1.61.0](https://github.com/documize/community/releases)
|
||||
|
||||
[Enterprise edition: v1.62.0](https://documize.com/downloads)
|
||||
[Enterprise edition: v1.63.0](https://documize.com/downloads)
|
||||
|
||||
## OS support
|
||||
|
||||
|
@ -69,7 +69,7 @@ Documize runs on the following:
|
|||
Documize is built with the following technologies:
|
||||
|
||||
- EmberJS (v2.18.0)
|
||||
- Go (v1.10)
|
||||
- Go (v1.10.1)
|
||||
|
||||
...and supports the following databases:
|
||||
|
||||
|
@ -77,7 +77,7 @@ Documize is built with the following technologies:
|
|||
- Percona (v5.7.16-10+)
|
||||
- MariaDB (10.3.0+)
|
||||
|
||||
Coming soon, PostgreSQL and Microsoft SQL Server support.
|
||||
Coming soon, PostgreSQL and Microsoft SQL Server database support.
|
||||
|
||||
## Authentication options
|
||||
|
||||
|
|
|
@ -227,6 +227,8 @@ CREATE TABLE IF NOT EXISTS `search` (
|
|||
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin
|
||||
ENGINE = MyISAM;
|
||||
|
||||
-- FULLTEXT search requires MyISAM and NOT InnoDB
|
||||
|
||||
DROP TABLE IF EXISTS `revision`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `revision` (
|
||||
|
@ -258,7 +260,8 @@ CREATE TABLE IF NOT EXISTS `config` (
|
|||
`key` CHAR(255) NOT NULL,
|
||||
`config` JSON,
|
||||
UNIQUE INDEX `idx_config_area` (`key` ASC))
|
||||
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
|
||||
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin
|
||||
ENGINE = InnoDB;
|
||||
|
||||
INSERT INTO `config` VALUES ('SMTP','{\"userid\": \"\",\"password\": \"\",\"host\": \"\",\"port\": \"\",\"sender\": \"\"}');
|
||||
INSERT INTO `config` VALUES ('FILEPLUGINS',
|
||||
|
|
|
@ -44,6 +44,7 @@ CREATE TABLE IF NOT EXISTS `search` (
|
|||
FULLTEXT INDEX `idx_search_content` (`content`))
|
||||
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
|
||||
ENGINE = MyISAM;
|
||||
-- FULLTEXT search requires MyISAM and NOT InnoDB
|
||||
|
||||
-- migrate page content
|
||||
INSERT INTO search (orgid, documentid, itemid, itemtype, content) SELECT orgid, documentid, id AS itemid, 'page' AS itemtype, TRIM(body) AS content FROM search_old;
|
||||
|
|
|
@ -22,7 +22,7 @@ CREATE TABLE IF NOT EXISTS `permission` (
|
|||
UNIQUE INDEX `idx_permission_id` (`id` ASC),
|
||||
INDEX `idx_permission_orgid` (`orgid` ASC))
|
||||
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
|
||||
ENGINE = MyISAM;
|
||||
ENGINE = InnoDB;
|
||||
|
||||
CREATE INDEX idx_permission_1 ON permission(orgid,who,whoid,location);
|
||||
CREATE INDEX idx_permission_2 ON permission(orgid,who,whoid,location,action);
|
||||
|
@ -44,7 +44,7 @@ CREATE TABLE IF NOT EXISTS `category` (
|
|||
INDEX `idx_category_refid` (`refid` ASC),
|
||||
INDEX `idx_category_orgid` (`orgid` ASC))
|
||||
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
|
||||
ENGINE = MyISAM;
|
||||
ENGINE = InnoDB;
|
||||
|
||||
CREATE INDEX idx_category_1 ON category(orgid,labelid);
|
||||
|
||||
|
@ -63,12 +63,12 @@ CREATE TABLE IF NOT EXISTS `categorymember` (
|
|||
UNIQUE INDEX `idx_categorymember_id` (`id` ASC),
|
||||
INDEX `idx_category_documentid` (`documentid`))
|
||||
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
|
||||
ENGINE = MyISAM;
|
||||
ENGINE = InnoDB;
|
||||
|
||||
CREATE INDEX idx_categorymember_1 ON categorymember(orgid,documentid);
|
||||
CREATE INDEX idx_categorymember_2 ON categorymember(orgid,labelid);
|
||||
|
||||
-- rolee represent user groups
|
||||
-- rolee represent user groups
|
||||
DROP TABLE IF EXISTS `role`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `role` (
|
||||
|
@ -81,7 +81,7 @@ CREATE TABLE IF NOT EXISTS `role` (
|
|||
INDEX `idx_category_refid` (`refid` ASC),
|
||||
INDEX `idx_category_orgid` (`orgid` ASC))
|
||||
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
|
||||
ENGINE = MyISAM;
|
||||
ENGINE = InnoDB;
|
||||
|
||||
-- role member records user role membership
|
||||
DROP TABLE IF EXISTS `rolemember`;
|
||||
|
@ -93,49 +93,49 @@ CREATE TABLE IF NOT EXISTS `rolemember` (
|
|||
`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;
|
||||
ENGINE = InnoDB;
|
||||
|
||||
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
|
||||
-- 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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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;
|
||||
|
||||
|
|
40
core/database/scripts/autobuild/db_00020.sql
Normal file
40
core/database/scripts/autobuild/db_00020.sql
Normal file
|
@ -0,0 +1,40 @@
|
|||
/* enterprise edition */
|
||||
|
||||
-- consistency of table engines
|
||||
ALTER TABLE config ENGINE = InnoDB;
|
||||
ALTER TABLE permission ENGINE = InnoDB;
|
||||
ALTER TABLE category ENGINE = InnoDB;
|
||||
ALTER TABLE categorymember ENGINE = InnoDB;
|
||||
ALTER TABLE role ENGINE = InnoDB;
|
||||
ALTER TABLE rolemember ENGINE = InnoDB;
|
||||
|
||||
-- content analytics
|
||||
ALTER TABLE useractivity ADD COLUMN `metadata` VARCHAR(1000) NOT NULL DEFAULT '' AFTER `activitytype`;
|
||||
|
||||
-- new role for viewing content analytics
|
||||
ALTER TABLE account ADD COLUMN `analytics` BOOL NOT NULL DEFAULT 0 AFTER `users`;
|
||||
UPDATE account SET analytics=1 WHERE admin=1;
|
||||
|
||||
-- content likes/feedback
|
||||
-- DROP TABLE IF EXISTS `vote`;
|
||||
|
||||
-- CREATE TABLE IF NOT EXISTS `vote` (
|
||||
-- `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
-- `refid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
-- `orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
-- `documentid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
-- `userid` CHAR(16) NOT NULL DEFAULT '' COLLATE utf8_bin,
|
||||
-- `vote` INT NOT NULL DEFAULT 0,
|
||||
-- `comment` VARCHAR(300) NOT NULL DEFAULT '',
|
||||
-- `created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
-- `revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
-- UNIQUE INDEX `idx_vote_id` (`id` ASC),
|
||||
-- INDEX `idx_vote_refid` (`refid` ASC),
|
||||
-- INDEX `idx_vote_documentid` (`documentid` ASC),
|
||||
-- INDEX `idx_vote_orgid` (`orgid` ASC))
|
||||
-- DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
|
||||
-- ENGINE = InnoDB;
|
||||
|
||||
-- CREATE INDEX idx_vote_1 ON vote(orgid,documentid);
|
||||
|
||||
-- deprecations
|
|
@ -33,8 +33,8 @@ func (s Scope) Add(ctx domain.RequestContext, account account.Account) (err erro
|
|||
account.Created = time.Now().UTC()
|
||||
account.Revised = time.Now().UTC()
|
||||
|
||||
_, 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)
|
||||
_, err = ctx.Transaction.Exec("INSERT INTO account (refid, orgid, userid, admin, editor, users, analytics, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
account.RefID, account.OrgID, account.UserID, account.Admin, account.Editor, account.Users, account.Analytics, account.Active, account.Created, account.Revised)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "unable to execute insert for account")
|
||||
|
@ -46,7 +46,7 @@ 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) {
|
||||
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,
|
||||
SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.users, a.analytics, a.active, a.created, a.revised,
|
||||
b.company, b.title, b.message, b.domain
|
||||
FROM account a, organization b
|
||||
WHERE b.refid=a.orgid AND a.orgid=? AND a.userid=?`, ctx.OrgID, userID)
|
||||
|
@ -61,8 +61,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.users, 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.analytics, a.active, a.created, a.revised,
|
||||
b.company, b.title, b.message, b.domain
|
||||
FROM account a, organization b
|
||||
WHERE a.userid=? AND a.orgid=b.refid AND a.active=1 ORDER BY b.title`, userID)
|
||||
|
||||
|
@ -76,7 +76,7 @@ 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.users, a.active, a.created, a.revised,
|
||||
`SELECT a.id, a.refid, a.orgid, a.userid, a.editor, a.admin, a.users, a.analytics, a.active, a.created, a.revised,
|
||||
b.company, b.title, b.message, b.domain
|
||||
FROM account a, organization b
|
||||
WHERE a.orgid=b.refid AND a.orgid=? AND a.active=1`, ctx.OrgID)
|
||||
|
@ -109,7 +109,7 @@ 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()
|
||||
|
||||
_, 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)
|
||||
_, err = ctx.Transaction.NamedExec("UPDATE account SET userid=:userid, admin=:admin, editor=:editor, users=:users, analytics=:analytics, active=:active, revised=:revised WHERE orgid=:orgid AND refid=:refid", &account)
|
||||
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("execute update for account %s", account.RefID))
|
||||
|
|
|
@ -34,8 +34,8 @@ func (s Scope) RecordUserActivity(ctx domain.RequestContext, activity activity.U
|
|||
activity.UserID = ctx.UserID
|
||||
activity.Created = time.Now().UTC()
|
||||
|
||||
_, err = ctx.Transaction.Exec("INSERT INTO useractivity (orgid, userid, labelid, documentid, pageid, sourcetype, activitytype, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
activity.OrgID, activity.UserID, activity.LabelID, activity.DocumentID, activity.PageID, activity.SourceType, activity.ActivityType, activity.Created)
|
||||
_, err = ctx.Transaction.Exec("INSERT INTO useractivity (orgid, userid, labelid, documentid, pageid, sourcetype, activitytype, metadata, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
activity.OrgID, activity.UserID, activity.LabelID, activity.DocumentID, activity.PageID, activity.SourceType, activity.ActivityType, activity.Metadata, activity.Created)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "execute record user activity")
|
||||
|
@ -46,7 +46,7 @@ func (s Scope) RecordUserActivity(ctx domain.RequestContext, activity activity.U
|
|||
|
||||
// GetDocumentActivity returns the metadata for a specified document.
|
||||
func (s Scope) GetDocumentActivity(ctx domain.RequestContext, id string) (a []activity.DocumentActivity, err error) {
|
||||
qry := `SELECT a.id, DATE(a.created) as created, a.orgid, IFNULL(a.userid, '') AS userid, a.labelid, a.documentid, a.pageid, a.activitytype,
|
||||
qry := `SELECT a.id, DATE(a.created) as created, a.orgid, IFNULL(a.userid, '') AS userid, a.labelid, a.documentid, a.pageid, a.activitytype, a.metadata,
|
||||
IFNULL(u.firstname, 'Anonymous') AS firstname, IFNULL(u.lastname, 'Viewer') AS lastname,
|
||||
IFNULL(p.title, '') as pagetitle
|
||||
FROM useractivity a
|
||||
|
|
|
@ -53,7 +53,7 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (c []catego
|
|||
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
|
||||
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)
|
||||
|
@ -70,19 +70,20 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (c []catego
|
|||
|
||||
// GetAllBySpace returns all space categories.
|
||||
func (s Scope) GetAllBySpace(ctx domain.RequestContext, spaceID string) (c []category.Category, err error) {
|
||||
c = []category.Category{}
|
||||
|
||||
err = s.Runtime.Db.Select(&c, `
|
||||
SELECT id, refid, orgid, labelid, category, created, revised FROM category
|
||||
WHERE orgid=? AND labelid=?
|
||||
AND labelid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' UNION ALL
|
||||
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space'
|
||||
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 || len(c) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
c = []category.Category{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to execute select all categories for space %s", spaceID))
|
||||
|
@ -190,6 +191,8 @@ func (s Scope) DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows in
|
|||
|
||||
// GetSpaceCategorySummary returns number of documents and users for space categories.
|
||||
func (s Scope) GetSpaceCategorySummary(ctx domain.RequestContext, spaceID string) (c []category.SummaryModel, err error) {
|
||||
c = []category.SummaryModel{}
|
||||
|
||||
err = s.Runtime.Db.Select(&c, `
|
||||
SELECT 'documents' as type, categoryid, COUNT(*) as count
|
||||
FROM categorymember
|
||||
|
@ -197,14 +200,13 @@ func (s Scope) GetSpaceCategorySummary(ctx domain.RequestContext, spaceID string
|
|||
UNION ALL
|
||||
SELECT 'users' as type, refid AS categoryid, count(*) AS count
|
||||
FROM permission
|
||||
WHERE orgid=? AND location='category'
|
||||
WHERE orgid=? AND location='category'
|
||||
AND refid IN (SELECT refid FROM category WHERE orgid=? AND labelid=?)
|
||||
GROUP BY refid, type`,
|
||||
ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, spaceID /*, ctx.OrgID, ctx.OrgID, spaceID*/)
|
||||
|
||||
if err == sql.ErrNoRows || len(c) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
c = []category.SummaryModel{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("select category summary for space %s", spaceID))
|
||||
|
@ -215,6 +217,8 @@ func (s Scope) GetSpaceCategorySummary(ctx domain.RequestContext, spaceID string
|
|||
|
||||
// GetDocumentCategoryMembership returns all space categories associated with given document.
|
||||
func (s Scope) GetDocumentCategoryMembership(ctx domain.RequestContext, documentID string) (c []category.Category, err error) {
|
||||
c = []category.Category{}
|
||||
|
||||
err = s.Runtime.Db.Select(&c, `
|
||||
SELECT id, refid, orgid, labelid, category, created, revised FROM category
|
||||
WHERE orgid=? AND refid IN (SELECT categoryid FROM categorymember WHERE orgid=? AND documentid=?)`, ctx.OrgID, ctx.OrgID, documentID)
|
||||
|
@ -236,7 +240,7 @@ func (s Scope) GetSpaceCategoryMembership(ctx domain.RequestContext, spaceID str
|
|||
WHERE orgid=? AND labelid=?
|
||||
AND labelid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' UNION ALL
|
||||
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space'
|
||||
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space'
|
||||
AND p.action='view' AND (r.userid=? OR r.userid='0')
|
||||
))
|
||||
ORDER BY documentid`, ctx.OrgID, spaceID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
|
||||
|
|
|
@ -29,6 +29,7 @@ type RequestContext struct {
|
|||
Guest bool
|
||||
Editor bool
|
||||
Global bool
|
||||
Analytics bool
|
||||
UserID string
|
||||
OrgID string
|
||||
OrgName string
|
||||
|
|
|
@ -142,6 +142,25 @@ func CopyDocument(ctx domain.RequestContext, s domain.Store, documentID string)
|
|||
}
|
||||
}
|
||||
|
||||
cats, err := s.Category.GetDocumentCategoryMembership(ctx, documentID)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "unable to add copied page")
|
||||
return
|
||||
}
|
||||
|
||||
for ci := range cats {
|
||||
cm := category.Member{}
|
||||
cm.DocumentID = newDocumentID
|
||||
cm.CategoryID = cats[ci].RefID
|
||||
cm.OrgID = ctx.OrgID
|
||||
cm.RefID = uniqueid.Generate()
|
||||
s.Category.AssociateDocument(ctx, cm)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "unable to add copied page")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/request"
|
||||
|
@ -72,15 +73,15 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// draft mode does not record document views
|
||||
if document.Lifecycle == workflow.LifecycleLive {
|
||||
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,
|
||||
DocumentID: document.RefID,
|
||||
|
@ -91,9 +92,9 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
|||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
ctx.Transaction.Commit()
|
||||
}
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeDocumentView)
|
||||
|
||||
|
@ -220,6 +221,13 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
if oldDoc.LabelID != d.LabelID {
|
||||
h.Store.Category.RemoveDocumentCategories(ctx, d.RefID)
|
||||
err = h.Store.Document.MoveActivity(ctx, documentID, oldDoc.LabelID, d.LabelID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = h.Store.Document.Update(ctx, d)
|
||||
|
@ -394,6 +402,8 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
options.Keywords = strings.TrimSpace(options.Keywords)
|
||||
|
||||
results, err := h.Store.Search.Documents(ctx, options)
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error(method, err)
|
||||
|
@ -401,13 +411,35 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Put in slugs for easy UI display of search URL
|
||||
for key, result := range results {
|
||||
result.DocumentSlug = stringutil.MakeSlug(result.Document)
|
||||
result.SpaceSlug = stringutil.MakeSlug(result.Space)
|
||||
results[key] = result
|
||||
results[key].DocumentSlug = stringutil.MakeSlug(result.Document)
|
||||
results[key].SpaceSlug = stringutil.MakeSlug(result.Space)
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
results = []search.QueryResult{}
|
||||
// Record user search history
|
||||
if !options.SkipLog {
|
||||
if len(results) > 0 {
|
||||
go h.recordSearchActivity(ctx, results, options.Keywords)
|
||||
} else {
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: "",
|
||||
DocumentID: "",
|
||||
Metadata: options.Keywords,
|
||||
SourceType: activity.SourceTypeSearch,
|
||||
ActivityType: activity.TypeSearched})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
}
|
||||
}
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSearch)
|
||||
|
@ -415,6 +447,50 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
|
|||
response.WriteJSON(w, results)
|
||||
}
|
||||
|
||||
// Record search request once per document.
|
||||
// But only if document is partof shared space at the time of the search.
|
||||
func (h *Handler) recordSearchActivity(ctx domain.RequestContext, q []search.QueryResult, keywords string) {
|
||||
method := "recordSearchActivity"
|
||||
var err error
|
||||
prev := make(map[string]bool)
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
for i := range q {
|
||||
// Empty space ID usually signals private document
|
||||
// hence search activity should not be visible to others.
|
||||
if len(q[i].SpaceID) == 0 {
|
||||
continue
|
||||
}
|
||||
sp, err := h.Store.Space.Get(ctx, q[i].SpaceID)
|
||||
if err != nil || len(sp.RefID) == 0 || sp.Type == space.ScopePrivate {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, isExisting := prev[q[i].DocumentID]; !isExisting {
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: q[i].SpaceID,
|
||||
DocumentID: q[i].DocumentID,
|
||||
Metadata: keywords,
|
||||
SourceType: activity.SourceTypeSearch,
|
||||
ActivityType: activity.TypeSearched})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
prev[q[i].DocumentID] = true
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
}
|
||||
|
||||
// FetchDocumentData returns all document data in single API call.
|
||||
func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
|
||||
method := "document.FetchDocumentData"
|
||||
|
|
|
@ -105,6 +105,8 @@ func (s Scope) DocumentMeta(ctx domain.RequestContext, id string) (meta doc.Docu
|
|||
// All versions of a document are returned, hence caller must
|
||||
// decide what to do with them.
|
||||
func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error) {
|
||||
documents = []doc.Document{}
|
||||
|
||||
err = s.Runtime.Db.Select(&documents, `
|
||||
SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template,
|
||||
protection, approval, lifecycle, versioned, versionid, versionorder, groupid, created, revised
|
||||
|
@ -120,9 +122,8 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (documents
|
|||
)
|
||||
ORDER BY title, versionorder`, ctx.OrgID, ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, spaceID, ctx.UserID)
|
||||
|
||||
if err == sql.ErrNoRows || len(documents) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
documents = []doc.Document{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "select documents by space")
|
||||
|
@ -237,6 +238,9 @@ func (s Scope) MoveDocumentSpace(ctx domain.RequestContext, id, move string) (er
|
|||
_, err = ctx.Transaction.Exec("UPDATE document SET labelid=? WHERE orgid=? AND labelid=?",
|
||||
move, ctx.OrgID, id)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("execute document space move %s", id))
|
||||
}
|
||||
|
@ -244,6 +248,18 @@ func (s Scope) MoveDocumentSpace(ctx domain.RequestContext, id, move string) (er
|
|||
return
|
||||
}
|
||||
|
||||
// MoveActivity changes the space for all document activity records.
|
||||
func (s Scope) MoveActivity(ctx domain.RequestContext, documentID, oldSpaceID, newSpaceID string) (err error) {
|
||||
_, err = ctx.Transaction.Exec("UPDATE useractivity SET labelid=? WHERE orgid=? AND labelid=? AND documentid=?",
|
||||
newSpaceID, ctx.OrgID, oldSpaceID, documentID)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("execute document activity move %s", documentID))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes the specified document.
|
||||
// Remove document pages, revisions, attachments, updates the search subsystem.
|
||||
func (s Scope) Delete(ctx domain.RequestContext, documentID string) (rows int64, err error) {
|
||||
|
@ -303,15 +319,16 @@ func (s Scope) DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows in
|
|||
// All versions of a document are returned, hence caller must
|
||||
// decide what to do with them.
|
||||
func (s Scope) GetVersions(ctx domain.RequestContext, groupID string) (v []doc.Version, err error) {
|
||||
v = []doc.Version{}
|
||||
|
||||
err = s.Runtime.Db.Select(&v, `
|
||||
SELECT versionid, refid as documentid
|
||||
FROM document
|
||||
WHERE orgid=? AND groupid=?
|
||||
ORDER BY versionorder`, ctx.OrgID, groupID)
|
||||
|
||||
if err == sql.ErrNoRows || len(v) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
v = []doc.Version{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "document.store.GetVersions")
|
||||
|
|
|
@ -58,6 +58,8 @@ func (s Scope) Get(ctx domain.RequestContext, refID string) (g group.Group, err
|
|||
|
||||
// GetAll returns all user groups for current orgID.
|
||||
func (s Scope) GetAll(ctx domain.RequestContext) (groups []group.Group, err error) {
|
||||
groups = []group.Group{}
|
||||
|
||||
err = s.Runtime.Db.Select(&groups,
|
||||
`SELECT a.id, a.refid, a.orgid, a.role as name, a.purpose, a.created, a.revised, COUNT(b.roleid) AS members
|
||||
FROM role a
|
||||
|
@ -67,9 +69,8 @@ func (s Scope) GetAll(ctx domain.RequestContext) (groups []group.Group, err erro
|
|||
ORDER BY a.role`,
|
||||
ctx.OrgID)
|
||||
|
||||
if err == sql.ErrNoRows || len(groups) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
groups = []group.Group{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "select groups")
|
||||
|
@ -101,8 +102,10 @@ func (s Scope) Delete(ctx domain.RequestContext, refID string) (rows int64, err
|
|||
|
||||
// GetGroupMembers returns all user associated with given group.
|
||||
func (s Scope) GetGroupMembers(ctx domain.RequestContext, groupID string) (members []group.Member, err error) {
|
||||
members = []group.Member{}
|
||||
|
||||
err = s.Runtime.Db.Select(&members,
|
||||
`SELECT a.id, a.orgid, a.roleid, a.userid,
|
||||
`SELECT a.id, a.orgid, a.roleid, a.userid,
|
||||
IFNULL(b.firstname, '') as firstname, IFNULL(b.lastname, '') as lastname
|
||||
FROM rolemember a
|
||||
LEFT JOIN user b ON b.refid=a.userid
|
||||
|
@ -110,9 +113,8 @@ func (s Scope) GetGroupMembers(ctx domain.RequestContext, groupID string) (membe
|
|||
ORDER BY b.firstname, b.lastname`,
|
||||
ctx.OrgID, groupID)
|
||||
|
||||
if err == sql.ErrNoRows || len(members) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
members = []group.Member{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "select members")
|
||||
|
@ -146,16 +148,17 @@ func (s Scope) LeaveGroup(ctx domain.RequestContext, groupID, userID string) (er
|
|||
// Useful when you need to bulk fetch membership records
|
||||
// for subsequent processing.
|
||||
func (s Scope) GetMembers(ctx domain.RequestContext) (r []group.Record, err error) {
|
||||
r = []group.Record{}
|
||||
|
||||
err = s.Runtime.Db.Select(&r,
|
||||
`SELECT a.id, a.orgid, a.roleid, a.userid, b.role as name, b.purpose
|
||||
FROM rolemember a, role b
|
||||
WHERE a.orgid=? AND a.roleid=b.refid
|
||||
WHERE a.orgid=? AND a.roleid=b.refid
|
||||
ORDER BY a.userid`,
|
||||
ctx.OrgID)
|
||||
|
||||
if err == sql.ErrNoRows || len(r) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
r = []group.Record{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "select group members")
|
||||
|
|
|
@ -1236,6 +1236,9 @@ func (h *Handler) FetchPages(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// Who referred user this document (e.g. search page).
|
||||
source := request.Query(r, "source")
|
||||
|
||||
doc, err := h.Store.Document.Get(ctx, documentID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
|
@ -1393,6 +1396,28 @@ func (h *Handler) FetchPages(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// If we have source, record document access via source.
|
||||
if len(source) > 0 {
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error(method, err)
|
||||
} else {
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
DocumentID: doc.RefID,
|
||||
Metadata: source, // deliberate
|
||||
SourceType: activity.SourceTypeSearch, // deliberate
|
||||
ActivityType: activity.TypeRead})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
}
|
||||
}
|
||||
|
||||
// deliver payload
|
||||
response.WriteJSON(w, model)
|
||||
}
|
||||
|
|
|
@ -62,6 +62,8 @@ func (s Scope) AddPermissions(ctx domain.RequestContext, r permission.Permission
|
|||
// Context is used to for userID because must match by userID
|
||||
// or everyone ID of 0.
|
||||
func (s Scope) GetUserSpacePermissions(ctx domain.RequestContext, spaceID string) (r []permission.Permission, err error) {
|
||||
r = []permission.Permission{}
|
||||
|
||||
err = s.Runtime.Db.Select(&r, `
|
||||
SELECT id, orgid, who, whoid, action, scope, location, refid
|
||||
FROM permission
|
||||
|
@ -73,9 +75,8 @@ func (s Scope) GetUserSpacePermissions(ctx domain.RequestContext, spaceID string
|
|||
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.UserID)
|
||||
|
||||
if err == sql.ErrNoRows || len(r) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
r = []permission.Permission{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to execute select user permissions %s", ctx.UserID))
|
||||
|
@ -87,6 +88,8 @@ func (s Scope) GetUserSpacePermissions(ctx domain.RequestContext, spaceID string
|
|||
// GetSpacePermissions returns space permissions for all users.
|
||||
// We do not filter by userID because we return permissions for all users.
|
||||
func (s Scope) GetSpacePermissions(ctx domain.RequestContext, spaceID string) (r []permission.Permission, err error) {
|
||||
r = []permission.Permission{}
|
||||
|
||||
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'
|
||||
|
@ -99,7 +102,6 @@ func (s Scope) GetSpacePermissions(ctx domain.RequestContext, spaceID string) (r
|
|||
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
r = []permission.Permission{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to execute select space permissions %s", ctx.UserID))
|
||||
|
@ -110,6 +112,8 @@ func (s Scope) GetSpacePermissions(ctx domain.RequestContext, spaceID string) (r
|
|||
|
||||
// GetCategoryPermissions returns category permissions for all users.
|
||||
func (s Scope) GetCategoryPermissions(ctx domain.RequestContext, catID string) (r []permission.Permission, err error) {
|
||||
r = []permission.Permission{}
|
||||
|
||||
err = s.Runtime.Db.Select(&r, `
|
||||
SELECT id, orgid, who, whoid, action, scope, location, refid
|
||||
FROM permission
|
||||
|
@ -121,9 +125,8 @@ func (s Scope) GetCategoryPermissions(ctx domain.RequestContext, catID string) (
|
|||
WHERE p.orgid=? AND p.location='category' AND p.who='role' AND (p.refid=? OR p.refid='0')`,
|
||||
ctx.OrgID, catID, ctx.OrgID, catID)
|
||||
|
||||
if err == sql.ErrNoRows || len(r) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
r = []permission.Permission{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to execute select category permissions %s", catID))
|
||||
|
@ -134,6 +137,8 @@ func (s Scope) GetCategoryPermissions(ctx domain.RequestContext, catID string) (
|
|||
|
||||
// GetCategoryUsers returns space permissions for all users.
|
||||
func (s Scope) GetCategoryUsers(ctx domain.RequestContext, catID string) (u []user.User, err error) {
|
||||
u = []user.User{}
|
||||
|
||||
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
|
||||
|
@ -149,7 +154,7 @@ func (s Scope) GetCategoryUsers(ctx domain.RequestContext, catID string) (u []us
|
|||
ORDER BY firstname, lastname`,
|
||||
ctx.OrgID, ctx.OrgID, catID, ctx.OrgID, catID)
|
||||
|
||||
if err == sql.ErrNoRows || len(u) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
u = []user.User{}
|
||||
}
|
||||
|
@ -162,6 +167,8 @@ func (s Scope) GetCategoryUsers(ctx domain.RequestContext, catID string) (u []us
|
|||
|
||||
// GetUserCategoryPermissions returns category permissions for given user.
|
||||
func (s Scope) GetUserCategoryPermissions(ctx domain.RequestContext, userID string) (r []permission.Permission, err error) {
|
||||
r = []permission.Permission{}
|
||||
|
||||
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')
|
||||
|
@ -172,9 +179,8 @@ func (s Scope) GetUserCategoryPermissions(ctx domain.RequestContext, userID stri
|
|||
WHERE p.orgid=? AND p.location='category' AND p.who='role' AND (r.userid=? OR r.userid='0')`,
|
||||
ctx.OrgID, userID, ctx.OrgID, userID)
|
||||
|
||||
if err == sql.ErrNoRows || len(r) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
r = []permission.Permission{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to execute select category permissions for user %s", userID))
|
||||
|
|
|
@ -204,6 +204,10 @@ func (s Scope) Documents(ctx domain.RequestContext, q search.QueryOptions) (resu
|
|||
results = append(results, r4...)
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
results = []search.QueryResult{}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -227,9 +231,10 @@ func (s Scope) matchFullText(ctx domain.RequestContext, keywords, itemType strin
|
|||
(
|
||||
SELECT refid FROM label WHERE orgid=? AND refid IN
|
||||
(
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
|
||||
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 r.userid=?
|
||||
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role'
|
||||
AND p.location='space' AND (r.userid=? OR r.userid='0')
|
||||
)
|
||||
)
|
||||
AND MATCH(s.content) AGAINST(? IN BOOLEAN MODE)`
|
||||
|
@ -280,13 +285,13 @@ func (s Scope) matchLike(ctx domain.RequestContext, keywords, itemType string) (
|
|||
-- AND d.template = 0
|
||||
AND d.labelid IN
|
||||
(
|
||||
SELECT refid FROM label WHERE orgid=?
|
||||
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
|
||||
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
|
||||
SELECT refid FROM label WHERE orgid=? 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')
|
||||
))
|
||||
AND p.location='space' AND (r.userid=? OR r.userid='0')
|
||||
)
|
||||
)
|
||||
AND s.content LIKE ?`
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ func (m *Indexer) IndexDocument(ctx domain.RequestContext, d doc.Document, a []a
|
|||
method := "search.IndexDocument"
|
||||
var err error
|
||||
|
||||
ctx.Transaction, err = m.runtime.Db.Beginx()
|
||||
tx, err := m.runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
m.runtime.Log.Error(method, err)
|
||||
return
|
||||
|
@ -32,12 +32,12 @@ func (m *Indexer) IndexDocument(ctx domain.RequestContext, d doc.Document, a []a
|
|||
|
||||
err = m.store.Search.IndexDocument(ctx, d, a)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
tx.Rollback()
|
||||
m.runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
tx.Commit()
|
||||
}
|
||||
|
||||
// DeleteDocument removes all search entries for document.
|
||||
|
@ -45,7 +45,7 @@ func (m *Indexer) DeleteDocument(ctx domain.RequestContext, ID string) {
|
|||
method := "search.DeleteDocument"
|
||||
var err error
|
||||
|
||||
ctx.Transaction, err = m.runtime.Db.Beginx()
|
||||
tx, err := m.runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
m.runtime.Log.Error(method, err)
|
||||
return
|
||||
|
@ -53,12 +53,12 @@ func (m *Indexer) DeleteDocument(ctx domain.RequestContext, ID string) {
|
|||
|
||||
err = m.store.Search.DeleteDocument(ctx, ID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
tx.Rollback()
|
||||
m.runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
tx.Commit()
|
||||
}
|
||||
|
||||
// IndexContent adds search index entry for document context.
|
||||
|
@ -67,7 +67,7 @@ func (m *Indexer) IndexContent(ctx domain.RequestContext, p page.Page) {
|
|||
method := "search.IndexContent"
|
||||
var err error
|
||||
|
||||
ctx.Transaction, err = m.runtime.Db.Beginx()
|
||||
tx, err := m.runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
m.runtime.Log.Error(method, err)
|
||||
return
|
||||
|
@ -75,12 +75,12 @@ func (m *Indexer) IndexContent(ctx domain.RequestContext, p page.Page) {
|
|||
|
||||
err = m.store.Search.IndexContent(ctx, p)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
tx.Rollback()
|
||||
m.runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
tx.Commit()
|
||||
}
|
||||
|
||||
// DeleteContent removes all search entries for specific document content.
|
||||
|
@ -88,7 +88,7 @@ func (m *Indexer) DeleteContent(ctx domain.RequestContext, pageID string) {
|
|||
method := "search.DeleteContent"
|
||||
var err error
|
||||
|
||||
ctx.Transaction, err = m.runtime.Db.Beginx()
|
||||
tx, err := m.runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
m.runtime.Log.Error(method, err)
|
||||
return
|
||||
|
@ -96,10 +96,10 @@ func (m *Indexer) DeleteContent(ctx domain.RequestContext, pageID string) {
|
|||
|
||||
err = m.store.Search.DeleteContent(ctx, pageID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
tx.Rollback()
|
||||
m.runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
}
|
||||
tx.Commit()
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/documize/community/domain/mail"
|
||||
"github.com/documize/community/domain/organization"
|
||||
"github.com/documize/community/model/account"
|
||||
"github.com/documize/community/model/activity"
|
||||
"github.com/documize/community/model/audit"
|
||||
"github.com/documize/community/model/doc"
|
||||
"github.com/documize/community/model/page"
|
||||
|
@ -127,6 +128,15 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: sp.RefID,
|
||||
SourceType: activity.SourceTypeSpace,
|
||||
ActivityType: activity.TypeCreated})
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSpaceAdd)
|
||||
|
@ -338,6 +348,25 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
|||
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.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: sp.RefID,
|
||||
SourceType: activity.SourceTypeSpace,
|
||||
ActivityType: activity.TypeRead})
|
||||
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
response.WriteJSON(w, sp)
|
||||
}
|
||||
|
||||
|
@ -510,6 +539,15 @@ func (h *Handler) Remove(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: id,
|
||||
SourceType: activity.SourceTypeSpace,
|
||||
ActivityType: activity.TypeDeleted})
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSpaceDelete)
|
||||
|
@ -598,6 +636,15 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: id,
|
||||
SourceType: activity.SourceTypeSpace,
|
||||
ActivityType: activity.TypeDeleted})
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
h.Runtime.Log.Error(method, err)
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeSpaceDelete)
|
||||
|
|
|
@ -180,6 +180,7 @@ type DocumentStorer interface {
|
|||
Delete(ctx RequestContext, documentID string) (rows int64, err error)
|
||||
DeleteBySpace(ctx RequestContext, spaceID string) (rows int64, err error)
|
||||
GetVersions(ctx RequestContext, groupID string) (v []doc.Version, err error)
|
||||
MoveActivity(ctx RequestContext, documentID, oldSpaceID, newSpaceID string) (err error)
|
||||
}
|
||||
|
||||
// SettingStorer defines required methods for persisting global and user level settings
|
||||
|
|
|
@ -162,6 +162,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|||
a.Editor = true
|
||||
a.Admin = false
|
||||
a.Active = true
|
||||
a.Analytics = false
|
||||
|
||||
err = h.Store.Account.Add(ctx, a)
|
||||
if err != nil {
|
||||
|
@ -481,6 +482,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
a.Admin = u.Admin
|
||||
a.Active = u.Active
|
||||
a.Users = u.ViewUsers
|
||||
a.Analytics = u.Analytics
|
||||
|
||||
err = h.Store.Account.UpdateAccount(ctx, a)
|
||||
if err != nil {
|
||||
|
@ -799,6 +801,7 @@ func (h *Handler) BulkImport(w http.ResponseWriter, r *http.Request) {
|
|||
a.Editor = true
|
||||
a.Admin = false
|
||||
a.Active = true
|
||||
a.Analytics = false
|
||||
|
||||
err = h.Store.Account.Add(ctx, a)
|
||||
if err != nil {
|
||||
|
|
|
@ -110,17 +110,18 @@ func (s Scope) GetBySerial(ctx domain.RequestContext, serial string) (u user.Use
|
|||
// GetActiveUsersForOrganization returns a slice containing of active user records for the organization
|
||||
// identified in the Persister.
|
||||
func (s Scope) GetActiveUsersForOrganization(ctx domain.RequestContext) (u []user.User, err error) {
|
||||
u = []user.User{}
|
||||
|
||||
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.lastversion, u.created, u.revised,
|
||||
u.global, a.active, a.editor, a.admin, a.users as viewusers
|
||||
u.global, a.active, a.editor, a.admin, a.users as viewusers, a.analytics
|
||||
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 == sql.ErrNoRows || len(u) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
u = []user.User{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("get active users by org %s", ctx.OrgID))
|
||||
|
@ -132,6 +133,8 @@ func (s Scope) GetActiveUsersForOrganization(ctx domain.RequestContext) (u []use
|
|||
// GetUsersForOrganization returns a slice containing all of the user records for the organizaiton
|
||||
// identified in the Persister.
|
||||
func (s Scope) GetUsersForOrganization(ctx domain.RequestContext, filter string) (u []user.User, err error) {
|
||||
u = []user.User{}
|
||||
|
||||
filter = strings.TrimSpace(strings.ToLower(filter))
|
||||
likeQuery := ""
|
||||
if len(filter) > 0 {
|
||||
|
@ -140,14 +143,13 @@ func (s Scope) GetUsersForOrganization(ctx domain.RequestContext, filter string)
|
|||
|
||||
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.lastversion, u.created, u.revised,
|
||||
u.global, a.active, a.editor, a.admin, a.users as viewusers
|
||||
u.global, a.active, a.editor, a.admin, a.users as viewusers, a.analytics
|
||||
FROM user u, account a
|
||||
WHERE u.refid=a.userid AND a.orgid=? `+likeQuery+
|
||||
`ORDER BY u.firstname, u.lastname LIMIT 100`, ctx.OrgID)
|
||||
|
||||
if err == sql.ErrNoRows || len(u) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
u = []user.User{}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -159,9 +161,11 @@ func (s Scope) GetUsersForOrganization(ctx domain.RequestContext, filter string)
|
|||
|
||||
// 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) {
|
||||
u = []user.User{}
|
||||
|
||||
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.lastversion, u.revised, u.global,
|
||||
a.active, a.users AS viewusers, a.editor, a.admin
|
||||
a.active, a.users AS viewusers, a.editor, a.admin, a.analytics
|
||||
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=? UNION ALL
|
||||
|
@ -170,9 +174,8 @@ func (s Scope) GetSpaceUsers(ctx domain.RequestContext, spaceID string) (u []use
|
|||
ORDER BY u.firstname, u.lastname
|
||||
`, ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, spaceID)
|
||||
|
||||
if err == sql.ErrNoRows || len(u) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
u = []user.User{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("get space users for org %s", ctx.OrgID))
|
||||
|
@ -183,14 +186,15 @@ func (s Scope) GetSpaceUsers(ctx domain.RequestContext, spaceID string) (u []use
|
|||
|
||||
// GetUsersForSpaces returns users with access to specified spaces.
|
||||
func (s Scope) GetUsersForSpaces(ctx domain.RequestContext, spaces []string) (u []user.User, err error) {
|
||||
u = []user.User{}
|
||||
|
||||
if len(spaces) == 0 {
|
||||
u = []user.User{}
|
||||
return
|
||||
}
|
||||
|
||||
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.lastversion, u.created, u.revised, u.global,
|
||||
a.active, a.users AS viewusers, a.editor, a.admin
|
||||
a.active, a.users AS viewusers, a.editor, a.admin, a.analytics
|
||||
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
|
||||
|
@ -202,9 +206,8 @@ func (s Scope) GetUsersForSpaces(ctx domain.RequestContext, spaces []string) (u
|
|||
query = s.Runtime.Db.Rebind(query)
|
||||
err = s.Runtime.Db.Select(&u, query, args...)
|
||||
|
||||
if err == sql.ErrNoRows || len(u) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
u = []user.User{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("get users for spaces for user %s", ctx.UserID))
|
||||
|
@ -282,6 +285,8 @@ func (s Scope) CountActiveUsers() (c int) {
|
|||
|
||||
// MatchUsers returns users that have match to either firstname, lastname or email.
|
||||
func (s Scope) MatchUsers(ctx domain.RequestContext, text string, maxMatches int) (u []user.User, err error) {
|
||||
u = []user.User{}
|
||||
|
||||
text = strings.TrimSpace(strings.ToLower(text))
|
||||
likeQuery := ""
|
||||
if len(text) > 0 {
|
||||
|
@ -290,15 +295,14 @@ func (s Scope) MatchUsers(ctx domain.RequestContext, text string, maxMatches int
|
|||
|
||||
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.lastversion, u.created, u.revised,
|
||||
u.global, a.active, a.editor, a.admin, a.users as viewusers
|
||||
u.global, a.active, a.editor, a.admin, a.users as viewusers, a.analytics
|
||||
FROM user u, account a
|
||||
WHERE a.orgid=? AND u.refid=a.userid AND a.active=1 `+likeQuery+
|
||||
`ORDER BY u.firstname,u.lastname LIMIT `+strconv.Itoa(maxMatches),
|
||||
ctx.OrgID)
|
||||
|
||||
if err == sql.ErrNoRows || len(u) == 0 {
|
||||
if err == sql.ErrNoRows {
|
||||
err = nil
|
||||
u = []user.User{}
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("matching users for org %s", ctx.OrgID))
|
||||
|
|
|
@ -40,6 +40,7 @@ func AttachUserAccounts(ctx domain.RequestContext, s domain.Store, orgID string,
|
|||
u.Admin = false
|
||||
u.Active = false
|
||||
u.ViewUsers = false
|
||||
u.Analytics = false
|
||||
|
||||
for _, account := range u.Accounts {
|
||||
if account.OrgID == orgID {
|
||||
|
@ -47,6 +48,7 @@ func AttachUserAccounts(ctx domain.RequestContext, s domain.Store, orgID string,
|
|||
u.Editor = account.Editor
|
||||
u.Active = account.Active
|
||||
u.ViewUsers = account.Users
|
||||
u.Analytics = account.Analytics
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ func main() {
|
|||
// product details
|
||||
rt.Product = env.ProdInfo{}
|
||||
rt.Product.Major = "1"
|
||||
rt.Product.Minor = "60"
|
||||
rt.Product.Minor = "61"
|
||||
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"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -27,7 +27,7 @@ export default Component.extend(AuthProvider, ModalMixin, {
|
|||
init() {
|
||||
this._super(...arguments);
|
||||
this.password = {};
|
||||
this.selectedUsers = [];
|
||||
this.selectedUsers = [];
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
|
@ -91,6 +91,13 @@ export default Component.extend(AuthProvider, ModalMixin, {
|
|||
cb(user);
|
||||
},
|
||||
|
||||
toggleAnalytics(id) {
|
||||
let user = this.users.findBy("id", id);
|
||||
user.set('analytics', !user.get('analytics'));
|
||||
let cb = this.get('onSave');
|
||||
cb(user);
|
||||
},
|
||||
|
||||
toggleUsers(id) {
|
||||
let user = this.users.findBy("id", id);
|
||||
user.set('viewUsers', !user.get('viewUsers'));
|
||||
|
@ -208,7 +215,7 @@ export default Component.extend(AuthProvider, ModalMixin, {
|
|||
|
||||
this.get('groupSvc').leave(groupId, userId).then(() => {
|
||||
this.filterUsers();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onJoinGroup(groupId) {
|
||||
|
@ -222,7 +229,7 @@ export default Component.extend(AuthProvider, ModalMixin, {
|
|||
|
||||
this.get('groupSvc').join(groupId, userId).then(() => {
|
||||
this.filterUsers();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -9,10 +9,14 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import { computed } from '@ember/object';
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
resultPhrase: '',
|
||||
searchQuery: computed('keywords', function() {
|
||||
return encodeURIComponent(this.get('keywords'));
|
||||
}),
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
|
56
gui/app/components/search/search-view.js
Normal file
56
gui/app/components/search/search-view.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
// 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 { A } from '@ember/array';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
searchSvc: service('search'),
|
||||
results: A([]),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
fetch() {
|
||||
let payload = {
|
||||
keywords: this.get('filter'),
|
||||
doc: this.get('matchDoc'),
|
||||
attachment: this.get('matchFile'),
|
||||
tag: this.get('matchTag'),
|
||||
content: this.get('matchContent'),
|
||||
slog: this.get('slog')
|
||||
};
|
||||
|
||||
payload.keywords = payload.keywords.trim();
|
||||
|
||||
if (payload.keywords.length == 0) {
|
||||
this.set('results', A([]));
|
||||
return;
|
||||
}
|
||||
if (!payload.doc && !payload.tag && !payload.content && !payload.attachment) {
|
||||
this.set('results', A([]));
|
||||
return;
|
||||
}
|
||||
|
||||
this.get('searchSvc').find(payload).then( (response) => {
|
||||
this.set('results', response);
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
onSearch() {
|
||||
this.fetch();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -9,8 +9,11 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import { inject as service } from '@ember/service';
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ['col', 'col-sm-8']
|
||||
appMeta: service(),
|
||||
classNames: ['col', 'col-sm-8'],
|
||||
selectItem: '',
|
||||
});
|
||||
|
|
|
@ -22,6 +22,7 @@ export default Model.extend({
|
|||
editor: attr('boolean', { defaultValue: false }),
|
||||
admin: attr('boolean', { defaultValue: false }),
|
||||
viewUsers: attr('boolean', { defaultValue: false }),
|
||||
analytics: attr('boolean', { defaultValue: false }),
|
||||
global: attr('boolean', { defaultValue: false }),
|
||||
accounts: attr(),
|
||||
groups: attr(),
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
// 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
|
||||
|
||||
import { inject as service } from '@ember/service';
|
||||
import Controller from '@ember/controller';
|
||||
|
||||
export default Controller.extend({});
|
||||
export default Controller.extend({
|
||||
appMeta: service()
|
||||
});
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default Route.extend(AuthenticatedRouteMixin, {
|
||||
beforeModel: function (transition) {
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
{{#toolbar/t-toolbar}}
|
||||
{{#toolbar/t-links}}
|
||||
{{#link-to "folders" class="link" tagName="li" }}Spaces{{/link-to}}
|
||||
{{/toolbar/t-links}}
|
||||
{{#toolbar/t-actions}}
|
||||
{{/toolbar/t-actions}}
|
||||
|
@ -10,7 +9,7 @@
|
|||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col my-5">
|
||||
<div class="col my-5 text-center">
|
||||
<ul class="tabnav-control">
|
||||
{{#link-to 'customize.general' activeClass='selected' class="tab" tagName="li" }}General{{/link-to}}
|
||||
{{#link-to 'customize.folders' activeClass='selected' class="tab" tagName="li" }}Spaces{{/link-to}}
|
||||
|
@ -21,8 +20,13 @@
|
|||
{{#link-to 'customize.license' activeClass='selected' class="tab" tagName="li" }}License{{/link-to}}
|
||||
{{#link-to 'customize.auth' activeClass='selected' class="tab" tagName="li" }}Authentication{{/link-to}}
|
||||
{{#link-to 'customize.search' activeClass='selected' class="tab" tagName="li" }}Search{{/link-to}}
|
||||
{{#if (eq appMeta.edition 'Enterprise')}}
|
||||
{{#link-to 'customize.audit' activeClass='selected' class="tab" tagName="li" }}Audit{{/link-to}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if (eq appMeta.edition 'Enterprise')}}
|
||||
{{#link-to 'customize.archive' activeClass='selected' class="tab" tagName="li" }}Archive{{/link-to}}
|
||||
{{/if}}
|
||||
{{#link-to 'customize.archive' activeClass='selected' class="tab" tagName="li" }}Archive{{/link-to}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -214,12 +214,16 @@ export default Controller.extend(TooltipMixin, {
|
|||
},
|
||||
|
||||
refresh() {
|
||||
return new EmberPromise((resolve) => {
|
||||
this.get('documentService').fetchPages(this.get('document.id'), this.get('session.user.id')).then((data) => {
|
||||
this.set('pages', data);
|
||||
});
|
||||
|
||||
this.get('sectionService').getSpaceBlocks(this.get('folder.id')).then((data) => {
|
||||
this.set('blocks', data);
|
||||
});
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,13 @@ export default Route.extend(AuthenticatedRouteMixin, {
|
|||
folderService: service('folder'),
|
||||
userService: service('user'),
|
||||
|
||||
beforeModel() {
|
||||
beforeModel(transition) {
|
||||
// Note the source that sent user to this document.
|
||||
let source = transition.queryParams.source;
|
||||
if (is.null(source) || is.undefined(source)) source = "";
|
||||
|
||||
return new EmberPromise((resolve) => {
|
||||
this.get('documentService').fetchPages(this.paramsFor('document').document_id, this.get('session.user.id')).then((data) => {
|
||||
this.get('documentService').fetchPages(this.paramsFor('document').document_id, this.get('session.user.id'), source).then((data) => {
|
||||
this.set('pages', data);
|
||||
resolve();
|
||||
});
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{{toolbar/nav-bar}} {{toolbar/for-document document=document spaces=folders space=folder
|
||||
{{toolbar/nav-bar}}
|
||||
|
||||
{{toolbar/for-document document=document spaces=folders space=folder
|
||||
permissions=permissions roles=roles tab=tab versions=versions
|
||||
onDocumentDelete=(action 'onDocumentDelete')
|
||||
onSaveTemplate=(action 'onSaveTemplate')
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
{{#toolbar/t-toolbar}}
|
||||
{{#toolbar/t-links}}
|
||||
{{#link-to "folders" class="link" tagName="li"}}Spaces{{/link-to}}
|
||||
{{#link-to "folder" model.folder.id model.folder.slug class="link" tagName="li"}}{{model.folder.name}}{{/link-to}}
|
||||
{{#link-to "folder" model.folder.id model.folder.slug class="link selected" tagName="li"}}{{model.folder.name}}{{/link-to}}
|
||||
{{/toolbar/t-links}}
|
||||
{{/toolbar/t-toolbar}}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
{{#toolbar/t-toolbar}}
|
||||
{{#toolbar/t-links}}
|
||||
{{#link-to "folders" class="link" tagName="li"}}Spaces{{/link-to}}
|
||||
{{/toolbar/t-links}}
|
||||
{{#toolbar/t-actions}}
|
||||
{{/toolbar/t-actions}}
|
||||
|
|
|
@ -9,64 +9,14 @@
|
|||
//
|
||||
// https://documize.com
|
||||
|
||||
import { debounce } from '@ember/runloop';
|
||||
import { inject as service } from '@ember/service';
|
||||
import Controller from '@ember/controller';
|
||||
|
||||
export default Controller.extend({
|
||||
searchService: service('search'),
|
||||
queryParams: ['filter', 'matchDoc', 'matchContent', 'matchTag', 'matchFile'],
|
||||
queryParams: ['filter', 'matchDoc', 'matchContent', 'matchTag', 'matchFile', 'slog'],
|
||||
filter: '',
|
||||
onKeywordChange: function () {
|
||||
debounce(this, this.fetch, 750);
|
||||
}.observes('filter'),
|
||||
|
||||
matchDoc: true,
|
||||
onMatchDoc: function () {
|
||||
debounce(this, this.fetch, 750);
|
||||
}.observes('matchDoc'),
|
||||
|
||||
matchContent: true,
|
||||
onMatchContent: function () {
|
||||
debounce(this, this.fetch, 750);
|
||||
}.observes('matchContent'),
|
||||
|
||||
matchTag: false,
|
||||
onMatchTag: function () {
|
||||
debounce(this, this.fetch, 750);
|
||||
}.observes('matchTag'),
|
||||
|
||||
matchFile: false,
|
||||
onMatchFile: function () {
|
||||
debounce(this, this.fetch, 750);
|
||||
}.observes('matchFile'),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.results = [];
|
||||
},
|
||||
|
||||
fetch() {
|
||||
let self = this;
|
||||
let payload = {
|
||||
keywords: this.get('filter'),
|
||||
doc: this.get('matchDoc'),
|
||||
attachment: this.get('matchFile'),
|
||||
tag: this.get('matchTag'),
|
||||
content: this.get('matchContent')
|
||||
};
|
||||
|
||||
payload.keywords = payload.keywords.trim();
|
||||
|
||||
if (payload.keywords.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (!payload.doc && !payload.tag && !payload.content && !payload.attachment) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.get('searchService').find(payload).then(function(response) {
|
||||
self.set('results', response);
|
||||
});
|
||||
},
|
||||
slog: false,
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -15,5 +15,5 @@ import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-rout
|
|||
export default Route.extend(AuthenticatedRouteMixin, {
|
||||
activate() {
|
||||
this.get('browser').setTitle('Search');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -2,35 +2,9 @@
|
|||
|
||||
{{#toolbar/t-toolbar}}
|
||||
{{#toolbar/t-links}}
|
||||
{{#link-to "folders" class="link" tagName="li" }}Spaces{{/link-to}}
|
||||
{{/toolbar/t-links}}
|
||||
{{#toolbar/t-actions}}
|
||||
{{/toolbar/t-actions}}
|
||||
{{/toolbar/t-toolbar}}
|
||||
|
||||
<div class="container">
|
||||
<div class="view-search mt-5">
|
||||
<div class="heading">Search</div>
|
||||
<div class="form-group mt-4">
|
||||
{{focus-input type="text" value=filter class="form-control mb-4" placeholder='a OR b, x AND y, "phrase mat*"'}}
|
||||
<div class="form-check form-check-inline">
|
||||
{{input type="checkbox" id="search-1" class="form-check-input" checked=matchDoc}}
|
||||
<label class="form-check-label" for="search-1"> document title</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
{{input type="checkbox" id="search-2" class="form-check-input" checked=matchContent}}
|
||||
<label class="form-check-label" for="search-2"> content</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
{{input type="checkbox" id="search-3" class="form-check-input" checked=matchTag}}
|
||||
<label class="form-check-label" for="search-3"> tag name</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
{{input type="checkbox" id="search-4" class="form-check-input" checked=matchFile}}
|
||||
<label class="form-check-label" for="search-4"> attachment name</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{search/search-results results=results}}
|
||||
</div>
|
||||
</div>
|
||||
{{search/search-view filter=filter matchDoc=matchDoc matchContent=matchContent matchTag=matchTag matchFile=matchFile slog=slog}}
|
||||
|
|
|
@ -25,6 +25,10 @@ export default Router.map(function () {
|
|||
path: 'dashboard'
|
||||
});
|
||||
|
||||
this.route('analytics', {
|
||||
path: 'analytics'
|
||||
});
|
||||
|
||||
this.route(
|
||||
'folder',
|
||||
{
|
||||
|
|
|
@ -40,15 +40,15 @@ export default AjaxService.extend({
|
|||
|
||||
// when unauthorized on local API AJAX calls, redirect to app root
|
||||
if (status === 401 && is.not.undefined(appVersion) && is.not.includes(window.location.href, '/auth')) {
|
||||
this.get('localStorage').clearAll();
|
||||
this.get('localStorage').clearAll();
|
||||
window.location.href = 'auth/login';
|
||||
}
|
||||
|
||||
if (is.not.empty(userUpdate)) {
|
||||
let latest = JSON.parse(userUpdate);
|
||||
|
||||
if (!latest.active || user.editor !== latest.editor || user.admin !== latest.admin || user.viewUsers !== latest.viewUsers) {
|
||||
this.get('localStorage').clearAll();
|
||||
if (!latest.active || user.editor !== latest.editor || user.admin !== latest.admin || user.analytics !== latest.analytics || user.viewUsers !== latest.viewUsers) {
|
||||
this.get('localStorage').clearAll();
|
||||
window.location.href = 'auth/login';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -375,7 +375,9 @@ export default Service.extend({
|
|||
// This method bulk fetches data to reduce network chatter.
|
||||
// We produce a bunch of calculated boolean's for UI display purposes
|
||||
// that can tell us quickly about pending changes for UI display.
|
||||
fetchPages(documentId, currentUserId) {
|
||||
|
||||
// Source - optional identifier of (document) referrer.
|
||||
fetchPages(documentId, currentUserId, source) {
|
||||
let constants = this.get('constants');
|
||||
let changePending = false;
|
||||
let changeAwaitingReview = false;
|
||||
|
@ -384,7 +386,9 @@ export default Service.extend({
|
|||
let userHasChangeAwaitingReview = false;
|
||||
let userHasChangeRejected = false;
|
||||
|
||||
return this.get('ajax').request(`fetch/page/${documentId}`, {
|
||||
if (is.null(source) || is.undefined(source)) source = "";
|
||||
|
||||
return this.get('ajax').request(`fetch/page/${documentId}?source=${source}`, {
|
||||
method: 'GET'
|
||||
}).then((response) => {
|
||||
let data = A([]);
|
||||
|
|
|
@ -30,6 +30,7 @@ $color-dark: #434343;
|
|||
$color-gray: #8b9096;
|
||||
$color-gray-light: #d8d8d8;
|
||||
$color-gray-light2: #eaeaea;
|
||||
$color-gray-light3: #f9f9f9;
|
||||
$color-border: #d3d3d3;
|
||||
|
||||
// colors
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
> .links {
|
||||
display: inlne-block;
|
||||
|
||||
> .link {
|
||||
> .link, div > .link {
|
||||
color: $color-gray;
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
margin-right: 30px;
|
||||
cursor: pointer;
|
||||
text-transform: uppercase;
|
||||
@include ease-in();
|
||||
// border-bottom: 2px solid $color-gray-light;
|
||||
|
||||
|
@ -19,7 +20,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
> .selected {
|
||||
> .selected, div > .link {
|
||||
color: $color-link;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="view-customize my-5">
|
||||
|
||||
|
||||
<div class="my-2">
|
||||
<span class="font-weight-bold">Spaces</span>
|
||||
<span class="text-muted"> — can add spaces, both personal and shared with others</span>
|
||||
|
@ -12,6 +12,10 @@
|
|||
<span class="font-weight-bold">Admin</span>
|
||||
<span class="text-muted"> — can manage all aspects of Documize, like this screen</span>
|
||||
</div>
|
||||
<div class="my-2">
|
||||
<span class="font-weight-bold">Analytics</span>
|
||||
<span class="text-muted"> — can view analytical reports</span>
|
||||
</div>
|
||||
<div class="mt-2 mb-4">
|
||||
<span class="font-weight-bold">Active</span>
|
||||
<span class="text-muted"> — can login and use Documize</span>
|
||||
|
@ -28,11 +32,12 @@
|
|||
<th class="text-muted">
|
||||
{{#if hasSelectedUsers}}
|
||||
<button id="bulk-delete-users" type="button" class="btn btn-danger" data-toggle="modal" data-target="#admin-user-delete-modal" data-backdrop="static">Delete selected users</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</th>
|
||||
<th class="no-width">Spaces</th>
|
||||
<th class="no-width">Visible</th>
|
||||
<th class="no-width">Admin</th>
|
||||
<th class="no-width">Analytics</th>
|
||||
<th class="no-width">Active</th>
|
||||
<th class="no-width">
|
||||
</th>
|
||||
|
@ -87,6 +92,13 @@
|
|||
<i class="material-icons checkbox" {{action 'toggleAdmin' user.id}}>check_box_outline_blank</i>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="no-width text-center">
|
||||
{{#if user.analytics}}
|
||||
<i class="material-icons checkbox" {{action 'toggleAnalytics' user.id}}>check_box</i>
|
||||
{{else}}
|
||||
<i class="material-icons checkbox" {{action 'toggleAnalytics' user.id}}>check_box_outline_blank</i>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="no-width text-center">
|
||||
{{#if user.me}}
|
||||
<i class="material-icons color-gray">check_box</i>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<div class="col-12 col-sm-3 heading">Tags</div>
|
||||
<div class="col-12 col-sm-9 value">
|
||||
{{#each tagz as |t index|}}
|
||||
{{#link-to 'search' (query-params filter=t matchTag=true)}}
|
||||
{{#link-to 'search' (query-params filter=t matchTag=true matchDoc=false matchContent=false matchFile=false)}}
|
||||
{{concat '#' t}}
|
||||
{{/link-to}}
|
||||
{{/each}}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<ul class="documents">
|
||||
{{#each documents key="id" as |result index|}}
|
||||
<li class="document">
|
||||
<a class="link" href="s/{{result.spaceId}}/{{result.spaceSlug}}/d/{{ result.documentId }}/{{result.documentSlug}}?page={{ result.itemId }}">
|
||||
{{#link-to 'document.index' result.spaceId result.spaceSlug result.documentId result.documentSlug (query-params currentPageId=result.itemId source=searchQuery) class="link"}}
|
||||
<div class="title">
|
||||
{{result.document}}
|
||||
{{#if (gt result.versionId.length 0)}}
|
||||
|
@ -16,7 +16,7 @@
|
|||
{{#if result.template}}
|
||||
<button type="button" class="mt-3 btn btn-warning text-uppercase font-weight-bold">TEMPLATE</button>
|
||||
{{/if}}
|
||||
</a>
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
|
31
gui/app/templates/components/search/search-view.hbs
Normal file
31
gui/app/templates/components/search/search-view.hbs
Normal file
|
@ -0,0 +1,31 @@
|
|||
<div class="container">
|
||||
<div class="view-search mt-5">
|
||||
<div class="heading">Search</div>
|
||||
<form onsubmit={{action 'onSearch'}}>
|
||||
<div class="form-group mt-4">
|
||||
{{focus-input type="text" value=filter class="form-control mb-4" placeholder='a OR b, x AND y, "phrase mat*"'}}
|
||||
<div class="form-check form-check-inline">
|
||||
{{input type="checkbox" id="search-1" class="form-check-input" checked=matchDoc}}
|
||||
<label class="form-check-label" for="search-1"> document title</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
{{input type="checkbox" id="search-2" class="form-check-input" checked=matchContent}}
|
||||
<label class="form-check-label" for="search-2"> content</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
{{input type="checkbox" id="search-3" class="form-check-input" checked=matchTag}}
|
||||
<label class="form-check-label" for="search-3"> tag name</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
{{input type="checkbox" id="search-4" class="form-check-input" checked=matchFile}}
|
||||
<label class="form-check-label" for="search-4"> attachment name</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-success" {{action 'onSearch'}}>Search</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{{search/search-results results=results keywords=filter}}
|
||||
</div>
|
||||
</div>
|
|
@ -1,8 +1,7 @@
|
|||
{{#toolbar/t-toolbar}}
|
||||
|
||||
{{#toolbar/t-links}}
|
||||
{{#link-to "folders" class="link" tagName="li"}}Spaces{{/link-to}}
|
||||
{{#link-to "folder" space.id space.slug class="link" tagName="li"}}{{space.name}}{{/link-to}}
|
||||
{{#link-to "folder" space.id space.slug class="link selected" tagName="li"}}{{space.name}}{{/link-to}}
|
||||
{{#if showDocumentLink}}
|
||||
{{#link-to 'document.index' space.id space.slug document.id document.slug class="link"}}{{document.name}}{{/link-to}}
|
||||
{{/if}}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{{#toolbar/t-toolbar}}
|
||||
|
||||
{{#toolbar/t-links}}
|
||||
{{#link-to "folders" class="link" tagName="li"}}Spaces{{/link-to}}
|
||||
{{#toolbar/t-links selectItem="spaces"}}
|
||||
{{/toolbar/t-links}}
|
||||
|
||||
{{#toolbar/t-actions}}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
{{#toolbar/t-toolbar}}
|
||||
{{#toolbar/t-links}}
|
||||
{{#link-to "folders" class="link selected" tagName="li"}}Spaces{{/link-to}}
|
||||
{{#toolbar/t-links selectItem="spaces"}}
|
||||
{{/toolbar/t-links}}
|
||||
{{#toolbar/t-actions}}
|
||||
{{#if session.isEditor}}
|
||||
|
|
|
@ -63,7 +63,8 @@
|
|||
{{/if}}
|
||||
{{/if}}
|
||||
<a href="#" class="dropdown-item {{if hasWhatsNew 'color-whats-new font-weight-bold'}}" {{action 'onShowWhatsNewModal'}}>What's New</a>
|
||||
<a href="#" class="dropdown-item" data-toggle="modal" data-target="#about-documize-modal" data-backdrop="static">About Documize</a>
|
||||
<a href="https://docs.documize.com" target="_blank" class="dropdown-item">Help</a>
|
||||
<a href="#" class="dropdown-item" data-toggle="modal" data-target="#about-documize-modal" data-backdrop="static">About</a>
|
||||
{{#if enableLogout}}
|
||||
<div class="dropdown-divider"></div>
|
||||
{{#link-to 'auth.logout' class="dropdown-item" }}Logout{{/link-to}}
|
||||
|
@ -140,7 +141,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
<div class="toolbar">
|
||||
<ul class="links">
|
||||
{{#if (eq appMeta.edition 'Community')}}
|
||||
{{#link-to "folders" class=(if (eq selectItem 'spaces') 'link selected' 'link') tagName="li"}}Spaces{{/link-to}}
|
||||
{{/if}}
|
||||
{{#if (eq appMeta.edition 'Enterprise')}}
|
||||
{{#if session.isEditor}}
|
||||
{{#link-to "dashboard" class=(if (eq selectItem 'dashboard') 'link selected' 'link') tagName="li"}}Actions{{/link-to}}
|
||||
{{#link-to "analytics" class=(if (eq selectItem 'analytics') 'link selected' 'link') tagName="li"}}Insights{{/link-to}}
|
||||
{{/if}}
|
||||
{{#link-to "folders" class=(if (eq selectItem 'spaces') 'link selected' 'link') tagName="li"}}Spaces{{/link-to}}
|
||||
{{/if}}
|
||||
{{yield}}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "documize",
|
||||
"version": "1.60.0",
|
||||
"version": "1.61.0",
|
||||
"description": "The Document IDE",
|
||||
"private": true,
|
||||
"repository": "",
|
||||
|
|
|
@ -16,14 +16,15 @@ import "github.com/documize/community/model"
|
|||
// Account links a User to an Organization.
|
||||
type Account struct {
|
||||
model.BaseEntity
|
||||
Admin bool `json:"admin"`
|
||||
Editor bool `json:"editor"`
|
||||
Users bool `json:"viewUsers"` // either view all users or just users in your space
|
||||
UserID string `json:"userId"`
|
||||
OrgID string `json:"orgId"`
|
||||
Company string `json:"company"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Domain string `json:"domain"`
|
||||
Active bool `json:"active"`
|
||||
Admin bool `json:"admin"`
|
||||
Editor bool `json:"editor"`
|
||||
Users bool `json:"viewUsers"` // either view all users or just users in your space
|
||||
Analytics bool `json:"analytics"` // view content analytics
|
||||
UserID string `json:"userId"`
|
||||
OrgID string `json:"orgId"`
|
||||
Company string `json:"company"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Domain string `json:"domain"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
|
|
@ -23,8 +23,11 @@ type UserActivity struct {
|
|||
PageID string `json:"pageId"`
|
||||
ActivityType Type `json:"activityType"`
|
||||
SourceType SourceType `json:"sourceType"`
|
||||
SourceName string `json:"sourceName"`
|
||||
Metadata string `json:"metadata"`
|
||||
Created time.Time `json:"created"`
|
||||
|
||||
// Read-only outbound fields (e.g. for UI display)
|
||||
SourceName string `json:"sourceName"`
|
||||
}
|
||||
|
||||
// DocumentActivity represents an activity taken against a document.
|
||||
|
@ -39,6 +42,7 @@ type DocumentActivity struct {
|
|||
Firstname string `json:"firstname"`
|
||||
Lastname string `json:"lastname"`
|
||||
ActivityType int `json:"activityType"`
|
||||
Metadata string `json:"metadata"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
|
@ -57,50 +61,57 @@ const (
|
|||
|
||||
// SourceTypePage indicates activity against a document page.
|
||||
SourceTypePage SourceType = 3
|
||||
|
||||
// SourceTypeSearch indicates activity on search page.
|
||||
SourceTypeSearch SourceType = 4
|
||||
)
|
||||
|
||||
const (
|
||||
// TypeCreated records user document creation
|
||||
// TypeCreated records user object creation (document or space).
|
||||
TypeCreated Type = 1
|
||||
|
||||
// TypeRead states user has read document
|
||||
// TypeRead states user has consumed object (document or space).
|
||||
TypeRead Type = 2
|
||||
|
||||
// TypeEdited states user has editing document
|
||||
// TypeEdited states user has editing document.
|
||||
TypeEdited Type = 3
|
||||
|
||||
// TypeDeleted records user deleting space/document
|
||||
// TypeDeleted records user deleting space/document.
|
||||
TypeDeleted Type = 4
|
||||
|
||||
// TypeArchived records user archiving space/document
|
||||
// TypeArchived records user archiving space/document.
|
||||
TypeArchived Type = 5
|
||||
|
||||
// TypeApproved records user approval of document
|
||||
// TypeApproved records user approval of document.
|
||||
TypeApproved Type = 6
|
||||
|
||||
// TypeReverted records user content roll-back to previous version
|
||||
// TypeReverted records user content roll-back to previous document version.
|
||||
TypeReverted Type = 7
|
||||
|
||||
// TypePublishedTemplate records user creating new document template
|
||||
// TypePublishedTemplate records user creating new document template.
|
||||
TypePublishedTemplate Type = 8
|
||||
|
||||
// TypePublishedBlock records user creating reusable content block
|
||||
// TypePublishedBlock records user creating reusable content block.
|
||||
TypePublishedBlock Type = 9
|
||||
|
||||
// TypeCommented records user providing document feedback
|
||||
// TypeCommented records user providing document feedback.
|
||||
TypeCommented Type = 10
|
||||
|
||||
// TypeRejected records user rejecting document
|
||||
// TypeRejected records user rejecting document.
|
||||
TypeRejected Type = 11
|
||||
|
||||
// TypeSentSecureLink records user sending secure document link to email address(es)
|
||||
// TypeSentSecureLink records user sending secure document link via email.
|
||||
TypeSentSecureLink Type = 12
|
||||
|
||||
// TypeDraft records user marking space/document as draft
|
||||
// TypeDraft records user marking space/document as draft.
|
||||
TypeDraft Type = 13
|
||||
|
||||
// TypeVersioned records user creating new document version
|
||||
// TypeVersioned records user creating new document version.
|
||||
TypeVersioned Type = 14
|
||||
|
||||
// TypeSearched records user performing document keyword search.
|
||||
// Metadata field should contain search terms.
|
||||
TypeSearched Type = 15
|
||||
)
|
||||
|
||||
// TypeName returns one-work descriptor for activity type
|
||||
|
@ -130,6 +141,12 @@ func TypeName(t Type) string {
|
|||
return "Reject"
|
||||
case TypeSentSecureLink:
|
||||
return "Share"
|
||||
case TypeDraft:
|
||||
return "Draft"
|
||||
case TypeVersioned:
|
||||
return "Version"
|
||||
case TypeSearched:
|
||||
return "Search"
|
||||
}
|
||||
|
||||
return ""
|
||||
|
|
|
@ -18,6 +18,7 @@ type QueryOptions struct {
|
|||
Tag bool `json:"tag"`
|
||||
Attachment bool `json:"attachment"`
|
||||
Content bool `json:"content"`
|
||||
SkipLog bool `json:"slog"`
|
||||
}
|
||||
|
||||
// QueryResult represents 'presentable' search results.
|
||||
|
|
|
@ -30,6 +30,7 @@ type User struct {
|
|||
Editor bool `json:"editor"`
|
||||
Admin bool `json:"admin"`
|
||||
ViewUsers bool `json:"viewUsers"`
|
||||
Analytics bool `json:"analytics"`
|
||||
Global bool `json:"global"`
|
||||
Password string `json:"-"`
|
||||
Salt string `json:"-"`
|
||||
|
|
|
@ -141,6 +141,7 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http
|
|||
rc.Administrator = false
|
||||
rc.Editor = false
|
||||
rc.Global = false
|
||||
rc.Analytics = false
|
||||
rc.AppURL = r.Host
|
||||
rc.Subdomain = organization.GetSubdomainFromHost(r)
|
||||
rc.SSL = r.TLS != nil
|
||||
|
@ -170,6 +171,7 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http
|
|||
rc.Administrator = u.Admin
|
||||
rc.Editor = u.Editor
|
||||
rc.Global = u.Global
|
||||
rc.Analytics = u.Analytics
|
||||
rc.Fullname = u.Fullname()
|
||||
|
||||
// We send back with every HTTP request/response cycle the latest
|
||||
|
@ -179,12 +181,14 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http
|
|||
Active bool `json:"active"`
|
||||
Admin bool `json:"admin"`
|
||||
Editor bool `json:"editor"`
|
||||
Analytics bool `json:"analytics"`
|
||||
ViewUsers bool `json:"viewUsers"`
|
||||
}
|
||||
|
||||
state.Active = u.Active
|
||||
state.Admin = u.Admin
|
||||
state.Editor = u.Editor
|
||||
state.Analytics = u.Analytics
|
||||
state.ViewUsers = u.ViewUsers
|
||||
sb, err := json.Marshal(state)
|
||||
|
||||
|
@ -234,6 +238,7 @@ func (m *middleware) preAuthorizeStaticAssets(rt *env.Runtime, r *http.Request)
|
|||
ctx.OrgName = org.Title
|
||||
ctx.Administrator = false
|
||||
ctx.Editor = false
|
||||
ctx.Analytics = false
|
||||
ctx.Global = false
|
||||
ctx.AppURL = r.Host
|
||||
ctx.SSL = r.TLS != nil
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue