mirror of
https://github.com/documize/community.git
synced 2025-07-24 15:49:44 +02:00
Some DB commit commands were out of sequence and so have been fixed to be consist across the board. Specially, audit log entries have their own DB TX and so should be executed outside of any other commit cycle.
544 lines
13 KiB
Go
544 lines
13 KiB
Go
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
//
|
|
// This software (Documize Community Edition) is licensed under
|
|
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
//
|
|
// You can operate outside the AGPL restrictions by purchasing
|
|
// Documize Enterprise Edition and obtaining a commercial license
|
|
// by contacting <sales@documize.com>.
|
|
//
|
|
// https://documize.com
|
|
|
|
// Package category handles API calls and persistence for categories.
|
|
// Categories sub-divide spaces.
|
|
package category
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"net/http"
|
|
|
|
"github.com/documize/community/core/env"
|
|
"github.com/documize/community/core/request"
|
|
"github.com/documize/community/core/response"
|
|
"github.com/documize/community/core/uniqueid"
|
|
"github.com/documize/community/domain"
|
|
"github.com/documize/community/domain/permission"
|
|
"github.com/documize/community/model/audit"
|
|
"github.com/documize/community/model/category"
|
|
pm "github.com/documize/community/model/permission"
|
|
)
|
|
|
|
// Handler contains the runtime information such as logging and database.
|
|
type Handler struct {
|
|
Runtime *env.Runtime
|
|
Store *domain.Store
|
|
}
|
|
|
|
// Add saves space category.
|
|
func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
|
method := "category.add"
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
if !ctx.Authenticated {
|
|
response.WriteForbiddenError(w)
|
|
return
|
|
}
|
|
|
|
defer r.Body.Close()
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
response.WriteBadRequestError(w, method, "body")
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
var cat category.Category
|
|
err = json.Unmarshal(body, &cat)
|
|
if err != nil {
|
|
response.WriteBadRequestError(w, method, "category")
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
cat.RefID = uniqueid.Generate()
|
|
cat.OrgID = ctx.OrgID
|
|
|
|
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
|
if err != nil {
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
err = h.Store.Category.Add(ctx, cat)
|
|
if err != nil {
|
|
ctx.Transaction.Rollback()
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
perm := pm.Permission{}
|
|
perm.OrgID = ctx.OrgID
|
|
perm.Who = "user"
|
|
perm.WhoID = ctx.UserID
|
|
perm.Scope = "object"
|
|
perm.Location = "category"
|
|
perm.RefID = cat.RefID
|
|
perm.Action = pm.CategoryView
|
|
|
|
err = h.Store.Permission.AddPermission(ctx, perm)
|
|
if err != nil {
|
|
ctx.Transaction.Rollback()
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
ctx.Transaction.Commit()
|
|
|
|
cat, err = h.Store.Category.Get(ctx, cat.RefID)
|
|
if err != nil {
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
h.Store.Audit.Record(ctx, audit.EventTypeCategoryAdd)
|
|
|
|
response.WriteJSON(w, cat)
|
|
}
|
|
|
|
// Get returns categories visible to user within a space.
|
|
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
|
|
method := "category.get"
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
spaceID := request.Param(r, "spaceID")
|
|
if len(spaceID) == 0 {
|
|
response.WriteMissingDataError(w, method, "spaceID")
|
|
return
|
|
}
|
|
|
|
ok := permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceManage, pm.SpaceOwner, pm.SpaceView)
|
|
if !ok {
|
|
response.WriteForbiddenError(w)
|
|
return
|
|
}
|
|
|
|
cat, err := h.Store.Category.GetBySpace(ctx, spaceID)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
h.Runtime.Log.Error("get space categories visible to user failed", err)
|
|
response.WriteServerError(w, method, err)
|
|
return
|
|
}
|
|
|
|
if len(cat) == 0 {
|
|
cat = []category.Category{}
|
|
}
|
|
|
|
response.WriteJSON(w, cat)
|
|
}
|
|
|
|
// GetAll returns categories within a space, disregarding permissions.
|
|
// Used in admin screens, lists, functions.
|
|
func (h *Handler) GetAll(w http.ResponseWriter, r *http.Request) {
|
|
method := "category.getAll"
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
spaceID := request.Param(r, "spaceID")
|
|
if len(spaceID) == 0 {
|
|
response.WriteMissingDataError(w, method, "spaceID")
|
|
return
|
|
}
|
|
|
|
cat, err := h.Store.Category.GetAllBySpace(ctx, spaceID)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
response.WriteServerError(w, method, err)
|
|
return
|
|
}
|
|
|
|
if len(cat) == 0 {
|
|
cat = []category.Category{}
|
|
}
|
|
|
|
response.WriteJSON(w, cat)
|
|
}
|
|
|
|
// Update saves existing space category.
|
|
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|
method := "category.update"
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
categoryID := request.Param(r, "categoryID")
|
|
if len(categoryID) == 0 {
|
|
response.WriteMissingDataError(w, method, "categoryID")
|
|
return
|
|
}
|
|
|
|
defer r.Body.Close()
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
response.WriteBadRequestError(w, method, "body")
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
var cat category.Category
|
|
err = json.Unmarshal(body, &cat)
|
|
if err != nil {
|
|
response.WriteBadRequestError(w, method, "category")
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
cat.OrgID = ctx.OrgID
|
|
cat.RefID = categoryID
|
|
|
|
ok := permission.HasPermission(ctx, *h.Store, cat.LabelID, pm.SpaceManage, pm.SpaceOwner)
|
|
if !ok || !ctx.Authenticated {
|
|
response.WriteForbiddenError(w)
|
|
return
|
|
}
|
|
|
|
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
|
if err != nil {
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
err = h.Store.Category.Update(ctx, cat)
|
|
if err != nil {
|
|
ctx.Transaction.Rollback()
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
ctx.Transaction.Commit()
|
|
|
|
h.Store.Audit.Record(ctx, audit.EventTypeCategoryUpdate)
|
|
|
|
cat, err = h.Store.Category.Get(ctx, cat.RefID)
|
|
if err != nil {
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
response.WriteJSON(w, cat)
|
|
}
|
|
|
|
// Delete removes category and associated member records.
|
|
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|
method := "category.delete"
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
catID := request.Param(r, "categoryID")
|
|
if len(catID) == 0 {
|
|
response.WriteMissingDataError(w, method, "categoryID")
|
|
return
|
|
}
|
|
|
|
cat, err := h.Store.Category.Get(ctx, catID)
|
|
if err != nil {
|
|
response.WriteServerError(w, method, err)
|
|
return
|
|
}
|
|
|
|
ok := permission.HasPermission(ctx, *h.Store, cat.LabelID, pm.SpaceManage, pm.SpaceOwner)
|
|
if !ok || !ctx.Authenticated {
|
|
response.WriteForbiddenError(w)
|
|
return
|
|
}
|
|
|
|
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
|
if err != nil {
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
// remove category members
|
|
_, err = h.Store.Category.RemoveCategoryMembership(ctx, cat.RefID)
|
|
if err != nil {
|
|
ctx.Transaction.Rollback()
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
// remove category permissions
|
|
_, err = h.Store.Permission.DeleteCategoryPermissions(ctx, cat.RefID)
|
|
if err != nil {
|
|
ctx.Transaction.Rollback()
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
// remove category
|
|
_, err = h.Store.Category.Delete(ctx, cat.RefID)
|
|
if err != nil {
|
|
ctx.Transaction.Rollback()
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
ctx.Transaction.Commit()
|
|
|
|
h.Store.Audit.Record(ctx, audit.EventTypeCategoryDelete)
|
|
|
|
response.WriteEmpty(w)
|
|
}
|
|
|
|
// GetSummary returns number of documents and users for space categories.
|
|
func (h *Handler) GetSummary(w http.ResponseWriter, r *http.Request) {
|
|
method := "category.GetSummary"
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
spaceID := request.Param(r, "spaceID")
|
|
if len(spaceID) == 0 {
|
|
response.WriteMissingDataError(w, method, "spaceID")
|
|
return
|
|
}
|
|
|
|
ok := permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceManage, pm.SpaceOwner, pm.SpaceView)
|
|
if !ok {
|
|
response.WriteForbiddenError(w)
|
|
return
|
|
}
|
|
|
|
s, err := h.Store.Category.GetSpaceCategorySummary(ctx, spaceID)
|
|
if err != nil {
|
|
h.Runtime.Log.Error("get space category summary failed", err)
|
|
response.WriteServerError(w, method, err)
|
|
return
|
|
}
|
|
|
|
if len(s) == 0 {
|
|
s = []category.SummaryModel{}
|
|
}
|
|
|
|
response.WriteJSON(w, s)
|
|
}
|
|
|
|
// SetDocumentCategoryMembership will link/unlink document from categories (query string switch mode=link or mode=unlink).
|
|
func (h *Handler) SetDocumentCategoryMembership(w http.ResponseWriter, r *http.Request) {
|
|
method := "category.addMember"
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
mode := request.Query(r, "mode")
|
|
if len(mode) == 0 {
|
|
response.WriteMissingDataError(w, method, "mode")
|
|
return
|
|
}
|
|
|
|
defer r.Body.Close()
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
response.WriteBadRequestError(w, method, "body")
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
var cats []category.Member
|
|
err = json.Unmarshal(body, &cats)
|
|
if err != nil {
|
|
response.WriteBadRequestError(w, method, "category")
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
if len(cats) == 0 {
|
|
response.WriteEmpty(w)
|
|
return
|
|
}
|
|
|
|
if !permission.HasPermission(ctx, *h.Store, cats[0].LabelID, pm.DocumentAdd, pm.DocumentEdit) {
|
|
response.WriteForbiddenError(w)
|
|
return
|
|
}
|
|
|
|
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
|
if err != nil {
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
|
|
for _, c := range cats {
|
|
if mode == "link" {
|
|
c.OrgID = ctx.OrgID
|
|
c.RefID = uniqueid.Generate()
|
|
_, err = h.Store.Category.DisassociateDocument(ctx, c.CategoryID, c.DocumentID)
|
|
err = h.Store.Category.AssociateDocument(ctx, c)
|
|
} else {
|
|
_, err = h.Store.Category.DisassociateDocument(ctx, c.CategoryID, c.DocumentID)
|
|
}
|
|
|
|
if err != nil {
|
|
ctx.Transaction.Rollback()
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error(method, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx.Transaction.Commit()
|
|
|
|
h.Store.Audit.Record(ctx, audit.EventTypeCategoryLink)
|
|
|
|
response.WriteEmpty(w)
|
|
}
|
|
|
|
// GetDocumentCategoryMembership returns user viewable categories associated with a given document.
|
|
func (h *Handler) GetDocumentCategoryMembership(w http.ResponseWriter, r *http.Request) {
|
|
method := "category.GetDocumentCategoryMembership"
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
documentID := request.Param(r, "documentID")
|
|
if len(documentID) == 0 {
|
|
response.WriteMissingDataError(w, method, "documentID")
|
|
return
|
|
}
|
|
|
|
doc, err := h.Store.Document.Get(ctx, documentID)
|
|
if err != nil {
|
|
response.WriteServerError(w, method, err)
|
|
h.Runtime.Log.Error("no document for category", err)
|
|
return
|
|
}
|
|
|
|
if !permission.HasPermission(ctx, *h.Store, doc.LabelID, pm.SpaceView, pm.DocumentAdd, pm.DocumentEdit) {
|
|
response.WriteForbiddenError(w)
|
|
return
|
|
}
|
|
|
|
cat, err := h.Store.Category.GetDocumentCategoryMembership(ctx, doc.RefID)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
h.Runtime.Log.Error("get document category membership", err)
|
|
response.WriteServerError(w, method, err)
|
|
return
|
|
}
|
|
|
|
if len(cat) == 0 {
|
|
cat = []category.Category{}
|
|
}
|
|
|
|
perm, err := h.Store.Permission.GetUserCategoryPermissions(ctx, ctx.UserID)
|
|
if err != nil {
|
|
h.Runtime.Log.Error("get user category permissions", err)
|
|
response.WriteServerError(w, method, err)
|
|
return
|
|
}
|
|
|
|
see := []category.Category{}
|
|
for _, c := range cat {
|
|
for _, p := range perm {
|
|
if p.RefID == c.RefID {
|
|
see = append(see, c)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
response.WriteJSON(w, see)
|
|
}
|
|
|
|
// GetSpaceCategoryMembers returns category/document associations within space.
|
|
func (h *Handler) GetSpaceCategoryMembers(w http.ResponseWriter, r *http.Request) {
|
|
method := "category.GetSpaceCategoryMembers"
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
spaceID := request.Param(r, "spaceID")
|
|
if len(spaceID) == 0 {
|
|
response.WriteMissingDataError(w, method, "spaceID")
|
|
return
|
|
}
|
|
|
|
if !permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceView) {
|
|
response.WriteForbiddenError(w)
|
|
return
|
|
}
|
|
|
|
cat, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
h.Runtime.Log.Error("get document category membership for space", err)
|
|
response.WriteServerError(w, method, err)
|
|
return
|
|
}
|
|
|
|
if len(cat) == 0 {
|
|
cat = []category.Member{}
|
|
}
|
|
|
|
response.WriteJSON(w, cat)
|
|
}
|
|
|
|
// FetchSpaceData returns:
|
|
// 1. categories that user can see for given space
|
|
// 2. summary data for each category
|
|
// 3. category viewing membership records
|
|
func (h *Handler) FetchSpaceData(w http.ResponseWriter, r *http.Request) {
|
|
method := "category.FetchSpaceData"
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
spaceID := request.Param(r, "spaceID")
|
|
if len(spaceID) == 0 {
|
|
response.WriteMissingDataError(w, method, "spaceID")
|
|
return
|
|
}
|
|
|
|
ok := permission.HasPermission(ctx, *h.Store, spaceID, pm.SpaceManage, pm.SpaceOwner, pm.SpaceView)
|
|
if !ok {
|
|
response.WriteForbiddenError(w)
|
|
return
|
|
}
|
|
|
|
fetch := category.FetchSpaceModel{}
|
|
|
|
// get space categories visible to user
|
|
cat, err := h.Store.Category.GetBySpace(ctx, spaceID)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
h.Runtime.Log.Error("get space categories visible to user failed", err)
|
|
response.WriteServerError(w, method, err)
|
|
return
|
|
}
|
|
if len(cat) == 0 {
|
|
cat = []category.Category{}
|
|
}
|
|
|
|
// summary of space category usage
|
|
summary, err := h.Store.Category.GetSpaceCategorySummary(ctx, spaceID)
|
|
if err != nil {
|
|
h.Runtime.Log.Error("get space category summary failed", err)
|
|
response.WriteServerError(w, method, err)
|
|
return
|
|
}
|
|
if len(summary) == 0 {
|
|
summary = []category.SummaryModel{}
|
|
}
|
|
|
|
// get category membership records
|
|
member, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
h.Runtime.Log.Error("get document category membership for space", err)
|
|
response.WriteServerError(w, method, err)
|
|
return
|
|
}
|
|
|
|
if len(member) == 0 {
|
|
member = []category.Member{}
|
|
}
|
|
|
|
fetch.Category = cat
|
|
fetch.Summary = summary
|
|
fetch.Membership = member
|
|
|
|
response.WriteJSON(w, fetch)
|
|
}
|