mirror of
https://github.com/documize/community.git
synced 2025-07-23 15:19:42 +02:00
Merge pull request #81 from documize/user-activity-logging
User activity logging
This commit is contained in:
commit
22a63f7deb
30 changed files with 5603 additions and 4332 deletions
|
@ -8,7 +8,7 @@ The mission is to bring software dev inspired features (refactoring, testing, li
|
|||
|
||||
## Latest version
|
||||
|
||||
v0.41.0
|
||||
v0.42.0
|
||||
|
||||
## OS Support
|
||||
|
||||
|
@ -19,7 +19,7 @@ v0.41.0
|
|||
## Tech stack
|
||||
|
||||
- EmberJS (v2.10.0)
|
||||
- Go (v1.7.3)
|
||||
- Go (v1.8)
|
||||
- MySQL (v5.7.10+) or Percona (v5.7.16-10+)
|
||||
|
||||
## Documentation
|
||||
|
|
32
app/app/models/user-activity.js
Normal file
32
app/app/models/user-activity.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
// 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 Ember from 'ember';
|
||||
|
||||
export default Model.extend({
|
||||
documentName: attr('string'),
|
||||
folderId: attr('string'),
|
||||
contributed: attr('string'),
|
||||
viewed: attr('string'),
|
||||
created: attr('string'),
|
||||
|
||||
hasContributed: Ember.computed('contributed', function () {
|
||||
return this.get('contributed').length > 0;
|
||||
}),
|
||||
hasViewed: Ember.computed('viewed', function () {
|
||||
return this.get('viewed').length > 0;
|
||||
}),
|
||||
hasCreated: Ember.computed('created', function () {
|
||||
return this.get('created').length > 0;
|
||||
})
|
||||
});
|
|
@ -32,7 +32,6 @@ $color-toast: #4c4c4c;
|
|||
$color-checkbox: #2667af;
|
||||
$color-card-active: #f7fcff;
|
||||
|
||||
$color-chip: #98A2AB;
|
||||
$color-chip: #dff0f9;
|
||||
$color-chip-border: #daeaf3;
|
||||
$color-chip-text: #1b88e3;
|
||||
|
@ -40,43 +39,36 @@ $color-chip-text: #1b88e3;
|
|||
$color-symbol-box: #dce5e8;
|
||||
$color-symbol-icon: #a4b8be;
|
||||
|
||||
$color-table-border: #e1e1e1;
|
||||
$color-table-header: #f5f5f5;
|
||||
|
||||
.color-white {
|
||||
color: $color-white !important;
|
||||
}
|
||||
|
||||
.color-off-white {
|
||||
color: $color-off-white !important;
|
||||
}
|
||||
|
||||
.color-black {
|
||||
color: $color-black !important;
|
||||
}
|
||||
|
||||
.color-off-black {
|
||||
color: $color-off-black !important;
|
||||
}
|
||||
|
||||
.color-primary {
|
||||
color: $color-primary !important;
|
||||
}
|
||||
|
||||
.color-link {
|
||||
color: $color-link !important;
|
||||
}
|
||||
|
||||
.color-blue {
|
||||
color: $color-blue !important;
|
||||
}
|
||||
|
||||
.color-red {
|
||||
color: $color-red !important;
|
||||
}
|
||||
|
||||
.color-green {
|
||||
color: $color-green !important;
|
||||
}
|
||||
|
||||
.color-gray {
|
||||
color: $color-gray !important;
|
||||
}
|
||||
|
@ -84,7 +76,9 @@ $color-symbol-icon: #a4b8be;
|
|||
.background-color-white {
|
||||
background-color: $color-white !important;
|
||||
}
|
||||
|
||||
.background-color-primary {
|
||||
background-color: $color-primary !important;
|
||||
}
|
||||
.background-color-green {
|
||||
background-color: $color-green !important;
|
||||
}
|
||||
|
|
|
@ -97,6 +97,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
> .right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
> li.danger {
|
||||
color: $color-red;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "documize",
|
||||
"version": "0.41.0",
|
||||
"version": "0.42.0",
|
||||
"description": "The Document IDE",
|
||||
"private": true,
|
||||
"repository": "",
|
||||
|
|
4548
app/vendor/moment.js
vendored
4548
app/vendor/moment.js
vendored
File diff suppressed because it is too large
Load diff
|
@ -62,19 +62,15 @@ func uploadDocument(w http.ResponseWriter, r *http.Request) (string, string, str
|
|||
}
|
||||
|
||||
// generate job id
|
||||
var job = "some-uuid"
|
||||
|
||||
newUUID, err := uuid.NewV4()
|
||||
|
||||
if err != nil {
|
||||
writeServerError(w, method, err)
|
||||
return "", "", ""
|
||||
}
|
||||
|
||||
job = newUUID.String()
|
||||
job := newUUID.String()
|
||||
|
||||
err = storageProvider.Upload(job, filename.Filename, b.Bytes())
|
||||
|
||||
if err != nil {
|
||||
writeServerError(w, method, err)
|
||||
return "", "", ""
|
||||
|
@ -229,10 +225,7 @@ func processDocument(p request.Persister, filename, job, folderID string, fileRe
|
|||
return
|
||||
}
|
||||
|
||||
// New code from normal conversion code
|
||||
|
||||
tx, err = request.Db.Beginx()
|
||||
|
||||
if err != nil {
|
||||
log.Error("Cannot begin a transatcion", err)
|
||||
return
|
||||
|
@ -248,9 +241,13 @@ func processDocument(p request.Persister, filename, job, folderID string, fileRe
|
|||
return
|
||||
}
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
p.RecordUserActivity(entity.UserActivity{
|
||||
LabelID: newDocument.LabelID,
|
||||
SourceID: newDocument.RefID,
|
||||
SourceType: entity.ActivitySourceTypeDocument,
|
||||
ActivityType: entity.ActivityTypeCreated})
|
||||
|
||||
// End new code
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -98,12 +98,31 @@ func GetDocument(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
json, err := json.Marshal(document)
|
||||
|
||||
if err != nil {
|
||||
writeJSONMarshalError(w, method, "document", err)
|
||||
return
|
||||
}
|
||||
|
||||
p.Context.Transaction, err = request.Db.Beginx()
|
||||
if err != nil {
|
||||
writeTransactionError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = p.RecordUserActivity(entity.UserActivity{
|
||||
LabelID: document.LabelID,
|
||||
SourceID: document.RefID,
|
||||
SourceType: entity.ActivitySourceTypeDocument,
|
||||
ActivityType: entity.ActivityTypeRead})
|
||||
|
||||
if err != nil {
|
||||
log.IfErr(p.Context.Transaction.Rollback())
|
||||
log.Error("Cannot record user activity", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.IfErr(p.Context.Transaction.Commit())
|
||||
|
||||
writeSuccessBytes(w, json)
|
||||
}
|
||||
|
||||
|
@ -259,8 +278,13 @@ func DeleteDocument(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
doc, err := p.GetDocument(documentID)
|
||||
if err != nil {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
if err != nil {
|
||||
writeTransactionError(w, method, err)
|
||||
return
|
||||
|
@ -284,6 +308,12 @@ func DeleteDocument(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
_ = p.RecordUserActivity(entity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: documentID,
|
||||
SourceType: entity.ActivitySourceTypeDocument,
|
||||
ActivityType: entity.ActivityTypeDeleted})
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
writeSuccessEmptyJSON(w)
|
||||
|
@ -404,7 +434,6 @@ func UpdateDocument(w http.ResponseWriter, r *http.Request) {
|
|||
d.RefID = documentID
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
|
||||
if err != nil {
|
||||
writeTransactionError(w, method, err)
|
||||
return
|
||||
|
@ -413,7 +442,6 @@ func UpdateDocument(w http.ResponseWriter, r *http.Request) {
|
|||
p.Context.Transaction = tx
|
||||
|
||||
err = p.UpdateDocument(d)
|
||||
|
||||
if err != nil {
|
||||
log.IfErr(tx.Rollback())
|
||||
writeGeneralSQLError(w, method, err)
|
||||
|
|
|
@ -68,8 +68,8 @@ func AddFolder(w http.ResponseWriter, r *http.Request) {
|
|||
id := util.UniqueID()
|
||||
folder.RefID = id
|
||||
folder.OrgID = p.Context.OrgID
|
||||
err = addFolder(p, &folder)
|
||||
|
||||
err = addFolder(p, &folder)
|
||||
if err != nil {
|
||||
log.IfErr(tx.Rollback())
|
||||
writeGeneralSQLError(w, method, err)
|
||||
|
@ -78,10 +78,9 @@ func AddFolder(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
folder, err = p.GetLabel(id)
|
||||
folder, _ = p.GetLabel(id)
|
||||
|
||||
json, err := json.Marshal(folder)
|
||||
|
||||
if err != nil {
|
||||
writeJSONMarshalError(w, method, "folder", err)
|
||||
return
|
||||
|
|
|
@ -84,6 +84,12 @@ func AddDocumentPage(w http.ResponseWriter, r *http.Request) {
|
|||
model.Meta.SetDefaults()
|
||||
// page.Title = template.HTMLEscapeString(page.Title)
|
||||
|
||||
doc, err := p.GetDocument(documentID)
|
||||
if err != nil {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
if err != nil {
|
||||
writeTransactionError(w, method, err)
|
||||
|
@ -110,12 +116,17 @@ func AddDocumentPage(w http.ResponseWriter, r *http.Request) {
|
|||
p.IncrementBlockUsage(model.Page.BlockID)
|
||||
}
|
||||
|
||||
_ = p.RecordUserActivity(entity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: model.Page.DocumentID,
|
||||
SourceType: entity.ActivitySourceTypeDocument,
|
||||
ActivityType: entity.ActivityTypeCreated})
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
newPage, _ := p.GetPage(pageID)
|
||||
|
||||
json, err := json.Marshal(newPage)
|
||||
|
||||
if err != nil {
|
||||
writeJSONMarshalError(w, method, "page", err)
|
||||
return
|
||||
|
@ -265,7 +276,6 @@ func GetDocumentPagesBatch(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
json, err := json.Marshal(pages)
|
||||
|
||||
if err != nil {
|
||||
writeJSONMarshalError(w, method, "document", err)
|
||||
return
|
||||
|
@ -299,13 +309,17 @@ func DeleteDocumentPage(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
doc, err := p.GetDocument(documentID)
|
||||
if err != nil {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
if err != nil {
|
||||
writeTransactionError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
p.Context.Transaction = tx
|
||||
|
||||
page, err := p.GetPage(pageID)
|
||||
|
@ -327,6 +341,12 @@ func DeleteDocumentPage(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
_ = p.RecordUserActivity(entity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: documentID,
|
||||
SourceType: entity.ActivitySourceTypeDocument,
|
||||
ActivityType: entity.ActivityTypeDeleted})
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
writeSuccessEmptyJSON(w)
|
||||
|
@ -352,7 +372,6 @@ func DeleteDocumentPages(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
defer utility.Close(r.Body)
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
|
||||
if err != nil {
|
||||
writeBadRequestError(w, method, "Bad body")
|
||||
return
|
||||
|
@ -366,13 +385,17 @@ func DeleteDocumentPages(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
doc, err := p.GetDocument(documentID)
|
||||
if err != nil {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
if err != nil {
|
||||
writeTransactionError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
p.Context.Transaction = tx
|
||||
|
||||
for _, page := range *model {
|
||||
|
@ -395,6 +418,12 @@ func DeleteDocumentPages(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
_ = p.RecordUserActivity(entity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: documentID,
|
||||
SourceType: entity.ActivitySourceTypeDocument,
|
||||
ActivityType: entity.ActivityTypeDeleted})
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
writeSuccessEmptyJSON(w)
|
||||
|
@ -406,22 +435,20 @@ func DeleteDocumentPages(w http.ResponseWriter, r *http.Request) {
|
|||
func UpdateDocumentPage(w http.ResponseWriter, r *http.Request) {
|
||||
method := "UpdateDocumentPage"
|
||||
p := request.GetPersister(r)
|
||||
params := mux.Vars(r)
|
||||
|
||||
if !p.Context.Editor {
|
||||
writeForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
params := mux.Vars(r)
|
||||
documentID := params["documentID"]
|
||||
|
||||
if len(documentID) == 0 {
|
||||
writeMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
pageID := params["pageID"]
|
||||
|
||||
if len(pageID) == 0 {
|
||||
writeMissingDataError(w, method, "pageID")
|
||||
return
|
||||
|
@ -429,7 +456,6 @@ func UpdateDocumentPage(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
defer utility.Close(r.Body)
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
|
||||
if err != nil {
|
||||
writeBadRequestError(w, method, "Bad request body")
|
||||
return
|
||||
|
@ -437,7 +463,6 @@ func UpdateDocumentPage(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
model := new(models.PageModel)
|
||||
err = json.Unmarshal(body, &model)
|
||||
|
||||
if err != nil {
|
||||
writePayloadError(w, method, err)
|
||||
return
|
||||
|
@ -448,8 +473,13 @@ func UpdateDocumentPage(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
doc, err := p.GetDocument(documentID)
|
||||
if err != nil {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
if err != nil {
|
||||
writeTransactionError(w, method, err)
|
||||
return
|
||||
|
@ -488,6 +518,12 @@ func UpdateDocumentPage(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
err = p.UpdatePageMeta(model.Meta, true) // change the UserID to the current one
|
||||
|
||||
_ = p.RecordUserActivity(entity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: model.Page.DocumentID,
|
||||
SourceType: entity.ActivitySourceTypeDocument,
|
||||
ActivityType: entity.ActivityTypeEdited})
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
updatedPage, err := p.GetPage(pageID)
|
||||
|
@ -894,6 +930,13 @@ func RollbackDocumentPage(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// fetch doc
|
||||
doc, err := p.GetDocument(documentID)
|
||||
if err != nil {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// roll back page
|
||||
page.Body = revision.Body
|
||||
refID := util.UniqueID()
|
||||
|
@ -916,6 +959,12 @@ func RollbackDocumentPage(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
_ = p.RecordUserActivity(entity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: page.DocumentID,
|
||||
SourceType: entity.ActivitySourceTypeDocument,
|
||||
ActivityType: entity.ActivityTypeReverted})
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
payload, err := json.Marshal(page)
|
||||
|
@ -962,6 +1011,12 @@ func CopyPage(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// fetch data
|
||||
doc, err := p.GetDocument(documentID)
|
||||
if err != nil {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
page, err := p.GetPage(pageID)
|
||||
if err == sql.ErrNoRows {
|
||||
writeNotFoundError(w, method, documentID)
|
||||
|
@ -1013,6 +1068,13 @@ func CopyPage(w http.ResponseWriter, r *http.Request) {
|
|||
p.IncrementBlockUsage(model.Page.BlockID)
|
||||
}
|
||||
|
||||
// Log action against target document
|
||||
_ = p.RecordUserActivity(entity.UserActivity{
|
||||
LabelID: doc.LabelID,
|
||||
SourceID: targetID,
|
||||
SourceType: entity.ActivitySourceTypeDocument,
|
||||
ActivityType: entity.ActivityTypeEdited})
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
newPage, _ := p.GetPage(pageID)
|
||||
|
|
|
@ -126,8 +126,7 @@ func SaveAsTemplate(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// Duplicate attachments
|
||||
attachments, err := p.GetAttachments(model.DocumentID)
|
||||
|
||||
attachments, _ := p.GetAttachments(model.DocumentID)
|
||||
for i, a := range attachments {
|
||||
a.DocumentID = docID
|
||||
a.RefID = util.UniqueID()
|
||||
|
@ -358,8 +357,8 @@ func StartDocumentFromSavedTemplate(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
pages, err = p.GetPages(templateID)
|
||||
attachments, err = p.GetAttachmentsWithData(templateID)
|
||||
pages, _ = p.GetPages(templateID)
|
||||
attachments, _ = p.GetAttachmentsWithData(templateID)
|
||||
}
|
||||
|
||||
// create new document
|
||||
|
|
|
@ -189,7 +189,6 @@ func AddUser(w http.ResponseWriter, r *http.Request) {
|
|||
userModel, err = getSecuredUser(p, p.Context.OrgID, userID)
|
||||
|
||||
json, err := json.Marshal(userModel)
|
||||
|
||||
if err != nil {
|
||||
writeJSONMarshalError(w, method, "user", err)
|
||||
return
|
||||
|
@ -442,6 +441,7 @@ func UpdateUser(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
account.Editor = user.Editor
|
||||
account.Admin = user.Admin
|
||||
account.Active = user.Active
|
||||
|
||||
err = p.UpdateAccount(account)
|
||||
if err != nil {
|
||||
|
@ -691,11 +691,13 @@ func attachUserAccounts(p request.Persister, orgID string, user *entity.User) {
|
|||
user.Accounts = a
|
||||
user.Editor = false
|
||||
user.Admin = false
|
||||
user.Active = false
|
||||
|
||||
for _, account := range user.Accounts {
|
||||
if account.OrgID == orgID {
|
||||
user.Admin = account.Admin
|
||||
user.Editor = account.Editor
|
||||
user.Active = account.Active
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
42
core/api/entity/nulltime.go
Normal file
42
core/api/entity/nulltime.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Source: https://github.com/lib/pq/blob/b269bd035a727d6c1081f76e7a239a1b00674c40/encode.go#L521
|
||||
//
|
||||
// Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// Package entity provides types that mirror database tables.
|
||||
package entity
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NullTime represents a time.Time that may be null. NullTime implements the
|
||||
// sql.Scanner interface so it can be used as a scan destination, similar to
|
||||
// sql.NullString.
|
||||
type NullTime struct {
|
||||
Time time.Time
|
||||
Valid bool // Valid is true if Time is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (nt *NullTime) Scan(value interface{}) error {
|
||||
nt.Time, nt.Valid = value.(time.Time)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nt NullTime) Value() (driver.Value, error) {
|
||||
if !nt.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return nt.Time, nil
|
||||
}
|
|
@ -89,6 +89,7 @@ type Account struct {
|
|||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Domain string `json:"domain"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
||||
// Label defines a container for documents.
|
||||
|
@ -437,3 +438,62 @@ type Pin struct {
|
|||
Pin string `json:"pin"`
|
||||
Sequence int `json:"sequence"`
|
||||
}
|
||||
|
||||
// UserActivity represents an activity undertaken by a user.
|
||||
type UserActivity struct {
|
||||
ID uint64 `json:"-"`
|
||||
OrgID string `json:"orgId"`
|
||||
UserID string `json:"userId"`
|
||||
LabelID string `json:"folderId"`
|
||||
SourceID string `json:"sourceId"`
|
||||
SourceName string `json:"sourceName"` // e.g. Document or Space name
|
||||
SourceType ActivitySourceType `json:"sourceType"`
|
||||
ActivityType ActivityType `json:"activityType"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
// ActivitySourceType details where the activity occured.
|
||||
type ActivitySourceType int
|
||||
|
||||
// ActivityType determines type of user activity
|
||||
type ActivityType int
|
||||
|
||||
const (
|
||||
// ActivitySourceTypeSpace indicates activity against a space.
|
||||
ActivitySourceTypeSpace ActivitySourceType = 1
|
||||
|
||||
// ActivitySourceTypeDocument indicates activity against a document.
|
||||
ActivitySourceTypeDocument ActivitySourceType = 2
|
||||
)
|
||||
|
||||
const (
|
||||
// ActivityTypeCreated records user document creation
|
||||
ActivityTypeCreated ActivityType = 1
|
||||
|
||||
// ActivityTypeRead states user has read document
|
||||
ActivityTypeRead ActivityType = 2
|
||||
|
||||
// ActivityTypeEdited states user has editing document
|
||||
ActivityTypeEdited ActivityType = 3
|
||||
|
||||
// ActivityTypeDeleted records user deleting space/document
|
||||
ActivityTypeDeleted ActivityType = 4
|
||||
|
||||
// ActivityTypeArchived records user archiving space/document
|
||||
ActivityTypeArchived ActivityType = 5
|
||||
|
||||
// ActivityTypeApproved records user approval of document
|
||||
ActivityTypeApproved ActivityType = 6
|
||||
|
||||
// ActivityTypeReverted records user content roll-back to previous version
|
||||
ActivityTypeReverted ActivityType = 7
|
||||
|
||||
// ActivityTypePublishedTemplate records user creating new document template
|
||||
ActivityTypePublishedTemplate ActivityType = 8
|
||||
|
||||
// ActivityTypePublishedBlock records user creating reusable content block
|
||||
ActivityTypePublishedBlock ActivityType = 9
|
||||
|
||||
// ActivityTypeFeedback records user providing document feedback
|
||||
ActivityTypeFeedback ActivityType = 10
|
||||
)
|
||||
|
|
|
@ -14,10 +14,11 @@ package request
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/documize/community/core/api/entity"
|
||||
"github.com/documize/community/core/log"
|
||||
"github.com/documize/community/core/utility"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AddAccount inserts the given record into the datbase account table.
|
||||
|
@ -25,7 +26,7 @@ func (p *Persister) AddAccount(account entity.Account) (err error) {
|
|||
account.Created = time.Now().UTC()
|
||||
account.Revised = time.Now().UTC()
|
||||
|
||||
stmt, err := p.Context.Transaction.Preparex("INSERT INTO account (refid, orgid, userid, admin, editor, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?)")
|
||||
stmt, err := p.Context.Transaction.Preparex("INSERT INTO account (refid, orgid, userid, admin, editor, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -33,7 +34,7 @@ func (p *Persister) AddAccount(account entity.Account) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(account.RefID, account.OrgID, account.UserID, account.Admin, account.Editor, account.Created, account.Revised)
|
||||
_, err = stmt.Exec(account.RefID, account.OrgID, account.UserID, account.Admin, account.Editor, account.Active, account.Created, account.Revised)
|
||||
|
||||
if err != nil {
|
||||
log.Error("Unable to execute insert for account", err)
|
||||
|
@ -43,11 +44,9 @@ func (p *Persister) AddAccount(account entity.Account) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// GetUserAccount returns the databse account record corresponding to the given userID, using the client's current organizaion.
|
||||
// GetUserAccount returns the database account record corresponding to the given userID, using the client's current organizaion.
|
||||
func (p *Persister) GetUserAccount(userID string) (account entity.Account, err error) {
|
||||
err = nil
|
||||
|
||||
stmt, err := Db.Preparex("SELECT a.*, b.company, b.title, b.message, b.domain FROM account a, organization b WHERE b.refid=a.orgid and a.orgid=? and a.userid=? AND b.active=1")
|
||||
stmt, err := Db.Preparex("SELECT a.*, 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 utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -67,10 +66,7 @@ func (p *Persister) GetUserAccount(userID string) (account entity.Account, err e
|
|||
|
||||
// GetUserAccounts returns a slice of database account records, for all organizations that the userID is a member of, in organization title order.
|
||||
func (p *Persister) GetUserAccounts(userID string) (t []entity.Account, err error) {
|
||||
|
||||
err = nil
|
||||
|
||||
err = Db.Select(&t, "SELECT a.*, b.company, b.title, b.message, b.domain FROM account a, organization b WHERE a.userid=? AND a.orgid=b.refid AND b.active=1 ORDER BY b.title", userID)
|
||||
err = Db.Select(&t, "SELECT a.*, b.company, b.title, b.message, b.domain FROM account a, organization b WHERE a.userid=? AND a.orgid=b.refid AND a.active=1 ORDER BY b.title", userID)
|
||||
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute select account for user %s", userID), err)
|
||||
|
@ -81,10 +77,7 @@ func (p *Persister) GetUserAccounts(userID string) (t []entity.Account, err erro
|
|||
|
||||
// GetAccountsByOrg returns a slice of database account records, for all users in the client's organization.
|
||||
func (p *Persister) GetAccountsByOrg() (t []entity.Account, err error) {
|
||||
|
||||
err = nil
|
||||
|
||||
err = Db.Select(&t, "SELECT a.*, b.company, b.title, b.message, b.domain FROM account a, organization b WHERE a.orgid=b.refid AND a.orgid=? AND b.active=1", p.Context.OrgID)
|
||||
err = Db.Select(&t, "SELECT a.*, 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", p.Context.OrgID)
|
||||
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute select account for org %s", p.Context.OrgID), err)
|
||||
|
@ -95,12 +88,9 @@ func (p *Persister) GetAccountsByOrg() (t []entity.Account, err error) {
|
|||
|
||||
// UpdateAccount updates the database record for the given account to the given values.
|
||||
func (p *Persister) UpdateAccount(account entity.Account) (err error) {
|
||||
|
||||
err = nil
|
||||
|
||||
account.Revised = time.Now().UTC()
|
||||
|
||||
stmt, err := p.Context.Transaction.PrepareNamed("UPDATE account SET userid=:userid, admin=:admin, editor=:editor, revised=:revised WHERE orgid=:orgid AND refid=:refid")
|
||||
stmt, err := p.Context.Transaction.PrepareNamed("UPDATE account SET userid=:userid, admin=:admin, editor=:editor, active=:active, revised=:revised WHERE orgid=:orgid AND refid=:refid")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -120,7 +110,6 @@ func (p *Persister) UpdateAccount(account entity.Account) (err error) {
|
|||
|
||||
// HasOrgAccount returns if the given orgID has valid userID.
|
||||
func (p *Persister) HasOrgAccount(orgID, userID string) bool {
|
||||
|
||||
row := Db.QueryRow("SELECT count(*) FROM account WHERE orgid=? and userid=?", orgID, userID)
|
||||
|
||||
var count int
|
||||
|
|
44
core/api/request/activity.go
Normal file
44
core/api/request/activity.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
// 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 request
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/documize/community/core/api/entity"
|
||||
"github.com/documize/community/core/log"
|
||||
"github.com/documize/community/core/utility"
|
||||
)
|
||||
|
||||
// RecordUserActivity logs user initiated data changes.
|
||||
func (p *Persister) RecordUserActivity(activity entity.UserActivity) (err error) {
|
||||
activity.OrgID = p.Context.OrgID
|
||||
activity.UserID = p.Context.UserID
|
||||
activity.Created = time.Now().UTC()
|
||||
|
||||
stmt, err := p.Context.Transaction.Preparex("INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created) VALUES (?, ?, ?, ?, ?, ?, ?)")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
log.Error("Unable to prepare insert RecordUserActivity", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(activity.OrgID, activity.UserID, activity.LabelID, activity.SourceID, activity.SourceType, activity.ActivityType, activity.Created)
|
||||
|
||||
if err != nil {
|
||||
log.Error("Unable to execute insert RecordUserActivity", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -49,9 +49,6 @@ func (p *Persister) AddAttachment(a entity.Attachment) (err error) {
|
|||
|
||||
// GetAttachment returns the database attachment record specified by the parameters.
|
||||
func (p *Persister) GetAttachment(orgID, attachmentID string) (attachment entity.Attachment, err error) {
|
||||
|
||||
err = nil
|
||||
|
||||
stmt, err := Db.Preparex("SELECT id, refid, orgid, documentid, job, fileid, filename, data, extension, created, revised FROM attachment WHERE orgid=? and refid=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
|
@ -72,9 +69,6 @@ func (p *Persister) GetAttachment(orgID, attachmentID string) (attachment entity
|
|||
|
||||
// GetAttachments returns a slice containing the attachement records (excluding their data) for document docID, ordered by filename.
|
||||
func (p *Persister) GetAttachments(docID string) (attachments []entity.Attachment, err error) {
|
||||
|
||||
err = nil
|
||||
|
||||
err = Db.Select(&attachments, "SELECT id, refid, orgid, documentid, job, fileid, filename, extension, created, revised FROM attachment WHERE orgid=? and documentid=? order by filename", p.Context.OrgID, docID)
|
||||
|
||||
if err != nil {
|
||||
|
@ -87,9 +81,6 @@ func (p *Persister) GetAttachments(docID string) (attachments []entity.Attachmen
|
|||
|
||||
// GetAttachmentsWithData returns a slice containing the attachement records (including their data) for document docID, ordered by filename.
|
||||
func (p *Persister) GetAttachmentsWithData(docID string) (attachments []entity.Attachment, err error) {
|
||||
|
||||
err = nil
|
||||
|
||||
err = Db.Select(&attachments, "SELECT id, refid, orgid, documentid, job, fileid, filename, extension, data, created, revised FROM attachment WHERE orgid=? and documentid=? order by filename", p.Context.OrgID, docID)
|
||||
|
||||
if err != nil {
|
||||
|
@ -102,7 +93,7 @@ func (p *Persister) GetAttachmentsWithData(docID string) (attachments []entity.A
|
|||
|
||||
// DeleteAttachment deletes the id record from the database attachment table.
|
||||
func (p *Persister) DeleteAttachment(id string) (rows int64, err error) {
|
||||
rows, err = p.Base.DeleteConstrained(p.Context.Transaction, "attachment", p.Context.OrgID, id)
|
||||
rows, _ = p.Base.DeleteConstrained(p.Context.Transaction, "attachment", p.Context.OrgID, id)
|
||||
|
||||
// Mark references to this document as orphaned
|
||||
err = p.MarkOrphanAttachmentLink(id)
|
||||
|
|
|
@ -47,15 +47,11 @@ func (p *Persister) AddDocument(document entity.Document) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "add-document", document.RefID, "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetDocument fetches the document record with the given id fromt the document table and audits that it has been got.
|
||||
func (p *Persister) GetDocument(id string) (document entity.Document, err error) {
|
||||
err = nil
|
||||
|
||||
stmt, err := 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 utility.Close(stmt)
|
||||
|
||||
|
@ -71,15 +67,11 @@ func (p *Persister) GetDocument(id string) (document entity.Document, err error)
|
|||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, AuditGetDocument, id, "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetDocumentMeta returns the metadata for a specified document.
|
||||
func (p *Persister) GetDocumentMeta(id string) (meta entity.DocumentMeta, err error) {
|
||||
err = nil
|
||||
|
||||
// sqlViewers := `SELECT CONVERT_TZ(MAX(a.created), @@session.time_zone, '+00:00') as created,
|
||||
|
||||
sqlViewers := `SELECT MAX(a.created) as created,
|
||||
|
@ -131,7 +123,6 @@ func (p *Persister) GetDocuments() (documents []entity.Document, err error) {
|
|||
|
||||
// GetDocumentsByFolder returns a slice containing the documents for a given folder, most recient first.
|
||||
func (p *Persister) GetDocumentsByFolder(folderID string) (documents []entity.Document, err error) {
|
||||
err = nil
|
||||
err = 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", p.Context.OrgID, folderID)
|
||||
|
||||
if err != nil {
|
||||
|
@ -306,8 +297,6 @@ func (p *Persister) SearchDocument(keywords string) (results []entity.DocumentSe
|
|||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "search", "", "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -345,8 +334,6 @@ func (p *Persister) UpdateDocument(document entity.Document) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "update-document", document.RefID, "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -377,8 +364,6 @@ func (p *Persister) ChangeDocumentLabel(document, label string) (err error) {
|
|||
return re
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "update-document-label", document, "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -400,8 +385,6 @@ func (p *Persister) MoveDocumentLabel(id, move string) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "move-document-label", "", "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -444,7 +427,5 @@ func (p *Persister) DeleteDocument(documentID string) (rows int64, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "delete-document", documentID, "")
|
||||
|
||||
return p.Base.DeleteConstrained(p.Context.Transaction, "document", p.Context.OrgID, documentID)
|
||||
}
|
||||
|
|
|
@ -128,9 +128,6 @@ type baseManager struct {
|
|||
}
|
||||
|
||||
func (m *baseManager) Delete(tx *sqlx.Tx, table string, id string) (rows int64, err error) {
|
||||
|
||||
err = nil
|
||||
|
||||
stmt, err := tx.Preparex("DELETE FROM " + table + " WHERE refid=?")
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to prepare delete of row in table %s", table), err)
|
||||
|
@ -203,16 +200,6 @@ func (m *baseManager) DeleteWhere(tx *sqlx.Tx, statement string) (rows int64, er
|
|||
return
|
||||
}
|
||||
|
||||
// Audit inserts a record into the audit table.
|
||||
func (m *baseManager) Audit(c Context, action, document, page string) {
|
||||
|
||||
_, err := Db.Exec("INSERT INTO audit (orgid, userid, documentid, pageid, action, created) VALUES (?, ?, ?, ?, ?, ?)", c.OrgID, c.UserID, document, page, action, time.Now().UTC())
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable record audit for action %s, user %s, customer %s", action, c.UserID, c.OrgID), err)
|
||||
}
|
||||
}
|
||||
|
||||
// SQLPrepareError returns a string detailing the location of the error.
|
||||
func (m *baseManager) SQLPrepareError(method string, id string) string {
|
||||
return fmt.Sprintf("Unable to prepare SQL for %s, ID %s", method, id)
|
||||
|
@ -222,8 +209,3 @@ func (m *baseManager) SQLPrepareError(method string, id string) string {
|
|||
func (m *baseManager) SQLSelectError(method string, id string) string {
|
||||
return fmt.Sprintf("Unable to execute SQL for %s, ID %s", method, id)
|
||||
}
|
||||
|
||||
const (
|
||||
// AuditGetDocument means someone viewed a document
|
||||
AuditGetDocument string = "get-document"
|
||||
)
|
||||
|
|
|
@ -46,9 +46,6 @@ func (p *Persister) AddLabel(l entity.Label) (err error) {
|
|||
|
||||
// GetLabel returns a folder from the store.
|
||||
func (p *Persister) GetLabel(id string) (label entity.Label, err error) {
|
||||
|
||||
err = nil
|
||||
|
||||
stmt, err := Db.Preparex("SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label WHERE orgid=? and refid=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
|
@ -69,8 +66,6 @@ func (p *Persister) GetLabel(id string) (label entity.Label, err error) {
|
|||
|
||||
// GetPublicFolders returns folders that anyone can see.
|
||||
func (p *Persister) GetPublicFolders(orgID string) (labels []entity.Label, err error) {
|
||||
err = nil
|
||||
|
||||
sql := "SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label a where orgid=? AND type=1"
|
||||
|
||||
err = Db.Select(&labels, sql, orgID)
|
||||
|
@ -86,8 +81,6 @@ func (p *Persister) GetPublicFolders(orgID string) (labels []entity.Label, err e
|
|||
// GetLabels returns folders that the user can see.
|
||||
// Also handles which folders can be seen by anonymous users.
|
||||
func (p *Persister) GetLabels() (labels []entity.Label, err error) {
|
||||
err = nil
|
||||
|
||||
sql := `
|
||||
(SELECT id,refid,label as name,orgid,userid,type,created,revised from label WHERE orgid=? AND type=2 AND userid=?)
|
||||
UNION ALL
|
||||
|
@ -117,7 +110,6 @@ ORDER BY name`
|
|||
|
||||
// UpdateLabel saves folder changes.
|
||||
func (p *Persister) UpdateLabel(label entity.Label) (err error) {
|
||||
err = nil
|
||||
label.Revised = time.Now().UTC()
|
||||
|
||||
stmt, err := p.Context.Transaction.PrepareNamed("UPDATE label SET label=:name, type=:type, userid=:userid, revised=:revised WHERE orgid=:orgid AND refid=:refid")
|
||||
|
@ -140,8 +132,6 @@ func (p *Persister) UpdateLabel(label entity.Label) (err error) {
|
|||
|
||||
// ChangeLabelOwner transfer folder ownership.
|
||||
func (p *Persister) ChangeLabelOwner(currentOwner, newOwner string) (err error) {
|
||||
err = nil
|
||||
|
||||
stmt, err := p.Context.Transaction.Preparex("UPDATE label SET userid=? WHERE userid=? AND orgid=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
|
@ -162,8 +152,6 @@ func (p *Persister) ChangeLabelOwner(currentOwner, newOwner string) (err error)
|
|||
|
||||
// GetFolderVisibility returns the list of people who can see shared folders.
|
||||
func (p *Persister) GetFolderVisibility() (visibleTo []entity.FolderVisibility, err error) {
|
||||
err = nil
|
||||
|
||||
sql := `
|
||||
SELECT a.userid,
|
||||
COALESCE(u.firstname, '') as firstname,
|
||||
|
|
|
@ -46,9 +46,6 @@ func (p *Persister) AddLabelRole(l entity.LabelRole) (err error) {
|
|||
|
||||
// GetLabelRoles returns a slice of labelrole records, for the given labelID in the client's organization, grouped by user.
|
||||
func (p *Persister) GetLabelRoles(labelID string) (roles []entity.LabelRole, err error) {
|
||||
|
||||
err = nil
|
||||
|
||||
query := `SELECT id, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? AND labelid=?` // was + "GROUP BY userid"
|
||||
|
||||
err = Db.Select(&roles, query, p.Context.OrgID, labelID)
|
||||
|
|
|
@ -48,9 +48,6 @@ func (p *Persister) AddContentLink(l entity.Link) (err error) {
|
|||
// SearchLinkCandidates returns matching documents, sections and attachments using keywords.
|
||||
func (p *Persister) SearchLinkCandidates(keywords string) (docs []entity.LinkCandidate,
|
||||
pages []entity.LinkCandidate, attachments []entity.LinkCandidate, err error) {
|
||||
|
||||
err = nil
|
||||
|
||||
// find matching documents
|
||||
temp := []entity.LinkCandidate{}
|
||||
likeQuery := "title LIKE '%" + keywords + "%'"
|
||||
|
@ -182,8 +179,6 @@ func (p *Persister) SearchLinkCandidates(keywords string) (docs []entity.LinkCan
|
|||
|
||||
// GetDocumentOutboundLinks returns outbound links for specified document.
|
||||
func (p *Persister) GetDocumentOutboundLinks(documentID string) (links []entity.Link, err error) {
|
||||
err = nil
|
||||
|
||||
err = Db.Select(&links,
|
||||
`select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.linktype, l.orphan, l.created, l.revised
|
||||
FROM link l
|
||||
|
@ -204,8 +199,6 @@ func (p *Persister) GetDocumentOutboundLinks(documentID string) (links []entity.
|
|||
|
||||
// GetPageLinks returns outbound links for specified page in document.
|
||||
func (p *Persister) GetPageLinks(documentID, pageID string) (links []entity.Link, err error) {
|
||||
err = nil
|
||||
|
||||
err = Db.Select(&links,
|
||||
`select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.linktype, l.orphan, l.created, l.revised
|
||||
FROM link l
|
||||
|
|
|
@ -59,8 +59,6 @@ func (p *Persister) AddOrganization(org entity.Organization) error {
|
|||
|
||||
// GetOrganization returns the Organization reocrod from the organization database table with the given id.
|
||||
func (p *Persister) GetOrganization(id string) (org entity.Organization, err error) {
|
||||
err = nil
|
||||
|
||||
stmt, err := Db.Preparex("SELECT id, refid, company, title, message, url, domain, email, serial, active, allowanonymousaccess, created, revised FROM organization WHERE refid=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
|
@ -128,7 +126,6 @@ func (p *Persister) GetOrganizationByDomain(subdomain string) (org entity.Organi
|
|||
|
||||
// UpdateOrganization updates the given organization record in the database to the values supplied.
|
||||
func (p *Persister) UpdateOrganization(org entity.Organization) (err error) {
|
||||
err = nil
|
||||
org.Revised = time.Now().UTC()
|
||||
|
||||
stmt, err := p.Context.Transaction.PrepareNamed("UPDATE organization SET title=:title, message=:message, email=:email, allowanonymousaccess=:allowanonymousaccess, revised=:revised WHERE refid=:refid")
|
||||
|
@ -164,8 +161,6 @@ func (p *Persister) DeleteOrganization(orgID string) (rows int64, err error) {
|
|||
|
||||
// RemoveOrganization sets the orgID organization to be inactive, thus executing a "soft delete" operation.
|
||||
func (p *Persister) RemoveOrganization(orgID string) (err error) {
|
||||
err = nil
|
||||
|
||||
stmt, err := p.Context.Transaction.Preparex("UPDATE organization SET active=0 WHERE refid=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
|
|
|
@ -82,8 +82,6 @@ func (p *Persister) AddPage(model models.PageModel) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "add-page", model.Page.DocumentID, model.Page.RefID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -289,8 +287,6 @@ func (p *Persister) UpdatePage(page entity.Page, refID, userID string, skipRevis
|
|||
}
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "update-page", page.DocumentID, page.RefID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -341,8 +337,6 @@ func (p *Persister) UpdatePageSequence(documentID, pageID string, sequence float
|
|||
|
||||
err = searches.UpdateSequence(&databaseRequest{OrgID: p.Context.OrgID}, documentID, pageID, sequence)
|
||||
|
||||
p.Base.Audit(p.Context, "re-sequence-page", "", pageID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -366,8 +360,6 @@ func (p *Persister) UpdatePageLevel(documentID, pageID string, level int) (err e
|
|||
|
||||
err = searches.UpdateLevel(&databaseRequest{OrgID: p.Context.OrgID}, documentID, pageID, level)
|
||||
|
||||
p.Base.Audit(p.Context, "re-level-page", "", pageID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -388,8 +380,6 @@ func (p *Persister) DeletePage(documentID, pageID string) (rows int64, err error
|
|||
|
||||
// nuke revisions
|
||||
_, _ = p.DeletePageRevisions(pageID)
|
||||
|
||||
p.Base.Audit(p.Context, "remove-page", documentID, pageID)
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -470,8 +460,6 @@ func (p *Persister) GetDocumentRevisions(documentID string) (revisions []entity.
|
|||
revisions = []entity.Revision{}
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "get-document-revisions", documentID, "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -485,8 +473,6 @@ func (p *Persister) GetPageRevisions(pageID string) (revisions []entity.Revision
|
|||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "get-page-revisions", "", pageID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import (
|
|||
|
||||
// AddPin saves pinned item.
|
||||
func (p *Persister) AddPin(pin entity.Pin) (err error) {
|
||||
|
||||
row := Db.QueryRow("SELECT max(sequence) FROM pin WHERE orgid=? AND userid=?", p.Context.OrgID, p.Context.UserID)
|
||||
var maxSeq int
|
||||
err = row.Scan(&maxSeq)
|
||||
|
|
|
@ -28,7 +28,7 @@ func (p *Persister) AddUser(user entity.User) (err error) {
|
|||
user.Created = time.Now().UTC()
|
||||
user.Revised = time.Now().UTC()
|
||||
|
||||
stmt, err := p.Context.Transaction.Preparex("INSERT INTO user (refid, firstname, lastname, email, initials, password, salt, reset, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
stmt, err := p.Context.Transaction.Preparex("INSERT INTO user (refid, firstname, lastname, email, initials, password, salt, reset, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -36,7 +36,7 @@ func (p *Persister) AddUser(user entity.User) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
res, err := stmt.Exec(user.RefID, user.Firstname, user.Lastname, strings.ToLower(user.Email), user.Initials, user.Password, user.Salt, "", true, user.Created, user.Revised)
|
||||
res, err := stmt.Exec(user.RefID, user.Firstname, user.Lastname, strings.ToLower(user.Email), user.Initials, user.Password, user.Salt, "", user.Created, user.Revised)
|
||||
if err != nil {
|
||||
log.Error("Unable insert for user", err)
|
||||
return
|
||||
|
@ -48,14 +48,12 @@ func (p *Persister) AddUser(user entity.User) (err error) {
|
|||
return er
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "add-user", "", "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetUser returns the user record for the given id.
|
||||
func (p *Persister) GetUser(id string) (user entity.User, err error) {
|
||||
stmt, err := Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, active, created, revised FROM user WHERE refid=?")
|
||||
stmt, err := Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE refid=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -77,7 +75,7 @@ func (p *Persister) GetUser(id string) (user entity.User, err error) {
|
|||
func (p *Persister) GetUserByEmail(email string) (user entity.User, err error) {
|
||||
email = strings.TrimSpace(strings.ToLower(email))
|
||||
|
||||
stmt, err := Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, active, created, revised FROM user WHERE TRIM(LOWER(email))=?")
|
||||
stmt, err := Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE TRIM(LOWER(email))=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -99,7 +97,7 @@ func (p *Persister) GetUserByEmail(email string) (user entity.User, err error) {
|
|||
func (p *Persister) GetUserByDomain(domain, email string) (user entity.User, err error) {
|
||||
email = strings.TrimSpace(strings.ToLower(email))
|
||||
|
||||
stmt, err := Db.Preparex("SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.global, u.password, u.salt, u.reset, u.active, 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))=?")
|
||||
stmt, err := 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 utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -119,7 +117,7 @@ func (p *Persister) GetUserByDomain(domain, email string) (user entity.User, err
|
|||
|
||||
// GetUserByToken returns a user record given a reset token value.
|
||||
func (p *Persister) GetUserByToken(token string) (user entity.User, err error) {
|
||||
stmt, err := Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, active, created, revised FROM user WHERE reset=?")
|
||||
stmt, err := Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE reset=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -141,7 +139,7 @@ func (p *Persister) GetUserByToken(token string) (user entity.User, err error) {
|
|||
// This occurs when we you share a folder with a new user and they have to complete
|
||||
// the onboarding process.
|
||||
func (p *Persister) GetUserBySerial(serial string) (user entity.User, err error) {
|
||||
stmt, err := Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, active, created, revised FROM user WHERE salt=?")
|
||||
stmt, err := Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE salt=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -161,7 +159,7 @@ func (p *Persister) GetUserBySerial(serial string) (user entity.User, err error)
|
|||
// identified in the Persister.
|
||||
func (p *Persister) GetUsersForOrganization() (users []entity.User, err error) {
|
||||
err = Db.Select(&users,
|
||||
"SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, active, created, revised FROM user WHERE refid IN (SELECT userid FROM account where orgid = ?) ORDER BY firstname,lastname", p.Context.OrgID)
|
||||
"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", p.Context.OrgID)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to get all users for org %s", p.Context.OrgID), err)
|
||||
|
@ -174,7 +172,7 @@ func (p *Persister) GetUsersForOrganization() (users []entity.User, err error) {
|
|||
// GetFolderUsers returns a slice containing all user records for given folder.
|
||||
func (p *Persister) GetFolderUsers(folderID string) (users []entity.User, err error) {
|
||||
err = Db.Select(&users,
|
||||
"SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, active, created, revised FROM user WHERE refid IN (SELECT userid from labelrole WHERE orgid=? AND labelid=?) ORDER BY firstname,lastname", p.Context.OrgID, folderID)
|
||||
"SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, created, revised FROM user WHERE refid IN (SELECT userid from labelrole WHERE orgid=? AND labelid=?) ORDER BY firstname,lastname", p.Context.OrgID, folderID)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to get all users for org %s", p.Context.OrgID), err)
|
||||
|
@ -190,7 +188,7 @@ func (p *Persister) UpdateUser(user entity.User) (err error) {
|
|||
user.Email = strings.ToLower(user.Email)
|
||||
|
||||
stmt, err := p.Context.Transaction.PrepareNamed(
|
||||
"UPDATE user SET firstname=:firstname, lastname=:lastname, email=:email, revised=:revised, active=:active, initials=:initials WHERE refid=:refid")
|
||||
"UPDATE user SET firstname=:firstname, lastname=:lastname, email=:email, revised=:revised, initials=:initials WHERE refid=:refid")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
|
@ -205,8 +203,6 @@ func (p *Persister) UpdateUser(user entity.User) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "update-user", "", "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -233,8 +229,6 @@ func (p *Persister) UpdateUserPassword(userID, salt, password string) (err error
|
|||
return er
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "change-password", "", "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -255,8 +249,6 @@ func (p *Persister) DeactiveUser(userID string) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "deactivate-user", "", "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -285,7 +277,5 @@ func (p *Persister) ForgotUserPassword(email, token string) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "forgot-password", "", "")
|
||||
|
||||
return
|
||||
}
|
||||
|
|
88
core/database/scripts/autobuild/db_00010.sql
Normal file
88
core/database/scripts/autobuild/db_00010.sql
Normal file
|
@ -0,0 +1,88 @@
|
|||
/* community edition */
|
||||
DROP TABLE IF EXISTS `useractivity`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `useractivity` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`userid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`labelid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`sourceid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`sourcetype` INT NOT NULL DEFAULT 0,
|
||||
`activitytype` INT NOT NULL DEFAULT 0,
|
||||
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT pk_id PRIMARY KEY (id),
|
||||
INDEX `idx_activity_orgid` (`orgid` ASC),
|
||||
INDEX `idx_activity_userid` (`userid` ASC),
|
||||
INDEX `idx_activity_sourceid` (`sourceid` ASC),
|
||||
INDEX `idx_activity_activitytype` (`activitytype` ASC))
|
||||
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
||||
ENGINE = InnoDB;
|
||||
/* Note:
|
||||
* - this table replaces the soon-to-be-deprecated audit log table
|
||||
* - we migrate existing data where there is a migration path */
|
||||
|
||||
INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created)
|
||||
SELECT a.orgid, a.userid, labelid, a.documentid as sourceid, 2 as sourcetype, 1 as activitytype, a.created
|
||||
FROM audit a, document d
|
||||
WHERE action='add-document' AND d.refid=a.documentid;
|
||||
|
||||
INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created)
|
||||
SELECT a.orgid, a.userid, labelid, a.documentid as sourceid, 2 as sourcetype, 1 as activitytype, a.created
|
||||
FROM audit a, document d
|
||||
WHERE action='add-page' AND d.refid=a.documentid;
|
||||
|
||||
INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created)
|
||||
SELECT a.orgid, a.userid, labelid, a.documentid as sourceid, 2 as sourcetype, 2 as activitytype, a.created
|
||||
FROM audit a, document d
|
||||
WHERE action='get-document' AND d.refid=a.documentid;
|
||||
|
||||
INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created)
|
||||
SELECT a.orgid, a.userid, labelid, a.documentid as sourceid, 2 as sourcetype, 3 as activitytype, a.created
|
||||
FROM audit a, document d
|
||||
WHERE action='update-page' AND d.refid=a.documentid;
|
||||
|
||||
INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created)
|
||||
SELECT a.orgid, a.userid, labelid, a.documentid as sourceid, 2 as sourcetype, 3 as activitytype, a.created
|
||||
FROM audit a, document d
|
||||
WHERE action='re-sequence-page' AND d.refid=a.documentid;
|
||||
|
||||
INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created)
|
||||
SELECT a.orgid, a.userid, labelid, a.documentid as sourceid, 2 as sourcetype, 3 as activitytype, a.created
|
||||
FROM audit a, document d
|
||||
WHERE action='re-level-page' AND d.refid=a.documentid;
|
||||
|
||||
INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created)
|
||||
SELECT a.orgid, a.userid, labelid, a.documentid as sourceid, 2 as sourcetype, 4 as activitytype, a.created
|
||||
FROM audit a, document d
|
||||
WHERE action='delete-document' AND d.refid=a.documentid;
|
||||
|
||||
INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created)
|
||||
SELECT a.orgid, a.userid, labelid, a.documentid as sourceid, 2 as sourcetype, 4 as activitytype, a.created
|
||||
FROM audit a, document d
|
||||
WHERE action='remove-page' AND d.refid=a.documentid;
|
||||
|
||||
/* enterprise edition */
|
||||
DROP TABLE IF EXISTS `useraction`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `useraction` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`refid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`orgid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`userid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`documentid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`requestorid` CHAR(16) NOT NULL COLLATE utf8_bin,
|
||||
`actiontype` INT NOT NULL DEFAULT 0,
|
||||
`note` NVARCHAR(2000) NOT NULL DEFAULT '',
|
||||
`requested` TIMESTAMP NULL,
|
||||
`due` TIMESTAMP NULL,
|
||||
`completed` TIMESTAMP NULL,
|
||||
`iscomplete` BOOL NOT NULL DEFAULT 0,
|
||||
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT pk_id PRIMARY KEY (id),
|
||||
INDEX `idx_useraction_refid` (`refid` ASC),
|
||||
INDEX `idx_useraction_userid` (`userid` ASC),
|
||||
INDEX `idx_useraction_documentid` (`documentid` ASC),
|
||||
INDEX `idx_useraction_requestorid` (`requestorid` ASC))
|
||||
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
||||
ENGINE = InnoDB;
|
|
@ -26,7 +26,7 @@ type ProdInfo struct {
|
|||
// Product returns product edition details
|
||||
func Product() (p ProdInfo) {
|
||||
p.Major = "0"
|
||||
p.Minor = "41"
|
||||
p.Minor = "42"
|
||||
p.Patch = "0"
|
||||
p.Version = fmt.Sprintf("%s.%s.%s", p.Major, p.Minor, p.Patch)
|
||||
p.Edition = "Community"
|
||||
|
|
38
core/utility/strings.go
Normal file
38
core/utility/strings.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
// 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 utility
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Conjoin returns "Suzzane, Fatima and Brian" from string of items.
|
||||
func Conjoin(conj string, items []string) string {
|
||||
if len(items) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(items) == 1 {
|
||||
return items[0]
|
||||
}
|
||||
if len(items) == 2 { // "a and b" not "a, and b"
|
||||
return items[0] + " " + conj + " " + items[1]
|
||||
}
|
||||
|
||||
sep := ", "
|
||||
pieces := []string{items[0]}
|
||||
for _, item := range items[1 : len(items)-1] {
|
||||
pieces = append(pieces, sep, item)
|
||||
}
|
||||
pieces = append(pieces, sep, conj, " ", items[len(items)-1])
|
||||
|
||||
return strings.Replace(strings.Join(pieces, ""), ", and ", " and ", 1)
|
||||
}
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue