mirror of
https://github.com/documize/community.git
synced 2025-08-02 20:15:26 +02:00
pin spaces and documents to sidebar
This commit is contained in:
parent
e0d2dd47df
commit
8cc798990a
27 changed files with 2943 additions and 1589 deletions
|
@ -276,6 +276,14 @@ func DeleteDocument(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
_, err = p.DeletePinnedDocument(documentID)
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
log.IfErr(tx.Rollback())
|
||||
writeServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
writeSuccessEmptyJSON(w)
|
||||
|
|
|
@ -319,6 +319,14 @@ func RemoveFolder(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
_, err = p.DeletePinnedSpace(id)
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
log.IfErr(tx.Rollback())
|
||||
writeServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
writeSuccessString(w, "{}")
|
||||
|
|
235
core/api/endpoint/pin_endpoint.go
Normal file
235
core/api/endpoint/pin_endpoint.go
Normal file
|
@ -0,0 +1,235 @@
|
|||
// 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 endpoint
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/documize/community/core/api/entity"
|
||||
"github.com/documize/community/core/api/request"
|
||||
"github.com/documize/community/core/api/util"
|
||||
"github.com/documize/community/core/log"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// AddPin saves pinned item.
|
||||
func AddPin(w http.ResponseWriter, r *http.Request) {
|
||||
method := "AddPin"
|
||||
p := request.GetPersister(r)
|
||||
params := mux.Vars(r)
|
||||
userID := params["userID"]
|
||||
|
||||
if !p.Context.Authenticated {
|
||||
writeForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
if len(userID) == 0 {
|
||||
writeMissingDataError(w, method, "userID")
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writePayloadError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
var pin entity.Pin
|
||||
err = json.Unmarshal(body, &pin)
|
||||
if err != nil {
|
||||
writePayloadError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
pin.RefID = util.UniqueID()
|
||||
pin.OrgID = p.Context.OrgID
|
||||
pin.UserID = p.Context.UserID
|
||||
pin.Pin = strings.TrimSpace(pin.Pin)
|
||||
if len(pin.Pin) > 20 {
|
||||
pin.Pin = pin.Pin[0:20]
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
if err != nil {
|
||||
writeTransactionError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
p.Context.Transaction = tx
|
||||
|
||||
err = p.AddPin(pin)
|
||||
if err != nil {
|
||||
log.IfErr(tx.Rollback())
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
newPin, err := p.GetPin(pin.RefID)
|
||||
if err != nil {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSON(w, newPin)
|
||||
}
|
||||
|
||||
// GetUserPins returns users' pins.
|
||||
func GetUserPins(w http.ResponseWriter, r *http.Request) {
|
||||
method := "GetUserPins"
|
||||
p := request.GetPersister(r)
|
||||
params := mux.Vars(r)
|
||||
userID := params["userID"]
|
||||
|
||||
if len(userID) == 0 {
|
||||
writeMissingDataError(w, method, "userID")
|
||||
return
|
||||
}
|
||||
|
||||
if p.Context.UserID != userID {
|
||||
writeForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
pins, err := p.GetUserPins(userID)
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
pins = []entity.Pin{}
|
||||
}
|
||||
|
||||
json, err := json.Marshal(pins)
|
||||
|
||||
if err != nil {
|
||||
writeJSONMarshalError(w, method, "pin", err)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessBytes(w, json)
|
||||
}
|
||||
|
||||
// DeleteUserPin removes saved user pin.
|
||||
func DeleteUserPin(w http.ResponseWriter, r *http.Request) {
|
||||
method := "DeleteUserPin"
|
||||
p := request.GetPersister(r)
|
||||
params := mux.Vars(r)
|
||||
userID := params["userID"]
|
||||
pinID := params["pinID"]
|
||||
|
||||
if len(userID) == 0 {
|
||||
writeMissingDataError(w, method, "userID")
|
||||
return
|
||||
}
|
||||
|
||||
if len(pinID) == 0 {
|
||||
writeMissingDataError(w, method, "pinID")
|
||||
return
|
||||
}
|
||||
|
||||
if p.Context.UserID != userID {
|
||||
writeForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
if err != nil {
|
||||
writeTransactionError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
p.Context.Transaction = tx
|
||||
|
||||
_, err = p.DeletePin(pinID)
|
||||
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
log.IfErr(tx.Rollback())
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
util.WriteSuccessEmptyJSON(w)
|
||||
}
|
||||
|
||||
// UpdatePinSequence records order of pinned items.
|
||||
func UpdatePinSequence(w http.ResponseWriter, r *http.Request) {
|
||||
method := "UpdatePinSequence"
|
||||
p := request.GetPersister(r)
|
||||
params := mux.Vars(r)
|
||||
userID := params["userID"]
|
||||
|
||||
if !p.Context.Authenticated {
|
||||
writeForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
if len(userID) == 0 {
|
||||
writeMissingDataError(w, method, "userID")
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writePayloadError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
var pins []string
|
||||
|
||||
err = json.Unmarshal(body, &pins)
|
||||
if err != nil {
|
||||
writePayloadError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
if err != nil {
|
||||
writeTransactionError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
p.Context.Transaction = tx
|
||||
|
||||
for k, v := range pins {
|
||||
err = p.UpdatePinSequence(v, k+1)
|
||||
|
||||
if err != nil {
|
||||
log.IfErr(tx.Rollback())
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
newPins, err := p.GetUserPins(userID)
|
||||
if err != nil {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
util.WriteJSON(w, newPins)
|
||||
}
|
|
@ -221,6 +221,12 @@ func init() {
|
|||
log.IfErr(Add(RoutePrefixPrivate, "global", []string{"GET", "OPTIONS"}, nil, GetGlobalConfig))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "global", []string{"PUT", "OPTIONS"}, nil, SaveGlobalConfig))
|
||||
|
||||
// Pinned items
|
||||
log.IfErr(Add(RoutePrefixPrivate, "pin/{userID}", []string{"POST", "OPTIONS"}, nil, AddPin))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "pin/{userID}", []string{"GET", "OPTIONS"}, nil, GetUserPins))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "pin/{userID}/sequence", []string{"POST", "OPTIONS"}, nil, UpdatePinSequence))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "pin/{userID}/{pinID}", []string{"DELETE", "OPTIONS"}, nil, DeleteUserPin))
|
||||
|
||||
// Single page app handler
|
||||
log.IfErr(Add(RoutePrefixRoot, "robots.txt", []string{"GET", "OPTIONS"}, nil, GetRobots))
|
||||
log.IfErr(Add(RoutePrefixRoot, "sitemap.xml", []string{"GET", "OPTIONS"}, nil, GetSitemap))
|
||||
|
|
|
@ -385,3 +385,14 @@ type LinkCandidate struct {
|
|||
Title string `json:"title"` // what we label the link
|
||||
Context string `json:"context"` // additional context (e.g. excerpt, parent, file extension)
|
||||
}
|
||||
|
||||
// Pin defines a saved link to a document or space
|
||||
type Pin struct {
|
||||
BaseEntity
|
||||
OrgID string `json:"orgId"`
|
||||
UserID string `json:"userId"`
|
||||
FolderID string `json:"folderId"`
|
||||
DocumentID string `json:"documentId"`
|
||||
Pin string `json:"pin"`
|
||||
Sequence int `json:"sequence"`
|
||||
}
|
||||
|
|
|
@ -16,13 +16,12 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"github.com/documize/community/core/api/endpoint/models"
|
||||
"github.com/documize/community/core/api/entity"
|
||||
"github.com/documize/community/core/api/util"
|
||||
"github.com/documize/community/core/log"
|
||||
"github.com/documize/community/core/utility"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// AddPage inserts the given page into the page table, adds that page to the queue of pages to index and audits that the page has been added.
|
||||
|
|
145
core/api/request/pin.go
Normal file
145
core/api/request/pin.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/documize/community/core/api/entity"
|
||||
"github.com/documize/community/core/log"
|
||||
"github.com/documize/community/core/utility"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
if err != nil {
|
||||
maxSeq = 99
|
||||
}
|
||||
|
||||
pin.Created = time.Now().UTC()
|
||||
pin.Revised = time.Now().UTC()
|
||||
pin.Sequence = maxSeq + 1
|
||||
|
||||
stmt, err := p.Context.Transaction.Preparex("INSERT INTO pin (refid, orgid, userid, labelid, documentid, pin, sequence, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
log.Error("Unable to prepare insert for pin", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(pin.RefID, pin.OrgID, pin.UserID, pin.FolderID, pin.DocumentID, pin.Pin, pin.Sequence, pin.Created, pin.Revised)
|
||||
|
||||
if err != nil {
|
||||
log.Error("Unable to execute insert for pin", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetPin returns requested pinned item.
|
||||
func (p *Persister) GetPin(id string) (pin entity.Pin, err error) {
|
||||
err = nil
|
||||
|
||||
stmt, err := Db.Preparex("SELECT id, refid, orgid, userid, labelid as folderid, documentid, pin, sequence, created, revised FROM pin WHERE orgid=? AND refid=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to prepare select for pin %s", id), err)
|
||||
return
|
||||
}
|
||||
|
||||
err = stmt.Get(&pin, p.Context.OrgID, id)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute select for pin %s", id), err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserPins returns pinned items for specified user.
|
||||
func (p *Persister) GetUserPins(userID string) (pins []entity.Pin, err error) {
|
||||
err = Db.Select(&pins, "SELECT id, refid, orgid, userid, labelid as folderid, documentid, pin, sequence, created, revised FROM pin WHERE orgid=? AND userid=? ORDER BY sequence", p.Context.OrgID, userID)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute select pin for org %s and user %s", p.Context.OrgID, userID), err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UpdatePin updates existing pinned item.
|
||||
func (p *Persister) UpdatePin(pin entity.Pin) (err error) {
|
||||
err = nil
|
||||
pin.Revised = time.Now().UTC()
|
||||
|
||||
var stmt *sqlx.NamedStmt
|
||||
stmt, err = p.Context.Transaction.PrepareNamed("UPDATE pin SET labelid=:folderid, documentid=:documentid, pin=:pin, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to prepare update for pin %s", pin.RefID), err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(&pin)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute update for pin %s", pin.RefID), err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UpdatePinSequence updates existing pinned item sequence number
|
||||
func (p *Persister) UpdatePinSequence(pinID string, sequence int) (err error) {
|
||||
err = nil
|
||||
|
||||
stmt, err := p.Context.Transaction.Preparex("UPDATE pin SET sequence=?, revised=? WHERE orgid=? AND userid=? AND refid=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to prepare update for pin sequence %s", pinID), err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = stmt.Exec(sequence, time.Now().UTC(), p.Context.OrgID, p.Context.UserID, pinID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeletePin removes folder from the store.
|
||||
func (p *Persister) DeletePin(id string) (rows int64, err error) {
|
||||
return p.Base.DeleteConstrained(p.Context.Transaction, "pin", p.Context.OrgID, id)
|
||||
}
|
||||
|
||||
// DeletePinnedSpace removes any pins for specified space.
|
||||
func (p *Persister) DeletePinnedSpace(spaceID string) (rows int64, err error) {
|
||||
return p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM pin WHERE orgid=\"%s\" AND labelid=\"%s\"", p.Context.OrgID, spaceID))
|
||||
}
|
||||
|
||||
// DeletePinnedDocument removes any pins for specified document.
|
||||
func (p *Persister) DeletePinnedDocument(documentID string) (rows int64, err error) {
|
||||
return p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM pin WHERE orgid=\"%s\" AND documentid=\"%s\"", p.Context.OrgID, documentID))
|
||||
}
|
|
@ -351,3 +351,21 @@ CREATE TABLE IF NOT EXISTS `participant` (
|
|||
INDEX `idx_participant_documentid` (`documentid` ASC))
|
||||
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
||||
ENGINE = InnoDB;
|
||||
|
||||
DROP TABLE IF EXISTS `pin`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `pin` (
|
||||
`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) DEFAULT '' COLLATE utf8_bin,
|
||||
`labelid` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||
`documentid` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||
`sequence` INT UNSIGNED NOT NULL DEFAULT 99,
|
||||
`pin` CHAR(20) NOT NULL DEFAULT '' COLLATE utf8_bin,
|
||||
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT pk_id PRIMARY KEY (id),
|
||||
INDEX `idx_pin_userid` (`userid` ASC))
|
||||
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
|
||||
ENGINE = InnoDB;
|
||||
|
|
18
core/database/scripts/autobuild/db_00008.sql
Normal file
18
core/database/scripts/autobuild/db_00008.sql
Normal file
|
@ -0,0 +1,18 @@
|
|||
/* community edition */
|
||||
DROP TABLE IF EXISTS `pin`;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `pin` (
|
||||
`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) DEFAULT '' COLLATE utf8_bin,
|
||||
`labelid` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||
`documentid` CHAR(16) DEFAULT '' COLLATE utf8_bin,
|
||||
`sequence` INT UNSIGNED NOT NULL DEFAULT 99,
|
||||
`pin` CHAR(20) NOT NULL DEFAULT '' COLLATE utf8_bin,
|
||||
`created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`revised` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT pk_id PRIMARY KEY (id),
|
||||
INDEX `idx_pin_userid` (`userid` 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 = "33"
|
||||
p.Minor = "34"
|
||||
p.Patch = "0"
|
||||
p.Version = fmt.Sprintf("%s.%s.%s", p.Major, p.Minor, p.Patch)
|
||||
p.Edition = "Community"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue