api/rest.go
package api
import (
"database/sql"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/casbin/casbin"
"github.com/lib/pq"
"github.com/pkg/errors"
"github.com/volatiletech/null/v8"
"github.com/volatiletech/sqlboiler/v4/boil"
"github.com/volatiletech/sqlboiler/v4/queries"
"github.com/volatiletech/sqlboiler/v4/queries/qm"
"gopkg.in/gin-gonic/gin.v1"
"github.com/Bnei-Baruch/mdb/common"
"github.com/Bnei-Baruch/mdb/events"
"github.com/Bnei-Baruch/mdb/models"
"github.com/Bnei-Baruch/mdb/permissions"
"github.com/Bnei-Baruch/mdb/utils"
)
const (
DEFAULT_PAGE_SIZE = 50
MAX_PAGE_SIZE = 1000
SEARCH_IN_FILES = 1
SEARCH_IN_CONTENT_UNITS = 2
SEARCH_IN_COLLECTIONS = 3
SEARCH_IN_CONTENT_UNITS_TYPE_SOURCE = 4
)
func CollectionsListHandler(c *gin.Context) {
var err *HttpError
var resp interface{}
switch c.Request.Method {
case http.MethodGet, "":
var r CollectionsRequest
if c.Bind(&r) != nil {
return
}
resp, err = handleCollectionsList(c, c.MustGet("MDB").(*sql.DB), r)
case http.MethodPost:
var collection Collection
if c.BindJSON(&collection) != nil {
return
}
if _, ok := common.CONTENT_TYPE_REGISTRY.ByID[collection.TypeID]; !ok {
err := errors.Errorf("Unknown content type %d", collection.TypeID)
NewBadRequestError(err).Abort(c)
return
}
for _, x := range collection.I18n {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
err := errors.Errorf("Unknown language %s", x.Language)
NewBadRequestError(err).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err = handleCreateCollection(c, tx, collection)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.CollectionCreateEvent(&resp.(*Collection).Collection))
}
}
concludeRequest(c, resp, err)
}
func CollectionHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var err *HttpError
var resp interface{}
switch c.Request.Method {
case http.MethodGet, "":
resp, err = handleGetCollection(c, c.MustGet("MDB").(*sql.DB), id)
case http.MethodPut:
var cl PartialCollection
if c.Bind(&cl) != nil {
return
}
cl.ID = id
tx := mustBeginTx(c)
resp, err = handleUpdateCollection(c, tx, &cl)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.CollectionUpdateEvent(&resp.(*Collection).Collection))
}
case http.MethodDelete:
tx := mustBeginTx(c)
var cl *models.Collection
cl, err = handleDeleteCollection(c, tx, id)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.CollectionDeleteEvent(cl))
}
}
concludeRequest(c, resp, err)
}
func CollectionI18nHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var i18ns []*models.CollectionI18n
if c.Bind(&i18ns) != nil {
return
}
for _, x := range i18ns {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
NewBadRequestError(errors.Errorf("Unknown language %s", x.Language)).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err := handleUpdateCollectionI18n(c, tx, id, i18ns)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.CollectionUpdateEvent(&resp.Collection))
}
concludeRequest(c, resp, err)
}
func CollectionContentUnitsHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var err *HttpError
var resp interface{}
switch c.Request.Method {
case http.MethodGet, "":
resp, err = handleCollectionCCU(c, c.MustGet("MDB").(*sql.DB), id)
case http.MethodPost:
var ccus []*models.CollectionsContentUnit
if c.BindJSON(&ccus) != nil {
return
}
var evnts []events.Event
tx := mustBeginTx(c)
evnts, err = handleCollectionAddCCU(c, tx, id, ccus)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, evnts...)
}
case http.MethodPut:
var ccu models.CollectionsContentUnit
if c.BindJSON(&ccu) != nil {
return
}
cuID, e := strconv.ParseInt(c.Param("cuID"), 10, 0)
if e != nil {
err = NewBadRequestError(errors.Wrap(e, "cuID expects int64"))
break
}
ccu.ContentUnitID = cuID
var event *events.Event
tx := mustBeginTx(c)
event, err = handleCollectionUpdateCCU(c, tx, id, ccu)
mustConcludeTx(tx, err)
if err == nil && event != nil {
emitEvents(c, *event)
}
case http.MethodDelete:
cuID, e := strconv.ParseInt(c.Param("cuID"), 10, 0)
if e != nil {
err = NewBadRequestError(errors.Wrap(e, "cuID expects int64"))
break
}
var evnts []events.Event
tx := mustBeginTx(c)
evnts, err = handleCollectionRemoveCCU(c, tx, id, cuID)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, evnts...)
}
}
concludeRequest(c, resp, err)
}
func CollectionContentUnitsPositionHandler(c *gin.Context) {
var err *HttpError
var event *events.Event
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var r CCUPositionRequest
if c.Bind(&r) != nil {
return
}
tx := mustBeginTx(c)
event, err = handleCollectionContentUnitsPosition(tx, id, r.OrderType)
mustConcludeTx(tx, err)
if err != nil {
NewInternalError(err).Abort(c)
return
}
if event != nil {
emitEvents(c, *event)
}
resp, err := handleCollectionCCU(c, c.MustGet("MDB").(*sql.DB), id)
concludeRequest(c, resp, err)
}
// Toggle the active flag of a single container
func CollectionActivateHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
resp, err := handleCollectionActivate(c, c.MustGet("MDB").(*sql.DB), id)
concludeRequest(c, resp, err)
}
func ContentUnitsListHandler(c *gin.Context) {
var err *HttpError
var resp interface{}
switch c.Request.Method {
case http.MethodGet, "":
var r ContentUnitsRequest
if c.Bind(&r) != nil {
return
}
resp, err = handleContentUnitsList(c, c.MustGet("MDB").(*sql.DB), r)
case http.MethodPost:
var unit ContentUnit
if c.BindJSON(&unit) != nil {
return
}
if _, ok := common.CONTENT_TYPE_REGISTRY.ByID[unit.TypeID]; !ok {
err := errors.Errorf("Unknown content type %d", unit.TypeID)
NewBadRequestError(err).Abort(c)
return
}
for _, x := range unit.I18n {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
err := errors.Errorf("Unknown language %s", x.Language)
NewBadRequestError(err).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err = handleCreateContentUnit(c, tx, unit)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.ContentUnitCreateEvent(&resp.(*ContentUnit).ContentUnit))
}
}
concludeRequest(c, resp, err)
}
func ContentUnitHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var err *HttpError
var resp interface{}
if c.Request.Method == http.MethodGet || c.Request.Method == "" {
resp, err = handleGetContentUnit(c, c.MustGet("MDB").(*sql.DB), id)
} else {
if c.Request.Method == http.MethodPut {
var cu PartialContentUnit
if c.Bind(&cu) != nil {
return
}
cu.ID = id
tx := mustBeginTx(c)
resp, err = handleUpdateContentUnit(c, tx, &cu)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.ContentUnitUpdateEvent(&resp.(*ContentUnit).ContentUnit))
}
}
}
concludeRequest(c, resp, err)
}
// Get auto names of unit
func ContentUnitAutoname(c *gin.Context) {
var r *ContentUnitAutonameRequest
if c.Bind(&r) != nil {
return
}
resp, err := handleContentUnitAutoname(c.MustGet("MDB").(*sql.DB), r)
concludeRequest(c, resp, err)
}
func ContentUnitI18nHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var i18ns []*models.ContentUnitI18n
if c.Bind(&i18ns) != nil {
return
}
for _, x := range i18ns {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
NewBadRequestError(errors.Errorf("Unknown language %s", x.Language)).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err := handleUpdateContentUnitI18n(c, tx, id, i18ns)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.ContentUnitUpdateEvent(&resp.ContentUnit))
}
concludeRequest(c, resp, err)
}
func ContentUnitFilesHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var err *HttpError
var resp interface{}
if c.Request.Method == http.MethodGet || c.Request.Method == "" {
resp, err = handleContentUnitFiles(c, c.MustGet("MDB").(*sql.DB), id)
} else {
if c.Request.Method == http.MethodPost {
var fids []int64
if c.Bind(&fids) != nil {
return
}
var evnts []events.Event
tx := mustBeginTx(c)
resp, evnts, err = handleContentUnitAddFiles(c, tx, id, fids)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, evnts...)
}
}
}
concludeRequest(c, resp, err)
}
func ContentUnitCollectionsHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
resp, err := handleContentUnitCCU(c, c.MustGet("MDB").(*sql.DB), id)
concludeRequest(c, resp, err)
}
func ContentUnitDerivativesHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var err *HttpError
var resp interface{}
switch c.Request.Method {
case http.MethodGet, "":
resp, err = handleContentUnitCUD(c, c.MustGet("MDB").(*sql.DB), id)
case http.MethodPost:
var cud models.ContentUnitDerivation
if c.BindJSON(&cud) != nil {
return
}
tx := mustBeginTx(c)
resp, err = handleContentUnitAddCUD(c, tx, id, cud)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.ContentUnitDerivativesChangeEvent(resp.(*models.ContentUnit)))
}
case http.MethodPut:
var cud models.ContentUnitDerivation
if c.BindJSON(&cud) != nil {
return
}
duID, e := strconv.ParseInt(c.Param("duID"), 10, 0)
if e != nil {
err = NewBadRequestError(errors.Wrap(e, "duID expects int64"))
break
}
cud.DerivedID = duID
tx := mustBeginTx(c)
resp, err = handleContentUnitUpdateCUD(c, tx, id, cud)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.ContentUnitDerivativesChangeEvent(resp.(*models.ContentUnit)))
}
case http.MethodDelete:
duID, e := strconv.ParseInt(c.Param("duID"), 10, 0)
if e != nil {
err = NewBadRequestError(errors.Wrap(e, "duID expects int64"))
break
}
tx := mustBeginTx(c)
resp, err = handleContentUnitRemoveCUD(c, tx, id, duID)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.ContentUnitDerivativesChangeEvent(resp.(*models.ContentUnit)))
}
}
concludeRequest(c, resp, err)
}
func ContentUnitOriginsHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
resp, err := handleContentUnitOrigins(c, c.MustGet("MDB").(*sql.DB), id)
concludeRequest(c, resp, err)
}
func ContentUnitSourcesHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
switch c.Request.Method {
case http.MethodGet, "":
resp, err := handleGetContentUnitSources(c, c.MustGet("MDB").(*sql.DB), id)
concludeRequest(c, resp, err)
case http.MethodPost:
var body map[string]int64
if c.BindJSON(&body) != nil {
return
}
sourceID, ok := body["sourceID"]
if !ok {
NewBadRequestError(errors.Wrap(e, "No sourceID given")).Abort(c)
return
}
tx := mustBeginTx(c)
resp, err := handleContentUnitAddSource(c, tx, id, sourceID)
mustConcludeTx(tx, err)
if err == nil && resp != nil {
emitEvents(c, events.ContentUnitSourcesChangeEvent(resp))
}
concludeRequest(c, resp, err)
case http.MethodDelete:
sourceID, e := strconv.ParseInt(c.Param("sourceID"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "sourceID expects int64")).Abort(c)
return
}
tx := mustBeginTx(c)
resp, err := handleContentUnitRemoveSource(c, tx, id, sourceID)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.ContentUnitSourcesChangeEvent(resp))
}
concludeRequest(c, resp, err)
}
}
func ContentUnitTagsHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
switch c.Request.Method {
case http.MethodGet, "":
resp, err := handleGetContentUnitTags(c, c.MustGet("MDB").(*sql.DB), id)
concludeRequest(c, resp, err)
case http.MethodPost:
var body map[string]int64
if c.BindJSON(&body) != nil {
return
}
tagID, ok := body["tagID"]
if !ok {
NewBadRequestError(errors.Wrap(e, "No tagID given")).Abort(c)
return
}
tx := mustBeginTx(c)
resp, err := handleContentUnitAddTag(c, tx, id, tagID)
mustConcludeTx(tx, err)
if err == nil && resp != nil {
emitEvents(c, events.ContentUnitTagsChangeEvent(resp))
}
concludeRequest(c, resp, err)
case http.MethodDelete:
tagID, e := strconv.ParseInt(c.Param("tagID"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "tagID expects int64")).Abort(c)
return
}
tx := mustBeginTx(c)
resp, err := handleContentUnitRemoveTag(c, tx, id, tagID)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.ContentUnitTagsChangeEvent(resp))
}
concludeRequest(c, resp, err)
}
}
func ContentUnitPersonsHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
switch c.Request.Method {
case http.MethodGet, "":
resp, err := handleGetContentUnitPersons(c, c.MustGet("MDB").(*sql.DB), id)
concludeRequest(c, resp, err)
case http.MethodPost:
var cup models.ContentUnitsPerson
if c.BindJSON(&cup) != nil {
return
}
tx := mustBeginTx(c)
resp, err := handleContentUnitAddPerson(c, tx, id, cup)
mustConcludeTx(tx, err)
if err == nil && resp != nil {
emitEvents(c, events.ContentUnitPersonsChangeEvent(resp))
}
concludeRequest(c, resp, err)
case http.MethodDelete:
personID, e := strconv.ParseInt(c.Param("personID"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "personID expects int64")).Abort(c)
return
}
tx := mustBeginTx(c)
resp, err := handleContentUnitRemovePerson(c, tx, id, personID)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.ContentUnitPersonsChangeEvent(resp))
}
concludeRequest(c, resp, err)
}
}
func ContentUnitPublishersHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var err *HttpError
var resp interface{}
switch c.Request.Method {
case http.MethodGet, "":
resp, err = handleGetContentUnitPublishers(c, c.MustGet("MDB").(*sql.DB), id)
case http.MethodPost:
var body map[string]int64
if c.BindJSON(&body) != nil {
return
}
publisherID, ok := body["publisherID"]
if !ok {
err = NewBadRequestError(errors.Wrap(e, "No publisherID given"))
break
}
tx := mustBeginTx(c)
resp, err = handleContentUnitAddPublisher(c, tx, id, publisherID)
mustConcludeTx(tx, err)
if respCU, ok := resp.(*models.ContentUnit); ok && err == nil {
emitEvents(c, events.ContentUnitPublishersChangeEvent(respCU))
}
case http.MethodDelete:
publisherID, e := strconv.ParseInt(c.Param("publisherID"), 10, 0)
if e != nil {
err = NewBadRequestError(errors.Wrap(e, "publisherID expects int64"))
break
}
tx := mustBeginTx(c)
resp, err = handleContentUnitRemovePublisher(c, tx, id, publisherID)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.ContentUnitPublishersChangeEvent(resp.(*models.ContentUnit)))
}
}
concludeRequest(c, resp, err)
}
func ContentUnitMergeHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var cuIDs []int64
if c.Bind(&cuIDs) != nil {
return
}
// filter out host unit ID
b := cuIDs[:0]
for _, x := range cuIDs {
if x != id {
b = append(b, x)
}
}
tx := mustBeginTx(c)
resp, evnts, err := handleContentUnitMerge(c, tx, id, b)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, evnts...)
}
concludeRequest(c, resp, err)
}
func FilesListHandler(c *gin.Context) {
var r FilesRequest
if c.Bind(&r) != nil {
return
}
resp, err := handleFilesList(c, c.MustGet("MDB").(*sql.DB), r)
concludeRequest(c, resp, err)
}
func FileHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var err *HttpError
var resp interface{}
if c.Request.Method == http.MethodGet || c.Request.Method == "" {
resp, err = handleGetFile(c, c.MustGet("MDB").(*sql.DB), id)
} else {
if c.Request.Method == http.MethodPut {
var f PartialFile
if c.Bind(&f) != nil {
return
}
f.ID = id
var evnts []events.Event
tx := mustBeginTx(c)
resp, evnts, err = handleUpdateFile(c, tx, &f)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, evnts...)
}
}
}
concludeRequest(c, resp, err)
}
func FileStoragesHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
resp, err := handleFileStorages(c, c.MustGet("MDB").(*sql.DB), id)
concludeRequest(c, resp, err)
}
func FilesWithOperationsTreeHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
db := c.MustGet("MDB").(*sql.DB)
files, err := FindFileTreeWithOperations(db, id)
if err != nil {
NewInternalError(err).Abort(c)
return
}
opIdsMap := make(map[int64]bool)
for i := range files {
for j := range files[i].OperationIds {
opIdsMap[files[i].OperationIds[j]] = true
}
}
opIds := make([]int64, len(opIdsMap))
i := 0
for k := range opIdsMap {
opIds[i] = k
i++
}
ops, err := models.Operations(qm.WhereIn("id in ?", utils.ConvertArgsInt64(opIds)...)).All(db)
if err != nil {
NewInternalError(err).Abort(c)
return
}
opsMap := make(map[int64]*models.Operation)
for _, op := range ops {
opsMap[op.ID] = op
}
resp := &struct {
Files []*MFile `json:"files"`
Operations map[int64]*models.Operation `json:"operations"`
}{
files,
opsMap,
}
concludeRequest(c, resp, nil)
}
func OperationsListHandler(c *gin.Context) {
// check permissions
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
var r OperationsRequest
if c.Bind(&r) != nil {
return
}
resp, err := handleOperationsList(c.MustGet("MDB").(*sql.DB), r)
concludeRequest(c, resp, err)
}
func OperationItemHandler(c *gin.Context) {
// check permissions
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
resp, err := handleOperationItem(c.MustGet("MDB").(*sql.DB), id)
concludeRequest(c, resp, err)
}
func OperationFilesHandler(c *gin.Context) {
// check permissions
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
resp, err := handleOperationFiles(c, c.MustGet("MDB").(*sql.DB), id)
concludeRequest(c, resp, err)
}
func AuthorsHandler(c *gin.Context) {
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
authors, err := models.Authors(qm.Load("AuthorI18ns"), qm.Load("Sources")).
All(c.MustGet("MDB").(*sql.DB))
if err != nil {
NewInternalError(errors.Wrap(err, "Load authors from DB")).Abort(c)
return
}
data := make([]*Author, len(authors))
for i, a := range authors {
x := &Author{Author: *a}
data[i] = x
x.I18n = make(map[string]*models.AuthorI18n, len(a.R.AuthorI18ns))
for _, i18n := range a.R.AuthorI18ns {
x.I18n[i18n.Language] = i18n
}
x.Sources = make([]*Source, len(a.R.Sources))
for j, s := range a.R.Sources {
x.Sources[j] = &Source{Source: *s}
}
}
resp := AuthorsResponse{
ListResponse: ListResponse{Total: int64(len(data))},
Authors: data,
}
concludeRequest(c, resp, nil)
}
func SourcesHandler(c *gin.Context) {
var err *HttpError
var resp interface{}
if c.Request.Method == http.MethodGet || c.Request.Method == "" {
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
var r SourcesRequest
if c.Bind(&r) != nil {
return
}
resp, err = handleGetSources(c.MustGet("MDB").(*sql.DB), r)
} else {
if c.Request.Method == http.MethodPost {
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
var r CreateSourceRequest
if c.Bind(&r) != nil {
return
}
if _, ok := common.SOURCE_TYPE_REGISTRY.ByID[r.Source.TypeID]; !ok {
err := errors.Errorf("Unknown source type %d", r.Source.TypeID)
NewBadRequestError(err).Abort(c)
return
}
for _, x := range r.Source.I18n {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
err := errors.Errorf("Unknown language %s", x.Language)
NewBadRequestError(err).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err = handleCreateSource(tx, r)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.SourceCreateEvent(&resp.(*Source).Source))
}
}
}
concludeRequest(c, resp, err)
}
func SourceHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var err *HttpError
var resp interface{}
if c.Request.Method == http.MethodGet || c.Request.Method == "" {
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
resp, err = handleGetSource(c.MustGet("MDB").(*sql.DB), id)
} else {
if c.Request.Method == http.MethodPut {
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
var s Source
if c.Bind(&s) != nil {
return
}
s.ID = id
tx := mustBeginTx(c)
resp, err = handleUpdateSource(tx, &s)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.SourceUpdateEvent(&resp.(*Source).Source))
}
}
}
concludeRequest(c, resp, err)
}
func SourceI18nHandler(c *gin.Context) {
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var i18ns []*models.SourceI18n
if c.Bind(&i18ns) != nil {
return
}
for _, x := range i18ns {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
NewBadRequestError(errors.Errorf("Unknown language %s", x.Language)).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err := handleUpdateSourceI18n(tx, id, i18ns)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.SourceUpdateEvent(&resp.Source))
}
concludeRequest(c, resp, err)
}
func TagsHandler(c *gin.Context) {
var err *HttpError
var resp interface{}
if c.Request.Method == http.MethodGet || c.Request.Method == "" {
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
var r TagsRequest
if c.Bind(&r) != nil {
return
}
resp, err = handleGetTags(c.MustGet("MDB").(*sql.DB), r)
} else {
if c.Request.Method == http.MethodPost {
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
var t Tag
if c.Bind(&t) != nil {
return
}
for _, x := range t.I18n {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
NewBadRequestError(errors.Errorf("Unknown language %s", x.Language)).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err = handleCreateTag(tx, &t)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.TagCreateEvent(&resp.(*Tag).Tag))
}
}
}
concludeRequest(c, resp, err)
}
func TagHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var err *HttpError
var resp interface{}
if c.Request.Method == http.MethodGet || c.Request.Method == "" {
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
resp, err = handleGetTag(c.MustGet("MDB").(*sql.DB), id)
} else {
if c.Request.Method == http.MethodPut {
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
var t Tag
if c.Bind(&t) != nil {
return
}
t.ID = id
tx := mustBeginTx(c)
resp, err = handleUpdateTag(tx, &t)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.TagUpdateEvent(&resp.(*Tag).Tag))
}
}
}
concludeRequest(c, resp, err)
}
func TagI18nHandler(c *gin.Context) {
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var i18ns []*models.TagI18n
if c.Bind(&i18ns) != nil {
return
}
for _, x := range i18ns {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
NewBadRequestError(errors.Errorf("Unknown language %s", x.Language)).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err := handleUpdateTagI18n(tx, id, i18ns)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.TagUpdateEvent(&resp.Tag))
}
concludeRequest(c, resp, err)
}
func PersonsListHandler(c *gin.Context) {
var err *HttpError
var resp interface{}
switch c.Request.Method {
case http.MethodGet, "":
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
var r PersonsRequest
if c.Bind(&r) != nil {
return
}
resp, err = handlePersonsList(c.MustGet("MDB").(*sql.DB), r)
case http.MethodPost:
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
var person Person
if c.BindJSON(&person) != nil {
return
}
for _, x := range person.I18n {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
err := errors.Errorf("Unknown language %s", x.Language)
NewBadRequestError(err).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err = handleCreatePerson(tx, &person)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.PersonCreateEvent(&resp.(*Person).Person))
}
}
concludeRequest(c, resp, err)
}
func PersonHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var err *HttpError
var resp interface{}
switch c.Request.Method {
case http.MethodGet, "":
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
resp, err = handleGetPerson(c.MustGet("MDB").(*sql.DB), id)
case http.MethodPut:
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
var p Person
if c.Bind(&p) != nil {
return
}
p.ID = id
tx := mustBeginTx(c)
resp, err = handleUpdatePerson(tx, &p)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.PersonUpdateEvent(&resp.(*Person).Person))
}
case http.MethodDelete:
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
tx := mustBeginTx(c)
pr, err := handleDeletePerson(tx, id)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.PersonDeleteEvent(pr))
}
}
concludeRequest(c, resp, err)
}
func PersonI18nHandler(c *gin.Context) {
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var i18ns []*models.PersonI18n
if c.Bind(&i18ns) != nil {
return
}
for _, x := range i18ns {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
NewBadRequestError(errors.Errorf("Unknown language %s", x.Language)).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err := handleUpdatePersonI18n(tx, id, i18ns)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.PersonUpdateEvent(&resp.Person))
}
concludeRequest(c, resp, err)
}
func PublishersHandler(c *gin.Context) {
var err *HttpError
var resp interface{}
switch c.Request.Method {
case http.MethodGet, "":
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
var r PublishersRequest
if c.Bind(&r) != nil {
return
}
resp, err = handlePublishersList(c.MustGet("MDB").(*sql.DB), r)
case http.MethodPost:
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
var publisher Publisher
if c.BindJSON(&publisher) != nil {
return
}
for _, x := range publisher.I18n {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
err := errors.Errorf("Unknown language %s", x.Language)
NewBadRequestError(err).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err = handleCreatePublisher(tx, &publisher)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.PublisherCreateEvent(&resp.(*Publisher).Publisher))
}
}
concludeRequest(c, resp, err)
}
func PublisherHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var err *HttpError
var resp interface{}
if c.Request.Method == http.MethodGet || c.Request.Method == "" {
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
resp, err = handleGetPublisher(c.MustGet("MDB").(*sql.DB), id)
} else {
if c.Request.Method == http.MethodPut {
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
var p Publisher
if c.Bind(&p) != nil {
return
}
p.ID = id
tx := mustBeginTx(c)
resp, err = handleUpdatePublisher(tx, &p)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.PublisherUpdateEvent(&resp.(*Publisher).Publisher))
}
}
}
concludeRequest(c, resp, err)
}
func PublisherI18nHandler(c *gin.Context) {
if !isAdmin(c) {
NewForbiddenError().Abort(c)
return
}
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var i18ns []*models.PublisherI18n
if c.Bind(&i18ns) != nil {
return
}
for _, x := range i18ns {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
NewBadRequestError(errors.Errorf("Unknown language %s", x.Language)).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err := handleUpdatePublisherI18n(tx, id, i18ns)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.PublisherUpdateEvent(&resp.Publisher))
}
concludeRequest(c, resp, err)
}
func StoragesHandler(c *gin.Context) {
if !can(c, secureToPermission(common.SEC_PUBLIC), common.PERM_READ) {
NewForbiddenError().Abort(c)
return
}
var r StoragesRequest
if c.Bind(&r) != nil {
return
}
resp, err := handleStoragesList(c.MustGet("MDB").(*sql.DB), r)
concludeRequest(c, resp, err)
}
// Labels
func LabelListHandler(c *gin.Context) {
var err *HttpError
var resp interface{}
if c.Request.Method == http.MethodGet || c.Request.Method == "" {
var r LabelsRequest
if c.Bind(&r) != nil {
return
}
resp, err = handleGetLabelList(c, c.MustGet("MDB").(*sql.DB), &r)
}
if c.Request.Method == http.MethodPost {
var r CreateLabelRequest
if c.Bind(&r) != nil {
return
}
tx := mustBeginTx(c)
label, err := handleCreateLabel(c, tx, &r)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.LabelCreateEvent(label))
}
resp = label.UID
}
concludeRequest(c, resp, err)
}
func LabelHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var err *HttpError
var resp interface{}
if c.Request.Method == http.MethodGet || c.Request.Method == "" {
resp, err = handleGetLabel(c, c.MustGet("MDB").(*sql.DB), id, common.PERM_LABEL_READ)
}
if c.Request.Method == http.MethodPut {
//using SEC_SENSITIVE depended on definitions on permissions_policy.csv
//for archive_editor and archive_label-moderation roles
if !can(c, secureToPermission(common.SEC_SENSITIVE), common.PERM_LABEL_MODERATE) {
NewForbiddenError().Abort(c)
return
}
var r UpdateApproveStateRequest
if c.Bind(&r) != nil {
return
}
tx := mustBeginTx(c)
l, err := handleUpdateLabelState(tx, id, r)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.LabelUpdateEvent(l))
}
}
if c.Request.Method == http.MethodDelete {
//using SEC_SENSITIVE depended on definitions on permissions_policy.csv
//for archive_editor and archive_label-moderation roles
if !can(c, secureToPermission(common.SEC_SENSITIVE), common.PERM_LABEL_MODERATE) {
NewForbiddenError().Abort(c)
return
}
tx := mustBeginTx(c)
l, err := handleDeleteLabelState(tx, id)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.LabelDeleteEvent(l))
}
}
concludeRequest(c, resp, err)
}
func LabelI18nHandler(c *gin.Context) {
id, e := strconv.ParseInt(c.Param("id"), 10, 0)
if e != nil {
NewBadRequestError(errors.Wrap(e, "id expects int64")).Abort(c)
return
}
var i18ns []*models.LabelI18n
if c.Bind(&i18ns) != nil {
return
}
for _, x := range i18ns {
if common.StdLang(x.Language) == common.LANG_UNKNOWN {
NewBadRequestError(errors.Errorf("Unknown language %s", x.Language)).Abort(c)
return
}
}
tx := mustBeginTx(c)
resp, err := handleUpdateLabelI18n(c, tx, id, i18ns)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.LabelUpdateEvent(&resp.Label))
}
concludeRequest(c, resp, err)
}
func LabelAddI18nHandler(c *gin.Context) {
uid := c.Param("uid")
var r *AddLabelI18nRequest
if c.Bind(&r) != nil {
return
}
user := c.MustGet("USER").(*models.User)
tx := mustBeginTx(c)
label, err := handleAddLabelI18n(c, tx, uid, r, user)
mustConcludeTx(tx, err)
if err == nil {
emitEvents(c, events.LabelUpdateEvent(label))
}
concludeRequest(c, nil, err)
}
// Handlers Logic
func handleCollectionsList(cp utils.ContextProvider, exec boil.Executor, r CollectionsRequest) (*CollectionsResponse, *HttpError) {
mods := make([]qm.QueryMod, 0)
appendPermissionsMods(cp, &mods)
// filters
if err := appendIDsFilterMods(&mods, r.IDsFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendUIDsFilterMods(&mods, r.UIDsFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendContentTypesFilterMods(&mods, r.ContentTypesFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendDateRangeFilterMods(&mods, r.DateRangeFilter,
"(coalesce(properties->>'film_date', properties->>'start_date', created_at::text))::date"); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendSecureFilterMods(&mods, r.SecureFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendSearchTermFilterMods(exec, &mods, r.SearchTermFilter, SEARCH_IN_COLLECTIONS); err != nil {
return nil, NewBadRequestError(err)
}
appendPublishedFilterMods(&mods, r.PublishedFilter)
// count query
var total int64
countMods := append([]qm.QueryMod{qm.Select("count(DISTINCT id)")}, mods...)
err := models.Collections(countMods...).QueryRow(exec).Scan(&total)
if err != nil {
return nil, NewInternalError(err)
}
if total == 0 {
return NewCollectionsResponse(), nil
}
// order, limit, offset
if err = appendListMods(&mods, r.ListRequest); err != nil {
return nil, NewBadRequestError(err)
}
// Eager loading
mods = append(mods, qm.Load("CollectionI18ns"))
// data query
collections, err := models.Collections(mods...).All(exec)
if err != nil {
return nil, NewInternalError(err)
}
// i18n
data := make([]*Collection, len(collections))
for i, c := range collections {
x := &Collection{Collection: *c}
data[i] = x
x.I18n = make(map[string]*models.CollectionI18n, len(c.R.CollectionI18ns))
for _, i18n := range c.R.CollectionI18ns {
x.I18n[i18n.Language] = i18n
}
}
return &CollectionsResponse{
ListResponse: ListResponse{Total: total},
Collections: data,
}, nil
}
func handleCreateCollection(cp utils.ContextProvider, exec boil.Executor, c Collection) (*Collection, *HttpError) {
// check object level permissions
if !can(cp, secureToPermission(c.Secure), common.PERM_WRITE) {
return nil, NewForbiddenError()
}
// unmarshal properties
props := make(map[string]interface{})
if c.Properties.Valid {
err := json.Unmarshal(c.Properties.JSON, &props)
if err != nil {
return nil, NewBadRequestError(errors.Wrap(err, "json.Unmarshal properties"))
}
}
// create collection in DB
ct := common.CONTENT_TYPE_REGISTRY.ByID[c.TypeID].Name
collection, err := CreateCollection(exec, ct, props)
if err != nil {
return nil, NewInternalError(err)
}
// save i18n
for _, v := range c.I18n {
err := collection.AddCollectionI18ns(exec, true, v)
if err != nil {
return nil, NewInternalError(err)
}
}
return handleGetCollection(cp, exec, collection.ID)
}
func handleGetCollection(cp utils.ContextProvider, exec boil.Executor, id int64) (*Collection, *HttpError) {
collection, err := models.Collections(qm.Where("id = ?", id), qm.Load("CollectionI18ns")).
One(exec)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(collection.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
// i18n
x := &Collection{Collection: *collection}
x.I18n = make(map[string]*models.CollectionI18n, len(collection.R.CollectionI18ns))
for _, i18n := range collection.R.CollectionI18ns {
x.I18n[i18n.Language] = i18n
}
return x, nil
}
func handleUpdateCollection(cp utils.ContextProvider, exec boil.Executor, c *PartialCollection) (*Collection, *HttpError) {
collection, err := models.FindCollection(exec, c.ID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(collection.Secure), common.PERM_WRITE) {
return nil, NewForbiddenError()
}
// update entity attributes
if c.Secure.Valid {
collection.Secure = c.Secure.Int16
_, err = collection.Update(exec, boil.Whitelist("secure"))
if err != nil {
return nil, NewInternalError(err)
}
}
// update properties bag
if c.Properties.Valid {
var props map[string]interface{}
err = c.Properties.Unmarshal(&props)
if err != nil {
return nil, NewInternalError(err)
}
err = UpdateCollectionProperties(exec, collection, props)
if err != nil {
return nil, NewInternalError(err)
}
}
return handleGetCollection(cp, exec, c.ID)
}
func handleCollectionContentUnitsPosition(exec boil.Executor, id int64, orderType string) (*events.Event, *HttpError) {
collection, err := models.FindCollection(exec, id)
event := events.CollectionContentUnitsChangeEvent(collection)
all, err := models.CollectionsContentUnits(
qm.Load("ContentUnit"),
models.CollectionsContentUnitWhere.CollectionID.EQ(id),
).All(exec)
if err != nil {
return nil, NewInternalError(err)
}
if orderType == "name" {
sort.Slice(all, func(i, j int) bool {
nameI, err := strconv.Atoi(all[i].Name)
if err != nil {
nameI = len(all)
}
nameJ, err := strconv.Atoi(all[j].Name)
if err != nil {
nameJ = len(all)
}
return nameI < nameJ
})
} else {
sort.Slice(all, func(i, j int) bool {
fdI := filmDateByCUWithDefault(all[i].R.ContentUnit)
fdJ := filmDateByCUWithDefault(all[j].R.ContentUnit)
return fdI.Before(fdJ)
})
}
for i, ccu := range all {
ccu.Position = i
_, err = ccu.Update(exec, boil.Whitelist(models.CollectionsContentUnitColumns.Position))
if err != nil {
return nil, NewInternalError(err)
}
}
return &event, nil
}
func filmDateByCUWithDefault(cu *models.ContentUnit) time.Time {
fd0, _ := time.Parse("2006-01-02", "2000-01-02")
if !cu.Properties.Valid {
return fd0
}
var props map[string]interface{}
if err := cu.Properties.Unmarshal(&props); err != nil {
return fd0
}
fd, err := time.Parse("2006-01-02", props["film_date"].(string))
if err != nil {
fd, err = time.Parse(time.RFC3339, props["film_date"].(string))
if err != nil {
return fd0
}
}
return fd
}
func handleDeleteCollection(cp utils.ContextProvider, exec boil.Executor, id int64) (*models.Collection, *HttpError) {
collection, err := models.FindCollection(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(collection.Secure), common.PERM_WRITE) {
return nil, NewForbiddenError()
}
_, err = models.CollectionsContentUnits(qm.Where("collection_id = ?", id)).DeleteAll(exec)
if err != nil {
return nil, NewInternalError(err)
}
_, err = models.CollectionI18ns(qm.Where("collection_id = ?", id)).DeleteAll(exec)
if err != nil {
return nil, NewInternalError(err)
}
_, err = collection.Delete(exec)
if err != nil {
return nil, NewInternalError(err)
}
return collection, nil
}
func handleUpdateCollectionI18n(cp utils.ContextProvider, exec boil.Executor, id int64, i18ns []*models.CollectionI18n) (*Collection, *HttpError) {
collection, err := handleGetCollection(cp, exec, id)
if err != nil {
return nil, err
}
// check object level permissions
if !can(cp, secureToPermission(collection.Secure), common.PERM_I18N_WRITE) {
return nil, NewForbiddenError()
}
// Upsert all new i18ns
nI18n := make(map[string]*models.CollectionI18n, len(i18ns))
for _, i18n := range i18ns {
if strings.TrimSpace(i18n.Name.String) == "" {
return nil, NewBadRequestError(errors.New("name can't be empty"))
}
if len(i18n.Description.String) > 0 && strings.TrimSpace(i18n.Description.String) == "" {
return nil, NewBadRequestError(errors.New("description can't be white space only"))
}
i18n.CollectionID = id
nI18n[i18n.Language] = i18n
err := i18n.Upsert(exec, true,
[]string{"collection_id", "language"},
boil.Whitelist("name", "description"),
boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
}
// Delete old i18ns not in new i18ns
for k, v := range collection.I18n {
if _, ok := nI18n[k]; !ok {
_, err := v.Delete(exec)
if err != nil {
return nil, NewInternalError(err)
}
}
}
return handleGetCollection(cp, exec, id)
}
func handleCollectionActivate(cp utils.ContextProvider, exec boil.Executor, id int64) (*Collection, *HttpError) {
collection, err := models.FindCollection(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(collection.Secure), common.PERM_WRITE) {
return nil, NewForbiddenError()
}
var props = make(map[string]interface{})
if collection.Properties.Valid {
collection.Properties.Unmarshal(&props)
}
active, ok := props["active"]
if ok {
b, _ := active.(bool)
props["active"] = !b
} else {
props["active"] = false
}
pbytes, err := json.Marshal(props)
if err != nil {
return nil, NewInternalError(err)
}
collection.Properties = null.JSONFrom(pbytes)
_, err = collection.Update(exec, boil.Whitelist("properties"))
if err != nil {
return nil, NewInternalError(err)
}
return handleGetCollection(cp, exec, id)
}
func handleCollectionCCU(cp utils.ContextProvider, exec boil.Executor, id int64) ([]*CollectionContentUnit, *HttpError) {
collection, err := models.FindCollection(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(collection.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
ccus, err := models.CollectionsContentUnits(
qm.Where("collection_id = ?", id),
qm.OrderBy("position")).
All(exec)
if err != nil {
return nil, NewInternalError(err)
} else if len(ccus) == 0 {
return make([]*CollectionContentUnit, 0), nil
}
ids := make([]int64, len(ccus))
for i, ccu := range ccus {
ids[i] = ccu.ContentUnitID
}
cus, err := models.ContentUnits(
qm.Where("secure <= ?", allowedRead(cp)),
qm.WhereIn("id in ?", utils.ConvertArgsInt64(ids)...),
qm.Load("ContentUnitI18ns")).
All(exec)
if err != nil {
return nil, NewInternalError(err)
}
cusById := make(map[int64]*ContentUnit, len(cus))
for _, cu := range cus {
x := ContentUnit{ContentUnit: *cu}
x.I18n = make(map[string]*models.ContentUnitI18n, len(cu.R.ContentUnitI18ns))
for _, i18n := range cu.R.ContentUnitI18ns {
x.I18n[i18n.Language] = i18n
}
cusById[x.ID] = &x
}
data := make([]*CollectionContentUnit, 0)
for i := range ccus {
ccu := ccus[i]
if cu, ok := cusById[ccu.ContentUnitID]; ok {
data = append(data, &CollectionContentUnit{
Name: ccu.Name,
Position: ccu.Position,
ContentUnit: cu,
})
}
}
return data, nil
}
func handleCollectionAddCCU(cp utils.ContextProvider, exec boil.Executor, id int64, ccus []*models.CollectionsContentUnit) ([]events.Event, *HttpError) {
c, err := models.FindCollection(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(c.Secure), common.PERM_WRITE) {
return nil, NewForbiddenError()
}
evnts := make([]events.Event, 1)
evnts[0] = events.CollectionContentUnitsChangeEvent(c)
for _, ccu := range ccus {
cu, err := models.FindContentUnit(exec, ccu.ContentUnitID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewBadRequestError(errors.Errorf("Unknown content unit id %d", ccu.ContentUnitID))
} else {
return nil, NewInternalError(err)
}
}
exists, err := models.CollectionsContentUnits(
qm.Where("collection_id = ? AND content_unit_id = ?", id, ccu.ContentUnitID)).
Exists(exec)
if err != nil {
return nil, NewInternalError(err)
}
if exists {
return nil, NewBadRequestError(errors.New("Association already exists"))
}
err = c.AddCollectionsContentUnits(exec, true, ccu)
if err != nil {
return nil, NewInternalError(err)
}
if cu.Published && !c.Published {
c.Published = true
if _, err := c.Update(exec, boil.Whitelist("published")); err != nil {
return nil, NewInternalError(err)
}
evnts = append(evnts, events.CollectionPublishedChangeEvent(c))
}
}
return evnts, nil
}
func handleCollectionUpdateCCU(cp utils.ContextProvider, exec boil.Executor, id int64, ccu models.CollectionsContentUnit) (*events.Event, *HttpError) {
c, err := models.FindCollection(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(c.Secure), common.PERM_WRITE) {
return nil, NewForbiddenError()
}
mCCU, err := models.FindCollectionsContentUnit(exec, id, ccu.ContentUnitID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
mCCU.Name = ccu.Name
mCCU.Position = ccu.Position
_, err = mCCU.Update(exec, boil.Whitelist("name", "position"))
if err != nil {
return nil, NewInternalError(err)
}
e := events.CollectionContentUnitsChangeEvent(c)
return &e, nil
}
func handleCollectionRemoveCCU(cp utils.ContextProvider, exec boil.Executor, id int64, cuID int64) ([]events.Event, *HttpError) {
c, err := models.FindCollection(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(c.Secure), common.PERM_WRITE) {
return nil, NewForbiddenError()
}
ccu, err := models.FindCollectionsContentUnit(exec, id, cuID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
_, err = ccu.Delete(exec)
if err != nil {
return nil, NewInternalError(err)
}
var evnts = make([]events.Event, 1)
evnts[0] = events.CollectionContentUnitsChangeEvent(c)
if c.Published {
var hasPublishedCUs bool
query := `SELECT count(*) > 0
FROM collections_content_units ccu INNER JOIN content_units cu
ON ccu.content_unit_id = cu.id AND ccu.collection_id = $1 AND cu.published IS TRUE`
if err := queries.Raw(query, id).QueryRow(exec).Scan(&hasPublishedCUs); err != nil {
return nil, NewInternalError(err)
}
if !hasPublishedCUs {
c.Published = false
if _, err := c.Update(exec, boil.Whitelist("published")); err != nil {
return nil, NewInternalError(err)
}
evnts = append(evnts, events.CollectionPublishedChangeEvent(c))
}
}
return evnts, nil
}
func handleContentUnitsList(cp utils.ContextProvider, exec boil.Executor, r ContentUnitsRequest) (*ContentUnitsResponse, *HttpError) {
mods := make([]qm.QueryMod, 0)
appendPermissionsMods(cp, &mods)
// filters
if err := appendIDsFilterMods(&mods, r.IDsFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendUIDsFilterMods(&mods, r.UIDsFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendContentTypesFilterMods(&mods, r.ContentTypesFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendDateRangeFilterMods(&mods, r.DateRangeFilter,
"(coalesce(properties->>'capture_date', properties->>'film_date', created_at::text))::date"); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendSourcesFilterMods(exec, &mods, r.SourcesFilter); err != nil {
if e, ok := err.(*HttpError); ok {
return nil, e
} else {
NewInternalError(err)
}
}
if err := appendTagsFilterMods(exec, &mods, r.TagsFilter); err != nil {
return nil, NewInternalError(err)
}
searchIn := SEARCH_IN_CONTENT_UNITS
for _, t := range r.ContentTypesFilter.ContentTypes {
if t == common.CT_SOURCE {
searchIn = SEARCH_IN_CONTENT_UNITS_TYPE_SOURCE
}
}
if err := appendSearchTermFilterMods(exec, &mods, r.SearchTermFilter, searchIn); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendSecureFilterMods(&mods, r.SecureFilter); err != nil {
return nil, NewBadRequestError(err)
}
appendPublishedFilterMods(&mods, r.PublishedFilter)
appendOriginalFilterMods(&mods, r.OriginalLanguageFilter)
// count query
var total int64
countMods := append([]qm.QueryMod{qm.Select("count(DISTINCT id)")}, mods...)
err := models.ContentUnits(countMods...).QueryRow(exec).Scan(&total)
if err != nil {
return nil, NewInternalError(err)
}
if total == 0 {
return NewContentUnitsResponse(), nil
}
// order, limit, offset
if err = appendListMods(&mods, r.ListRequest); err != nil {
return nil, NewBadRequestError(err)
}
// Eager loading
// Eager loading
loadTables := []qm.QueryMod{qm.Load("ContentUnitI18ns")}
if r.WithCollections {
loadTables = append(loadTables,
qm.Load("CollectionsContentUnits"),
qm.Load("CollectionsContentUnits.Collection"),
qm.Load("CollectionsContentUnits.Collection.CollectionI18ns"),
)
}
mods = append(mods, loadTables...)
// data query
units, err := models.ContentUnits(mods...).All(exec)
if err != nil {
return nil, NewInternalError(err)
}
// i18n
data := make([]*ContentUnit, len(units))
for i, cu := range units {
x := &ContentUnit{ContentUnit: *cu}
data[i] = x
x.I18n = make(map[string]*models.ContentUnitI18n, len(cu.R.ContentUnitI18ns))
for _, i18n := range cu.R.ContentUnitI18ns {
x.I18n[i18n.Language] = i18n
}
if cu.R.CollectionsContentUnits != nil {
x.Collections = make([]*Collection, len(cu.R.CollectionsContentUnits))
for i, ccu := range cu.R.CollectionsContentUnits {
c := &Collection{Collection: *ccu.R.Collection}
c.I18n = make(map[string]*models.CollectionI18n, len(ccu.R.Collection.R.CollectionI18ns))
for _, i18n := range ccu.R.Collection.R.CollectionI18ns {
c.I18n[i18n.Language] = i18n
}
x.Collections[i] = c
}
}
}
return &ContentUnitsResponse{
ListResponse: ListResponse{Total: total},
ContentUnits: data,
}, nil
}
func handleGetContentUnit(cp utils.ContextProvider, exec boil.Executor, id int64) (*ContentUnit, *HttpError) {
unit, err := models.ContentUnits(qm.Where("id = ?", id), qm.Load("ContentUnitI18ns")).One(exec)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
// i18n
x := &ContentUnit{ContentUnit: *unit}
x.I18n = make(map[string]*models.ContentUnitI18n, len(unit.R.ContentUnitI18ns))
for _, i18n := range unit.R.ContentUnitI18ns {
x.I18n[i18n.Language] = i18n
}
return x, nil
}
func handleCreateContentUnit(cp utils.ContextProvider, exec boil.Executor, cu ContentUnit) (*ContentUnit, *HttpError) {
// check object level permissions
if !can(cp, secureToPermission(cu.Secure), common.PERM_WRITE) {
return nil, NewForbiddenError()
}
// unmarshal properties
props := make(map[string]interface{})
if cu.Properties.Valid {
err := json.Unmarshal(cu.Properties.JSON, &props)
if err != nil {
return nil, NewBadRequestError(errors.Wrap(err, "json.Unmarshal properties"))
}
}
// create content_unit in DB
ct := common.CONTENT_TYPE_REGISTRY.ByID[cu.TypeID].Name
unit, err := CreateContentUnit(exec, ct, props)
if err != nil {
return nil, NewInternalError(err)
}
// save i18n
for _, v := range cu.I18n {
err := unit.AddContentUnitI18ns(exec, true, v)
if err != nil {
return nil, NewInternalError(err)
}
}
return handleGetContentUnit(cp, exec, unit.ID)
}
func handleUpdateContentUnit(cp utils.ContextProvider, exec boil.Executor, cu *PartialContentUnit) (*ContentUnit, *HttpError) {
unit, err := models.FindContentUnit(exec, cu.ID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_WRITE) {
return nil, NewForbiddenError()
}
if unit.TypeID == common.CONTENT_TYPE_REGISTRY.ByName[common.CT_SOURCE].ID {
return nil, NewBadRequestError(errors.Errorf("Unit type %s is close for changes", common.CT_SOURCE))
}
if cu.Secure.Valid {
unit.Secure = cu.Secure.Int16
_, err = unit.Update(exec, boil.Whitelist("secure"))
if err != nil {
return nil, NewInternalError(err)
}
}
if cu.TypeID.Valid {
canChangeFrom := false
canChangeTo := false
for _, n := range common.UNIT_CONTENT_TYPE_CAN_CHANGE {
if int64(cu.TypeID.Int16) == common.CONTENT_TYPE_REGISTRY.ByName[n].ID {
canChangeTo = true
}
if unit.TypeID == common.CONTENT_TYPE_REGISTRY.ByName[n].ID {
canChangeFrom = true
}
}
if !canChangeFrom || !canChangeTo || !isAdmin(cp) {
return nil, NewForbiddenError()
}
unit.TypeID = int64(cu.TypeID.Int16)
_, err = unit.Update(exec, boil.Whitelist("type_id"))
if err != nil {
return nil, NewInternalError(err)
}
}
// update properties bag
if cu.Properties.Valid {
var props map[string]interface{}
err = cu.Properties.Unmarshal(&props)
if err != nil {
return nil, NewInternalError(err)
}
err = UpdateContentUnitProperties(exec, unit, props)
if err != nil {
return nil, NewInternalError(err)
}
}
return handleGetContentUnit(cp, exec, cu.ID)
}
func handleContentUnitAutoname(exec boil.Executor, data *ContentUnitAutonameRequest) ([]*models.ContentUnitI18n, *HttpError) {
metadata := CITMetadata{CollectionUID: data.CollectionUID}
describer, err := GetCUDescriber(exec, &models.ContentUnit{TypeID: data.TypeID}, metadata)
if err != nil {
return nil, NewInternalError(err)
}
// describe
resp, err := describer.DescribeContentUnit(exec, &models.ContentUnit{ID: -1, TypeID: data.TypeID}, metadata)
if err != nil {
return nil, NewInternalError(err)
}
return resp, nil
}
func handleUpdateContentUnitI18n(cp utils.ContextProvider, exec boil.Executor, id int64, i18ns []*models.ContentUnitI18n) (*ContentUnit, *HttpError) {
unit, err := handleGetContentUnit(cp, exec, id)
if err != nil {
return nil, err
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_I18N_WRITE) {
return nil, NewForbiddenError()
}
if unit.TypeID == common.CONTENT_TYPE_REGISTRY.ByName[common.CT_SOURCE].ID {
return nil, NewBadRequestError(errors.Errorf("Unit type %s is close for changes", common.CT_SOURCE))
}
// Upsert all new i18ns
nI18n := make(map[string]*models.ContentUnitI18n, len(i18ns))
for _, i18n := range i18ns {
i18n.ContentUnitID = id
nI18n[i18n.Language] = i18n
err := i18n.Upsert(exec, true,
[]string{"content_unit_id", "language"},
boil.Whitelist("name", "description"),
boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
}
// Delete old i18ns not in new i18ns
for k, v := range unit.I18n {
if _, ok := nI18n[k]; !ok {
_, err := v.Delete(exec)
if err != nil {
return nil, NewInternalError(err)
}
}
}
return handleGetContentUnit(cp, exec, id)
}
func handleContentUnitFiles(cp utils.ContextProvider, exec boil.Executor, id int64) ([]*MFile, *HttpError) {
unit, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
files, err := models.Files(
qm.Where("secure <= ?", allowedRead(cp)),
qm.Where("content_unit_id = ?", id)).
All(exec)
if err != nil {
return nil, NewInternalError(err)
}
data := make([]*MFile, len(files))
for i, f := range files {
data[i] = NewMFile(f)
}
return data, nil
}
func handleContentUnitAddFiles(cp utils.ContextProvider, exec boil.Executor, id int64, fileIDs []int64) (*ContentUnit, []events.Event, *HttpError) {
unit, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil, NewNotFoundError()
} else {
return nil, nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_WRITE) {
return nil, nil, NewForbiddenError()
}
// fetch files
// With respect to write permissions (as we're about to modify them)
files, err := models.Files(
qm.Where("secure <= ?", allowedWrite(cp)),
qm.WhereIn("id in ?", utils.ConvertArgsInt64(fileIDs)...)).
All(exec)
if err != nil {
return nil, nil, NewInternalError(err)
}
if len(files) != len(fileIDs) {
return nil, nil, NewBadRequestError(errors.New("Couldn't find all files (permissions maybe ?)"))
}
// look through files, some might change indeed.
// collect a set of CU IDs whose files are gone. Any impact on published status ?
evnts := make([]events.Event, 0)
somePublished := false
changedIDs := make([]interface{}, 0)
possiblyEffectedCUs := make(map[int64]bool)
for i := range files {
f := files[i]
fCUID := f.ContentUnitID.Int64
if fCUID == id {
continue
} else {
if f.ContentUnitID.Valid {
possiblyEffectedCUs[fCUID] = possiblyEffectedCUs[fCUID] || f.Published
}
f.ContentUnitID = null.Int64From(id) // so we respond with it without re-fetch from db
}
changedIDs = append(changedIDs, f.ID)
evnts = append(evnts, events.FileUpdateEvent(f))
if f.Published {
somePublished = true
}
}
// actual update of files that needs to be changed
_, err = models.Files(qm.WhereIn("id in ?", changedIDs...)).
UpdateAll(exec, models.M{"content_unit_id": id})
if err != nil {
return nil, nil, NewInternalError(err)
}
// published status may change for host unit and it's related collections
impact, err := FileAddedUnitImpact(exec, somePublished, unit.ID)
if err != nil {
return nil, nil, NewInternalError(err)
}
evnts = append(evnts, impact.Events()...)
// published status may change for units we leave as well
for k, v := range possiblyEffectedCUs {
impact, err := FileLeftUnitImpact(exec, v, k)
if err != nil {
return nil, nil, NewInternalError(err)
}
evnts = append(evnts, impact.Events()...)
}
resp, herr := handleGetContentUnit(cp, exec, id)
return resp, evnts, herr
}
func handleContentUnitCCU(cp utils.ContextProvider, exec boil.Executor, id int64) ([]*CollectionContentUnit, *HttpError) {
unit, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
ccus, err := models.CollectionsContentUnits(qm.Where("content_unit_id = ?", id)).All(exec)
if err != nil {
return nil, NewInternalError(err)
} else if len(ccus) == 0 {
return make([]*CollectionContentUnit, 0), nil
}
ids := make([]int64, len(ccus))
for i, ccu := range ccus {
ids[i] = ccu.CollectionID
}
cs, err := models.Collections(
qm.Where("secure <= ?", allowedRead(cp)),
qm.WhereIn("id in ?", utils.ConvertArgsInt64(ids)...),
qm.Load("CollectionI18ns")).
All(exec)
if err != nil {
return nil, NewInternalError(err)
}
csById := make(map[int64]*Collection, len(cs))
for _, c := range cs {
x := Collection{Collection: *c}
x.I18n = make(map[string]*models.CollectionI18n, len(c.R.CollectionI18ns))
for _, i18n := range c.R.CollectionI18ns {
x.I18n[i18n.Language] = i18n
}
csById[x.ID] = &x
}
data := make([]*CollectionContentUnit, len(ccus))
for i, ccu := range ccus {
data[i] = &CollectionContentUnit{
Name: ccu.Name,
Position: ccu.Position,
Collection: csById[ccu.CollectionID],
}
}
return data, nil
}
func handleContentUnitCUD(cp utils.ContextProvider, exec boil.Executor, id int64) ([]*ContentUnitDerivation, *HttpError) {
unit, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
cuds, err := models.ContentUnitDerivations(qm.Where("source_id = ?", id)).All(exec)
if err != nil {
return nil, NewInternalError(err)
} else if len(cuds) == 0 {
return make([]*ContentUnitDerivation, 0), nil
}
ids := make([]int64, len(cuds))
for i := range cuds {
ids[i] = cuds[i].DerivedID
}
cus, err := models.ContentUnits(
qm.Where("secure <= ?", allowedRead(cp)),
qm.WhereIn("id in ?", utils.ConvertArgsInt64(ids)...),
qm.Load("ContentUnitI18ns")).
All(exec)
if err != nil {
return nil, NewInternalError(err)
}
cusById := make(map[int64]*ContentUnit, len(cus))
for _, cu := range cus {
x := ContentUnit{ContentUnit: *cu}
x.I18n = make(map[string]*models.ContentUnitI18n, len(cu.R.ContentUnitI18ns))
for _, i18n := range cu.R.ContentUnitI18ns {
x.I18n[i18n.Language] = i18n
}
cusById[x.ID] = &x
}
data := make([]*ContentUnitDerivation, len(cuds))
for i, cud := range cuds {
data[i] = &ContentUnitDerivation{
Derived: cusById[cud.DerivedID],
Name: cud.Name,
}
}
return data, nil
}
func handleContentUnitAddCUD(cp utils.ContextProvider, exec boil.Executor, id int64, cud models.ContentUnitDerivation) (*models.ContentUnit, *HttpError) {
cu, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(cu.Secure), common.PERM_WRITE) {
return nil, NewForbiddenError()
}
exists, err := models.ContentUnits(qm.Where("id = ?", cud.DerivedID)).Exists(exec)
if err != nil {
return nil, NewInternalError(err)
}
if !exists {
return nil, NewBadRequestError(errors.Errorf("Unknown content unit id %d", cud.DerivedID))
}
exists, err = models.ContentUnitDerivations(qm.Where("source_id = ? AND derived_id = ?", id, cud.DerivedID)).
Exists(exec)
if err != nil {
return nil, NewInternalError(err)
}
if exists {
return nil, NewBadRequestError(errors.New("Derivation already exists"))
}
err = cu.AddSourceContentUnitDerivations(exec, true, &cud)
if err != nil {
return nil, NewInternalError(err)
}
return cu, nil
}
func handleContentUnitUpdateCUD(cp utils.ContextProvider, exec boil.Executor, id int64, cud models.ContentUnitDerivation) (*models.ContentUnit, *HttpError) {
cu, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(cu.Secure), common.PERM_WRITE) {
return nil, NewForbiddenError()
}
mCUD, err := models.FindContentUnitDerivation(exec, id, cud.DerivedID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
mCUD.Name = cud.Name
_, err = mCUD.Update(exec, boil.Whitelist("name"))
if err != nil {
return nil, NewInternalError(err)
}
return cu, nil
}
func handleContentUnitRemoveCUD(cp utils.ContextProvider, exec boil.Executor, id int64, duID int64) (*models.ContentUnit, *HttpError) {
cu, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(cu.Secure), common.PERM_WRITE) {
return nil, NewForbiddenError()
}
cud, err := models.FindContentUnitDerivation(exec, id, duID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
_, err = cud.Delete(exec)
if err != nil {
return nil, NewInternalError(err)
}
return cu, nil
}
func handleContentUnitOrigins(cp utils.ContextProvider, exec boil.Executor, id int64) ([]*ContentUnitDerivation, *HttpError) {
unit, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
cuds, err := models.ContentUnitDerivations(qm.Where("derived_id = ?", id)).All(exec)
if err != nil {
return nil, NewInternalError(err)
} else if len(cuds) == 0 {
return make([]*ContentUnitDerivation, 0), nil
}
ids := make([]int64, len(cuds))
for i := range cuds {
ids[i] = cuds[i].SourceID
}
cus, err := models.ContentUnits(
qm.Where("secure <= ?", allowedRead(cp)),
qm.WhereIn("id in ?", utils.ConvertArgsInt64(ids)...),
qm.Load("ContentUnitI18ns")).
All(exec)
if err != nil {
return nil, NewInternalError(err)
}
cusById := make(map[int64]*ContentUnit, len(cus))
for _, cu := range cus {
x := ContentUnit{ContentUnit: *cu}
x.I18n = make(map[string]*models.ContentUnitI18n, len(cu.R.ContentUnitI18ns))
for _, i18n := range cu.R.ContentUnitI18ns {
x.I18n[i18n.Language] = i18n
}
cusById[x.ID] = &x
}
data := make([]*ContentUnitDerivation, len(cuds))
for i, cud := range cuds {
data[i] = &ContentUnitDerivation{
Name: cud.Name,
Source: cusById[cud.SourceID],
}
}
return data, nil
}
func handleGetContentUnitSources(cp utils.ContextProvider, exec boil.Executor, id int64) ([]*Source, *HttpError) {
unit, err := models.ContentUnits(
qm.Where("id = ?", id),
qm.Load("Sources"),
qm.Load("Sources.SourceI18ns")).
One(exec)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
data := make([]*Source, len(unit.R.Sources))
for i, source := range unit.R.Sources {
x := &Source{Source: *source}
x.I18n = make(map[string]*models.SourceI18n, len(source.R.SourceI18ns))
for _, i18n := range source.R.SourceI18ns {
x.I18n[i18n.Language] = i18n
}
data[i] = x
}
return data, nil
}
func handleContentUnitAddSource(cp utils.ContextProvider, exec boil.Executor, id int64, sourceID int64) (*models.ContentUnit, *HttpError) {
cu, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(cu.Secure), common.PERM_METADATA_WRITE) {
return nil, NewForbiddenError()
}
source, err := models.FindSource(exec, sourceID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewBadRequestError(errors.Errorf("Unknown source id %d", sourceID))
} else {
return nil, NewInternalError(err)
}
}
var count int64
err = queries.Raw(
`SELECT COUNT(1) FROM content_units_sources WHERE content_unit_id=$1 AND source_id=$2`,
cu.ID, source.ID).
QueryRow(exec).
Scan(&count)
if err != nil {
return nil, NewInternalError(err)
}
if count > 0 {
return nil, nil // noop
}
err = cu.AddSources(exec, false, source)
if err != nil {
return nil, NewInternalError(err)
}
return cu, nil
}
func handleContentUnitRemoveSource(cp utils.ContextProvider, exec boil.Executor, id int64, sourceID int64) (*models.ContentUnit, *HttpError) {
cu, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(cu.Secure), common.PERM_METADATA_WRITE) {
return nil, NewForbiddenError()
}
source, err := models.FindSource(exec, sourceID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewBadRequestError(errors.Errorf("Unknown source id %d", sourceID))
} else {
return nil, NewInternalError(err)
}
}
err = cu.RemoveSources(exec, source)
if err != nil {
return nil, NewInternalError(err)
}
return cu, nil
}
func handleGetContentUnitTags(cp utils.ContextProvider, exec boil.Executor, id int64) ([]*Tag, *HttpError) {
unit, err := models.ContentUnits(
qm.Where("id = ?", id),
qm.Load("Tags"),
qm.Load("Tags.TagI18ns")).
One(exec)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
data := make([]*Tag, len(unit.R.Tags))
for i, tag := range unit.R.Tags {
x := &Tag{Tag: *tag}
x.I18n = make(map[string]*models.TagI18n, len(tag.R.TagI18ns))
for _, i18n := range tag.R.TagI18ns {
x.I18n[i18n.Language] = i18n
}
data[i] = x
}
return data, nil
}
func handleContentUnitAddTag(cp utils.ContextProvider, exec boil.Executor, id int64, tagID int64) (*models.ContentUnit, *HttpError) {
cu, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(cu.Secure), common.PERM_METADATA_WRITE) {
return nil, NewForbiddenError()
}
tag, err := models.FindTag(exec, tagID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewBadRequestError(errors.Errorf("Unknown tag id %d", tagID))
} else {
return nil, NewInternalError(err)
}
}
var count int64
err = queries.Raw(
`SELECT COUNT(1) FROM content_units_tags WHERE content_unit_id=$1 AND tag_id=$2`,
cu.ID, tag.ID).
QueryRow(exec).
Scan(&count)
if err != nil {
return nil, NewInternalError(err)
}
if count > 0 {
return nil, nil // noop
}
err = cu.AddTags(exec, false, tag)
if err != nil {
return nil, NewInternalError(err)
}
return cu, nil
}
func handleContentUnitRemoveTag(cp utils.ContextProvider, exec boil.Executor, id int64, tagID int64) (*models.ContentUnit, *HttpError) {
cu, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(cu.Secure), common.PERM_METADATA_WRITE) {
return nil, NewForbiddenError()
}
tag, err := models.FindTag(exec, tagID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewBadRequestError(errors.Errorf("Unknown tag id %d", tagID))
} else {
return nil, NewInternalError(err)
}
}
err = cu.RemoveTags(exec, tag)
if err != nil {
return nil, NewInternalError(err)
}
return cu, nil
}
func handleGetContentUnitPersons(cp utils.ContextProvider, exec boil.Executor, id int64) ([]*ContentUnitPerson, *HttpError) {
unit, err := models.ContentUnits(
qm.Where("id = ?", id),
qm.Load("ContentUnitsPersons"),
qm.Load("ContentUnitsPersons.Person"),
qm.Load("ContentUnitsPersons.Person.PersonI18ns")).
One(exec)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
data := make([]*ContentUnitPerson, len(unit.R.ContentUnitsPersons))
for i, cup := range unit.R.ContentUnitsPersons {
p := &Person{Person: *cup.R.Person}
p.I18n = make(map[string]*models.PersonI18n, len(cup.R.Person.R.PersonI18ns))
for _, i18n := range cup.R.Person.R.PersonI18ns {
p.I18n[i18n.Language] = i18n
}
data[i] = &ContentUnitPerson{Person: p, RoleID: cup.RoleID}
}
return data, nil
}
func handleContentUnitAddPerson(cp utils.ContextProvider, exec boil.Executor, id int64, cup models.ContentUnitsPerson) (*models.ContentUnit, *HttpError) {
cu, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(cu.Secure), common.PERM_METADATA_WRITE) {
return nil, NewForbiddenError()
}
exists, err := models.PersonExists(exec, cup.PersonID)
if err != nil {
return nil, NewInternalError(err)
}
if !exists {
return nil, NewBadRequestError(errors.Errorf("Unknown person id %d", cup.PersonID))
}
exists, err = models.ContentRoleTypeExists(exec, cup.RoleID)
if err != nil {
return nil, NewInternalError(err)
}
if !exists {
return nil, NewBadRequestError(errors.Errorf("Unknown role id %d", cup.RoleID))
}
existingCUP, err := models.FindContentUnitsPerson(exec, id, cup.PersonID)
if err != nil {
if err == sql.ErrNoRows {
// create new
cup.ContentUnitID = id
err = cup.Insert(exec, boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
return cu, nil
} else {
return nil, NewInternalError(err)
}
}
// update role
existingCUP.RoleID = cup.RoleID
_, err = existingCUP.Update(exec, boil.Whitelist("role_id"))
if err != nil {
return nil, NewInternalError(err)
}
return cu, nil
}
func handleContentUnitRemovePerson(cp utils.ContextProvider, exec boil.Executor, id int64, personID int64) (*models.ContentUnit, *HttpError) {
cu, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(cu.Secure), common.PERM_METADATA_WRITE) {
return nil, NewForbiddenError()
}
cup, err := models.FindContentUnitsPerson(exec, id, personID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
_, err = cup.Delete(exec)
if err != nil {
return nil, NewInternalError(err)
}
return cu, nil
}
func handleGetContentUnitPublishers(cp utils.ContextProvider, exec boil.Executor, id int64) ([]*Publisher, *HttpError) {
unit, err := models.ContentUnits(
qm.Where("id = ?", id),
qm.Load("Publishers"),
qm.Load("Publishers.PublisherI18ns")).
One(exec)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
data := make([]*Publisher, len(unit.R.Publishers))
for i, publisher := range unit.R.Publishers {
p := &Publisher{Publisher: *publisher}
p.I18n = make(map[string]*models.PublisherI18n, len(publisher.R.PublisherI18ns))
for _, i18n := range publisher.R.PublisherI18ns {
p.I18n[i18n.Language] = i18n
}
data[i] = p
}
return data, nil
}
func handleContentUnitAddPublisher(cp utils.ContextProvider, exec boil.Executor, id int64, publisherID int64) (*models.ContentUnit, *HttpError) {
cu, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(cu.Secure), common.PERM_METADATA_WRITE) {
return nil, NewForbiddenError()
}
publisher, err := models.FindPublisher(exec, publisherID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewBadRequestError(errors.Errorf("Unknown publisher id %d", publisherID))
} else {
return nil, NewInternalError(err)
}
}
var count int64
err = queries.Raw(
`SELECT COUNT(1) FROM content_units_publishers WHERE content_unit_id=$1 AND publisher_id=$2`,
cu.ID, publisher.ID).
QueryRow(exec).
Scan(&count)
if err != nil {
return nil, NewInternalError(err)
}
if count > 0 {
return nil, nil // noop
}
err = cu.AddPublishers(exec, false, publisher)
if err != nil {
return nil, NewInternalError(err)
}
return cu, nil
}
func handleContentUnitRemovePublisher(cp utils.ContextProvider, exec boil.Executor, id int64, publisherID int64) (*models.ContentUnit, *HttpError) {
cu, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(cu.Secure), common.PERM_METADATA_WRITE) {
return nil, NewForbiddenError()
}
publisher, err := models.FindPublisher(exec, publisherID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewBadRequestError(errors.Errorf("Unknown publisher id %d", publisherID))
} else {
return nil, NewInternalError(err)
}
}
err = cu.RemovePublishers(exec, publisher)
if err != nil {
return nil, NewInternalError(err)
}
return cu, nil
}
func handleContentUnitMerge(cp utils.ContextProvider, exec boil.Executor, id int64, cuIDs []int64) (*ContentUnit, []events.Event, *HttpError) {
unit, err := models.FindContentUnit(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil, NewNotFoundError()
} else {
return nil, nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(unit.Secure), common.PERM_WRITE) {
return nil, nil, NewForbiddenError()
}
// fetch units to be merged
// With respect to write permissions (as we're about to modify them)
units, err := models.ContentUnits(
qm.Where("secure <= ?", allowedWrite(cp)),
qm.WhereIn("id in ?", utils.ConvertArgsInt64(cuIDs)...),
qm.Load("Files"),
qm.Load("DerivedContentUnitDerivations"),
qm.Load("SourceContentUnitDerivations")).
All(exec)
if err != nil {
return nil, nil, NewInternalError(err)
}
if len(units) != len(cuIDs) {
return nil, nil, NewBadRequestError(errors.New("Couldn't find all units (permissions maybe ?)"))
}
// for each merged unit we:
// 1. move all it's files to host unit
// 2. move all derivations to host unit
// 3. remove unit
evnts := make([]events.Event, 0)
somePublished := false
cuDerivativesChange := false
for i := range units {
cu := units[i]
log.Infof("Merging CU %d into CU %d", cu.ID, unit.ID)
if cu.Published {
somePublished = true
}
// move files
_, err := cu.R.Files.UpdateAll(exec, models.M{"content_unit_id": unit.ID})
if err != nil {
return nil, nil, NewInternalError(err)
}
for i := range cu.R.Files {
evnts = append(evnts, events.FileUpdateEvent(cu.R.Files[i]))
}
// move derivations
_, err = cu.R.DerivedContentUnitDerivations.UpdateAll(exec, models.M{"derived_id": unit.ID})
if err != nil {
return nil, nil, NewInternalError(err)
}
for i := range cu.R.DerivedContentUnitDerivations {
sourceCU, err := cu.R.DerivedContentUnitDerivations[i].Source().One(exec)
if err != nil {
return nil, nil, NewInternalError(err)
}
evnts = append(evnts, events.ContentUnitDerivativesChangeEvent(sourceCU))
}
_, err = cu.R.SourceContentUnitDerivations.UpdateAll(exec, models.M{"source_id": unit.ID})
if err != nil {
return nil, nil, NewInternalError(err)
}
if len(cu.R.SourceContentUnitDerivations) > 0 {
cuDerivativesChange = true
}
// remove unit
err = DeleteContentUnit(exec, cu)
if err != nil {
return nil, nil, NewInternalError(err)
}
evnts = append(evnts, events.ContentUnitDeleteEvent(cu))
}
if cuDerivativesChange {
evnts = append(evnts, events.ContentUnitDerivativesChangeEvent(unit))
}
// published status may change for host unit and it's related collections
impact, err := FileAddedUnitImpact(exec, somePublished, unit.ID)
if err != nil {
return nil, nil, NewInternalError(err)
}
evnts = append(evnts, impact.Events()...)
resp, herr := handleGetContentUnit(cp, exec, id)
return resp, evnts, herr
}
func handleFilesList(cp utils.ContextProvider, exec boil.Executor, r FilesRequest) (*FilesResponse, *HttpError) {
mods := make([]qm.QueryMod, 0)
appendPermissionsMods(cp, &mods)
// filters
if err := appendIDsFilterMods(&mods, r.IDsFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendUIDsFilterMods(&mods, r.UIDsFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendSHA1sFilterMods(&mods, r.SHA1sFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendDateRangeFilterMods(&mods, r.DateRangeFilter, "file_created_at"); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendSecureFilterMods(&mods, r.SecureFilter); err != nil {
return nil, NewBadRequestError(err)
}
appendPublishedFilterMods(&mods, r.PublishedFilter)
if err := appendSearchTermFilterMods(exec, &mods, r.SearchTermFilter, SEARCH_IN_FILES); err != nil {
return nil, NewBadRequestError(err)
}
/*if r.Query != "" {
mods = append(mods, qm.Where("name ~ ?", r.Query),
qm.Or("uid ~ ?", r.Query),
qm.Or("id::TEXT ~ ?", r.Query))
}*/
// count query
var total int64
countMods := append([]qm.QueryMod{qm.Select("count(DISTINCT id)")}, mods...)
err := models.Files(countMods...).QueryRow(exec).Scan(&total)
if err != nil {
return nil, NewInternalError(err)
}
if total == 0 {
return NewFilesResponse(), nil
}
// order, limit, offset
if err = appendListMods(&mods, r.ListRequest); err != nil {
return nil, NewBadRequestError(err)
}
// data query
files, err := models.Files(mods...).All(exec)
if err != nil {
return nil, NewInternalError(err)
}
data := make([]*MFile, len(files))
for i, f := range files {
data[i] = NewMFile(f)
}
return &FilesResponse{
ListResponse: ListResponse{Total: total},
Files: data,
}, nil
}
func handleGetFile(cp utils.ContextProvider, exec boil.Executor, id int64) (*MFile, *HttpError) {
file, err := models.FindFile(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(file.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
return NewMFile(file), nil
}
func handleUpdateFile(cp utils.ContextProvider, exec boil.Executor, f *PartialFile) (*MFile, []events.Event, *HttpError) {
file, err := models.FindFile(exec, f.ID)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil, NewNotFoundError()
} else {
return nil, nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(file.Secure), common.PERM_WRITE) {
return nil, nil, NewForbiddenError()
}
evnts := make([]events.Event, 0)
if f.Type.Valid {
file.Type = f.Type.String
}
if f.SubType.Valid {
file.SubType = f.SubType.String
}
if f.MimeType.Valid {
file.MimeType = f.MimeType
}
if f.Language.Valid {
file.Language = f.Language
}
if f.ParentID.Valid {
file.ParentID = f.ParentID
}
if f.Secure.Valid {
file.Secure = f.Secure.Int16
}
prevCUID := file.ContentUnitID.Int64
if f.ContentUnitID.Valid {
file.ContentUnitID = f.ContentUnitID
}
_, err = file.Update(exec, boil.Infer())
if err != nil {
return nil, nil, NewInternalError(err)
}
evnts = append(evnts, events.FileUpdateEvent(file))
// We might be leaving some unit and joining another.
// What should be the impact of their published status ?
// The unit we're joining
if f.ContentUnitID.Valid {
impact, err := FileAddedUnitImpact(exec, file.Published, f.ContentUnitID.Int64)
if err != nil {
return nil, nil, NewInternalError(err)
}
evnts = append(evnts, impact.Events()...)
}
// The unit we're leaving
if prevCUID != 0 {
impact, err := FileLeftUnitImpact(exec, file.Published, prevCUID)
if err != nil {
return nil, nil, NewInternalError(err)
}
evnts = append(evnts, impact.Events()...)
}
resp, herr := handleGetFile(cp, exec, f.ID)
return resp, evnts, herr
}
func handleFileStorages(cp utils.ContextProvider, exec boil.Executor, id int64) ([]*Storage, *HttpError) {
file, err := models.FindFile(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// check object level permissions
if !can(cp, secureToPermission(file.Secure), common.PERM_READ) {
return nil, NewForbiddenError()
}
storages, err := models.Storages(
qm.InnerJoin("files_storages fs on fs.storage_id=id and fs.file_id = ?", id)).
All(exec)
if err != nil {
return nil, NewInternalError(err)
}
data := make([]*Storage, len(storages))
for i := range storages {
data[i] = &Storage{Storage: *storages[i]}
}
return data, nil
}
func handleOperationsList(exec boil.Executor, r OperationsRequest) (*OperationsResponse, *HttpError) {
mods := make([]qm.QueryMod, 0)
// filters
if err := appendDateRangeFilterMods(&mods, r.DateRangeFilter, "created_at"); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendOperationTypesFilterMods(&mods, r.OperationTypesFilter); err != nil {
return nil, NewBadRequestError(err)
}
// count query
var total int64
countMods := append([]qm.QueryMod{qm.Select("count(DISTINCT id)")}, mods...)
err := models.Operations(countMods...).QueryRow(exec).Scan(&total)
if err != nil {
return nil, NewInternalError(err)
}
if total == 0 {
return NewOperationsResponse(), nil
}
// order, limit, offset
if err = appendListMods(&mods, r.ListRequest); err != nil {
return nil, NewBadRequestError(err)
}
// data query
data, err := models.Operations(mods...).All(exec)
if err != nil {
return nil, NewInternalError(err)
}
return &OperationsResponse{
ListResponse: ListResponse{Total: total},
Operations: data,
}, nil
}
func handleOperationItem(exec boil.Executor, id int64) (*models.Operation, *HttpError) {
operation, err := models.FindOperation(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
return operation, nil
}
func handleOperationFiles(cp utils.ContextProvider, exec boil.Executor, id int64) ([]*MFile, *HttpError) {
ok, err := models.OperationExists(exec, id)
if err != nil {
return nil, NewInternalError(err)
}
if !ok {
return nil, NewNotFoundError()
}
files, err := models.Files(
qm.InnerJoin("files_operations fo on fo.file_id=id and fo.operation_id = ? and secure <= ?",
id, allowedRead(cp))).
All(exec)
if err != nil {
return nil, NewInternalError(err)
}
data := make([]*MFile, len(files))
for i, f := range files {
data[i] = NewMFile(f)
}
return data, nil
}
func handleGetSources(exec boil.Executor, r SourcesRequest) (*SourcesResponse, *HttpError) {
mods := make([]qm.QueryMod, 0)
// count query
var total int64
countMods := append([]qm.QueryMod{qm.Select("count(DISTINCT id)")}, mods...)
err := models.Sources(countMods...).QueryRow(exec).Scan(&total)
if err != nil {
return nil, NewInternalError(err)
}
if total == 0 {
return NewSourcesResponse(), nil
}
// order, limit, offset
if err = appendListMods(&mods, r.ListRequest); err != nil {
return nil, NewBadRequestError(err)
}
// Eager loading
mods = append(mods, qm.Load("SourceI18ns"))
// data query
sources, err := models.Sources(mods...).All(exec)
if err != nil {
return nil, NewInternalError(err)
}
// i18n
data := make([]*Source, len(sources))
for i, s := range sources {
x := &Source{Source: *s}
data[i] = x
x.I18n = make(map[string]*models.SourceI18n, len(s.R.SourceI18ns))
for _, i18n := range s.R.SourceI18ns {
x.I18n[i18n.Language] = i18n
}
}
return &SourcesResponse{
ListResponse: ListResponse{Total: total},
Sources: data,
}, nil
}
func handleCreateSource(exec boil.Executor, r CreateSourceRequest) (*Source, *HttpError) {
s := r.Source
// check pattern unique constraint
if s.Pattern.Valid {
ok, err := models.Sources(qm.Where("pattern = ?", s.Pattern.String)).Exists(exec)
if err != nil {
return nil, NewInternalError(err)
}
if ok {
err = errors.Errorf("Pattern already in use: %s", s.Pattern.String)
return nil, NewBadRequestError(err)
}
}
// make sure parent source exists if given
if s.ParentID.Valid {
ok, err := models.Sources(qm.Where("id = ?", s.ParentID.Int64)).Exists(exec)
if err != nil {
return nil, NewInternalError(err)
}
if !ok {
err = errors.Errorf("Unknown parent source: %d", s.ParentID.Int64)
return nil, NewBadRequestError(err)
}
}
// make sure author exists if given
if r.AuthorID.Valid {
ok, err := models.Authors(qm.Where("id = ?", r.AuthorID.Int64)).Exists(exec)
if err != nil {
return nil, NewInternalError(err)
}
if !ok {
err = errors.Errorf("Unknown author: %d", r.AuthorID.Int64)
return nil, NewBadRequestError(err)
}
}
// save source to DB
uid, err := getUniqSourceAndCUUID(exec, 0)
if err != nil {
return nil, NewInternalError(err)
}
s.UID = uid
err = s.Source.Insert(exec, boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
// save i18n
for _, v := range s.I18n {
err := s.AddSourceI18ns(exec, true, v)
if err != nil {
return nil, NewInternalError(err)
}
}
// save author
if r.AuthorID.Valid {
err := s.Source.AddAuthors(exec, false, &models.Author{ID: r.AuthorID.Int64})
if err != nil {
return nil, NewInternalError(err)
}
}
// create CU type source
props, _ := json.Marshal(map[string]string{"source_id": s.UID})
cu := &models.ContentUnit{
UID: s.UID,
TypeID: common.CONTENT_TYPE_REGISTRY.ByName[common.CT_SOURCE].ID,
Published: true,
Properties: null.JSONFrom(props),
}
err = cu.Insert(exec, boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
return handleGetSource(exec, s.ID)
}
func getUniqSourceAndCUUID(exec boil.Executor, attempts int64) (string, error) {
if attempts > 10 {
return "", errors.New("Too match attempts of find unique UID for CU and Source")
}
uid, err := GetFreeUID(exec, new(SourceUIDChecker))
if err != nil {
return "", NewInternalError(err)
}
hasCU, err := models.ContentUnits(qm.Where("uid = ?", uid)).Exists(exec)
if err != nil {
return "", NewInternalError(err)
}
if hasCU {
attempts++
return getUniqSourceAndCUUID(exec, attempts)
}
return uid, err
}
func handleGetSource(exec boil.Executor, id int64) (*Source, *HttpError) {
source, err := models.Sources(
qm.Where("id = ?", id),
qm.Load("SourceI18ns")).
One(exec)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// i18n
x := &Source{Source: *source}
x.I18n = make(map[string]*models.SourceI18n, len(source.R.SourceI18ns))
for _, i18n := range source.R.SourceI18ns {
x.I18n[i18n.Language] = i18n
}
return x, nil
}
func handleUpdateSource(exec boil.Executor, s *Source) (*Source, *HttpError) {
source, err := models.FindSource(exec, s.ID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
if s.TypeID != 0 { // to allow partial updates
source.TypeID = s.TypeID
}
if s.Position.Valid {
source.Position = s.Position
}
source.Pattern = s.Pattern
source.Description = s.Description
_, err = s.Update(exec, boil.Whitelist("pattern", "description", "type_id", "position"))
if err != nil {
return nil, NewInternalError(err)
}
return handleGetSource(exec, s.ID)
}
func handleUpdateSourceI18n(exec boil.Executor, id int64, i18ns []*models.SourceI18n) (*Source, *HttpError) {
source, err := handleGetSource(exec, id)
if err != nil {
return nil, err
}
// Upsert all new i18ns
nI18n := make(map[string]*models.SourceI18n, len(i18ns))
for _, i18n := range i18ns {
i18n.SourceID = id
nI18n[i18n.Language] = i18n
err := i18n.Upsert(exec, true,
[]string{"source_id", "language"},
boil.Whitelist("name", "description"),
boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
}
// Delete old i18ns not in new i18ns
for k, v := range source.I18n {
if _, ok := nI18n[k]; !ok {
_, err := v.Delete(exec)
if err != nil {
return nil, NewInternalError(err)
}
}
}
return handleGetSource(exec, id)
}
func handleGetTags(exec boil.Executor, r TagsRequest) (*TagsResponse, *HttpError) {
mods := make([]qm.QueryMod, 0)
// count query
var total int64
countMods := append([]qm.QueryMod{qm.Select("count(DISTINCT id)")}, mods...)
err := models.Tags(countMods...).QueryRow(exec).Scan(&total)
if err != nil {
return nil, NewInternalError(err)
}
if total == 0 {
return NewTagsResponse(), nil
}
// order, limit, offset
if err = appendListMods(&mods, r.ListRequest); err != nil {
return nil, NewBadRequestError(err)
}
// Eager loading
mods = append(mods, qm.Load("TagI18ns"))
// data query
tags, err := models.Tags(mods...).All(exec)
if err != nil {
return nil, NewInternalError(err)
}
// i18n
data := make([]*Tag, len(tags))
for i, t := range tags {
x := &Tag{Tag: *t}
data[i] = x
x.I18n = make(map[string]*models.TagI18n, len(t.R.TagI18ns))
for _, i18n := range t.R.TagI18ns {
x.I18n[i18n.Language] = i18n
}
}
return &TagsResponse{
ListResponse: ListResponse{Total: total},
Tags: data,
}, nil
}
func handleCreateTag(exec boil.Executor, t *Tag) (*Tag, *HttpError) {
// make sure parent tag exists if given
if t.ParentID.Valid {
ok, err := models.Tags(qm.Where("id = ?", t.ParentID.Int64)).Exists(exec)
if err != nil {
return nil, NewInternalError(err)
}
if !ok {
return nil, NewBadRequestError(errors.Errorf("Unknown parent tag %d", t.ParentID.Int64))
}
}
// save tag to DB
uid, err := GetFreeUID(exec, new(TagUIDChecker))
if err != nil {
return nil, NewInternalError(err)
}
t.UID = uid
err = t.Tag.Insert(exec, boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
// save i18n
for _, v := range t.I18n {
err := t.AddTagI18ns(exec, true, v)
if err != nil {
return nil, NewInternalError(err)
}
}
return handleGetTag(exec, t.ID)
}
func handleGetTag(exec boil.Executor, id int64) (*Tag, *HttpError) {
tag, err := models.Tags(
qm.Where("id = ?", id),
qm.Load("TagI18ns")).
One(exec)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// i18n
x := &Tag{Tag: *tag}
x.I18n = make(map[string]*models.TagI18n, len(tag.R.TagI18ns))
for _, i18n := range tag.R.TagI18ns {
x.I18n[i18n.Language] = i18n
}
return x, nil
}
func handleUpdateTag(exec boil.Executor, t *Tag) (*Tag, *HttpError) {
tag, err := models.FindTag(exec, t.ID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
tag.Pattern = t.Pattern
tag.Description = t.Description
tag.ParentID = t.ParentID
_, err = t.Update(exec, boil.Whitelist("pattern", "description", "parent_id"))
if err != nil {
return nil, NewInternalError(err)
}
return handleGetTag(exec, t.ID)
}
func handleUpdateTagI18n(exec boil.Executor, id int64, i18ns []*models.TagI18n) (*Tag, *HttpError) {
tag, err := handleGetTag(exec, id)
if err != nil {
return nil, err
}
// Upsert all new i18ns
nI18n := make(map[string]*models.TagI18n, len(i18ns))
for _, i18n := range i18ns {
i18n.TagID = id
nI18n[i18n.Language] = i18n
err := i18n.Upsert(exec, true,
[]string{"tag_id", "language"},
boil.Whitelist("label"),
boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
}
// Delete old i18ns not in new i18ns
for k, v := range tag.I18n {
if _, ok := nI18n[k]; !ok {
_, err := v.Delete(exec)
if err != nil {
return nil, NewInternalError(err)
}
}
}
return handleGetTag(exec, id)
}
func handlePersonsList(exec boil.Executor, r PersonsRequest) (*PersonsResponse, *HttpError) {
mods := make([]qm.QueryMod, 0)
// filters
if err := appendIDsFilterMods(&mods, r.IDsFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendUIDsFilterMods(&mods, r.UIDsFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendPatternsFilterMods(&mods, r.PatternsFilter); err != nil {
return nil, NewBadRequestError(err)
}
// count query
var total int64
countMods := append([]qm.QueryMod{qm.Select("count(DISTINCT id)")}, mods...)
err := models.Persons(countMods...).QueryRow(exec).Scan(&total)
if err != nil {
return nil, NewInternalError(err)
}
if total == 0 {
return NewPersonsResponse(), nil
}
// order, limit, offset
if err = appendListMods(&mods, r.ListRequest); err != nil {
return nil, NewBadRequestError(err)
}
// Eager loading
mods = append(mods, qm.Load("PersonI18ns"))
// data query
persons, err := models.Persons(mods...).All(exec)
if err != nil {
return nil, NewInternalError(err)
}
// i18n
data := make([]*Person, len(persons))
for i, pr := range persons {
x := &Person{Person: *pr}
data[i] = x
x.I18n = make(map[string]*models.PersonI18n, len(pr.R.PersonI18ns))
for _, i18n := range pr.R.PersonI18ns {
x.I18n[i18n.Language] = i18n
}
}
return &PersonsResponse{
ListResponse: ListResponse{Total: total},
Persons: data,
}, nil
}
func handleCreatePerson(exec boil.Executor, p *Person) (*Person, *HttpError) {
// save person to DB
uid, err := GetFreeUID(exec, new(PersonUIDChecker))
if err != nil {
return nil, NewInternalError(err)
}
p.UID = uid
err = p.Person.Insert(exec, boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
// save i18n
for _, v := range p.I18n {
err := p.AddPersonI18ns(exec, true, v)
if err != nil {
return nil, NewInternalError(err)
}
}
return handleGetPerson(exec, p.ID)
}
func handleUpdatePerson(exec boil.Executor, p *Person) (*Person, *HttpError) {
person, err := models.FindPerson(exec, p.ID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
person.Pattern = p.Pattern
_, err = p.Update(exec, boil.Whitelist("pattern"))
if err != nil {
return nil, NewInternalError(err)
}
return handleGetPerson(exec, p.ID)
}
func handleGetPerson(exec boil.Executor, id int64) (*Person, *HttpError) {
person, err := models.Persons(
qm.Where("id = ?", id),
qm.Load("PersonI18ns")).
One(exec)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// i18n
x := &Person{Person: *person}
x.I18n = make(map[string]*models.PersonI18n, len(person.R.PersonI18ns))
for _, i18n := range person.R.PersonI18ns {
x.I18n[i18n.Language] = i18n
}
return x, nil
}
func handleUpdatePersonI18n(exec boil.Executor, id int64, i18ns []*models.PersonI18n) (*Person, *HttpError) {
person, err := handleGetPerson(exec, id)
if err != nil {
return nil, err
}
// Upsert all new i18ns
nI18n := make(map[string]*models.PersonI18n, len(i18ns))
for _, i18n := range i18ns {
i18n.PersonID = id
nI18n[i18n.Language] = i18n
err := i18n.Upsert(exec, true,
[]string{"person_id", "language"},
boil.Whitelist("name", "description"),
boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
}
// Delete old i18ns not in new i18ns
for k, v := range person.I18n {
if _, ok := nI18n[k]; !ok {
_, err := v.Delete(exec)
if err != nil {
return nil, NewInternalError(err)
}
}
}
return handleGetPerson(exec, id)
}
func handleDeletePerson(exec boil.Executor, id int64) (*models.Person, *HttpError) {
person, err := models.FindPerson(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
_, err = models.ContentUnitsPersons(qm.Where("person_id = ?", id)).DeleteAll(exec)
if err != nil {
return nil, NewInternalError(err)
}
_, err = models.PersonI18ns(qm.Where("person_id = ?", id)).DeleteAll(exec)
if err != nil {
return nil, NewInternalError(err)
}
_, err = person.Delete(exec)
if err != nil {
return nil, NewInternalError(err)
}
return person, nil
}
func handleStoragesList(exec boil.Executor, r StoragesRequest) (*StoragesResponse, *HttpError) {
mods := make([]qm.QueryMod, 0)
// count query
var total int64
countMods := append([]qm.QueryMod{qm.Select("count(DISTINCT id)")}, mods...)
err := models.Storages(countMods...).QueryRow(exec).Scan(&total)
if err != nil {
return nil, NewInternalError(err)
}
if total == 0 {
return NewStoragesResponse(), nil
}
// order, limit, offset
if err = appendListMods(&mods, r.ListRequest); err != nil {
return nil, NewBadRequestError(err)
}
// data query
data, err := models.Storages(mods...).All(exec)
if err != nil {
return nil, NewInternalError(err)
}
return &StoragesResponse{
ListResponse: ListResponse{Total: total},
Storages: data,
}, nil
}
func handlePublishersList(exec boil.Executor, r PublishersRequest) (*PublishersResponse, *HttpError) {
mods := make([]qm.QueryMod, 0)
// filters
if err := appendIDsFilterMods(&mods, r.IDsFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendUIDsFilterMods(&mods, r.UIDsFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendPatternsFilterMods(&mods, r.PatternsFilter); err != nil {
return nil, NewBadRequestError(err)
}
// count query
var total int64
countMods := append([]qm.QueryMod{qm.Select("count(DISTINCT id)")}, mods...)
err := models.Publishers(countMods...).QueryRow(exec).Scan(&total)
if err != nil {
return nil, NewInternalError(err)
}
if total == 0 {
return NewPublishersResponse(), nil
}
// order, limit, offset
if err = appendListMods(&mods, r.ListRequest); err != nil {
return nil, NewBadRequestError(err)
}
// Eager loading
mods = append(mods, qm.Load("PublisherI18ns"))
// data query
publishers, err := models.Publishers(mods...).All(exec)
if err != nil {
return nil, NewInternalError(err)
}
// i18n
data := make([]*Publisher, len(publishers))
for i, pr := range publishers {
x := &Publisher{Publisher: *pr}
data[i] = x
x.I18n = make(map[string]*models.PublisherI18n, len(pr.R.PublisherI18ns))
for _, i18n := range pr.R.PublisherI18ns {
x.I18n[i18n.Language] = i18n
}
}
return &PublishersResponse{
ListResponse: ListResponse{Total: total},
Publishers: data,
}, nil
}
func handleCreatePublisher(exec boil.Executor, p *Publisher) (*Publisher, *HttpError) {
// save publisher to DB
uid, err := GetFreeUID(exec, new(PublisherUIDChecker))
if err != nil {
return nil, NewInternalError(err)
}
p.UID = uid
err = p.Publisher.Insert(exec, boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
// save i18n
for _, v := range p.I18n {
err := p.AddPublisherI18ns(exec, true, v)
if err != nil {
return nil, NewInternalError(err)
}
}
return handleGetPublisher(exec, p.ID)
}
func handleGetPublisher(exec boil.Executor, id int64) (*Publisher, *HttpError) {
publisher, err := models.Publishers(
qm.Where("id = ?", id),
qm.Load("PublisherI18ns")).
One(exec)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// i18n
x := &Publisher{Publisher: *publisher}
x.I18n = make(map[string]*models.PublisherI18n, len(publisher.R.PublisherI18ns))
for _, i18n := range publisher.R.PublisherI18ns {
x.I18n[i18n.Language] = i18n
}
return x, nil
}
func handleUpdatePublisher(exec boil.Executor, p *Publisher) (*Publisher, *HttpError) {
publisher, err := models.FindPublisher(exec, p.ID)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
publisher.Pattern = p.Pattern
_, err = p.Update(exec, boil.Whitelist("pattern"))
if err != nil {
return nil, NewInternalError(err)
}
return handleGetPublisher(exec, p.ID)
}
func handleUpdatePublisherI18n(exec boil.Executor, id int64, i18ns []*models.PublisherI18n) (*Publisher, *HttpError) {
publisher, err := handleGetPublisher(exec, id)
if err != nil {
return nil, err
}
// Upsert all new i18ns
nI18n := make(map[string]*models.PublisherI18n, len(i18ns))
for _, i18n := range i18ns {
i18n.PublisherID = id
nI18n[i18n.Language] = i18n
err := i18n.Upsert(exec, true,
[]string{"publisher_id", "language"},
boil.Whitelist("name", "description"),
boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
}
// Delete old i18ns not in new i18ns
for k, v := range publisher.I18n {
if _, ok := nI18n[k]; !ok {
_, err := v.Delete(exec)
if err != nil {
return nil, NewInternalError(err)
}
}
}
return handleGetPublisher(exec, id)
}
// Labels
func handleGetLabelList(cp utils.ContextProvider, exec boil.Executor, r *LabelsRequest) (*LabelsResponse, *HttpError) {
mods := []qm.QueryMod{
qm.InnerJoin("content_units cu on cu.id=content_unit_id and cu.secure <= ?", allowedSecure(cp, common.PERM_LABEL_READ)),
}
// filters
if err := appendIDsFilterMods(&mods, r.IDsFilter); err != nil {
return nil, NewBadRequestError(err)
}
if err := appendDateRangeFilterMods(&mods, r.DateRangeFilter, "created_at"); err != nil {
return nil, NewBadRequestError(err)
}
//append approve state filter
if r.ApproveState.Valid {
mods = append(mods, qm.Where("approve_state = ?", r.ApproveState))
}
// count query
var total int64
countMods := append([]qm.QueryMod{qm.Select("count(DISTINCT \"labels\".id)")}, mods...)
err := models.Labels(countMods...).QueryRow(exec).Scan(&total)
if err != nil {
return nil, NewInternalError(err)
}
if total == 0 {
return NewLabelsResponse(), nil
}
labels, err := models.Labels(qm.Load("LabelI18ns"),
qm.Load("LabelI18ns.User"),
qm.Load("Tags")).All(exec)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
// i18n
data := make([]*Label, len(labels))
for i, pr := range labels {
x := &Label{Label: *pr}
data[i] = x
x.I18n = make(map[string]*LabelI18n, len(pr.R.LabelI18ns))
for _, i18n := range pr.R.LabelI18ns {
x.I18n[i18n.Language] = &LabelI18n{LabelI18n: *i18n, Author: i18n.R.User}
}
}
return &LabelsResponse{
ListResponse: ListResponse{Total: total},
Labels: data,
}, nil
}
func handleCreateLabel(cp utils.ContextProvider, exec boil.Executor, r *CreateLabelRequest) (*models.Label, *HttpError) {
cu, err := models.ContentUnits(
qm.WhereIn("uid = ?", r.ContentUnit),
qm.Where("secure <= ?", allowedSecure(cp, common.PERM_LABEL_WRITE)),
).One(exec)
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
}
if err != nil {
return nil, NewInternalError(err)
}
label := &models.Label{
MediaType: r.MediaType,
ApproveState: common.APR_NONE,
ContentUnitID: cu.ID,
}
// save label to DB
uid, err := GetFreeUID(exec, new(LabelUIDChecker))
if err != nil {
return nil, NewInternalError(err)
}
label.UID = uid
if r.Properties.Valid {
label.Properties = r.Properties
}
err = label.Insert(exec, boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
user := cp.MustGet("USER").(*models.User)
// save i18n
for _, v := range r.I18n {
i18n := &models.LabelI18n{
Language: v.Language,
Name: v.Name,
UserID: null.Int64From(user.ID),
}
i18n.UserID = null.Int64From(user.ID)
err := label.AddLabelI18ns(exec, true, i18n)
if err != nil {
return nil, NewInternalError(err)
}
}
// save tags connection
tags, err := models.Tags(qm.WhereIn("uid IN ?", utils.ConvertArgsString(r.Tags)...)).All(exec)
if err != nil {
return nil, NewInternalError(err)
}
if err = label.AddTags(exec, false, tags...); err != nil {
return nil, NewInternalError(err)
}
return label, nil
}
func handleGetLabel(c utils.ContextProvider, exec boil.Executor, id int64, act string) (*LabelResponse, *HttpError) {
label, err := models.Labels(
qm.Where("id = ?", id),
qm.Load("LabelI18ns"),
qm.Load("Tags"),
qm.Load("ContentUnit"),
qm.Load("LabelI18ns.User")).
One(exec)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
if !can(c, secureToPermission(label.R.ContentUnit.Secure), act) {
return nil, NewForbiddenError()
}
// i18n
resp := &LabelResponse{Label: *label, ContentUnit: label.R.ContentUnit.UID}
resp.I18n = make(map[string]*LabelI18n, len(label.R.LabelI18ns))
for _, i18n := range label.R.LabelI18ns {
resp.I18n[i18n.Language] = &LabelI18n{LabelI18n: *i18n, Author: i18n.R.User}
}
resp.Tags = make([]string, len(label.R.Tags))
for i, t := range label.R.Tags {
resp.Tags[i] = t.UID
}
return resp, nil
}
func handleUpdateLabelState(exec boil.Executor, id int64, r UpdateApproveStateRequest) (*models.Label, *HttpError) {
label, err := models.FindLabel(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
label.ApproveState = r.ApproveState
_, err = label.Update(exec, boil.Whitelist("ApproveState"))
if err != nil {
return nil, NewInternalError(err)
}
return label, nil
}
func handleUpdateLabelI18n(c utils.ContextProvider, exec boil.Executor, id int64, i18ns []*models.LabelI18n) (*LabelResponse, *HttpError) {
label, err := handleGetLabel(c, exec, id, common.PERM_LABEL_I18N_WRITE)
if err != nil {
return nil, err
}
// Upsert all new i18ns
nI18n := make(map[string]*models.LabelI18n, len(i18ns))
for _, i18n := range i18ns {
i18n.LabelID = id
nI18n[i18n.Language] = i18n
err := i18n.Upsert(exec, true,
[]string{"label_id", "language"},
boil.Infer(),
boil.Infer())
if err != nil {
return nil, NewInternalError(err)
}
}
// Delete old i18ns not in new i18ns
for k, v := range label.I18n {
if _, ok := nI18n[k]; !ok {
_, err := v.Delete(exec)
if err != nil {
return nil, NewInternalError(err)
}
}
}
return handleGetLabel(c, exec, id, common.PERM_LABEL_I18N_WRITE)
}
func handleAddLabelI18n(cp utils.ContextProvider, exec boil.Executor, uid string, r *AddLabelI18nRequest, user *models.User) (*models.Label, *HttpError) {
if common.StdLang(r.I18n.Language) == common.LANG_UNKNOWN {
return nil, NewBadRequestError(errors.Errorf("Unknown language %s", r.I18n.Language))
}
label, err := models.Labels(
qm.Where("uid = ?", uid),
qm.InnerJoin("content_units cu on cu.id=content_unit_id and and cu.secure <= ?", allowedSecure(cp, common.PERM_LABEL_I18N_WRITE)),
).One(exec)
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
}
if err != nil {
return nil, NewInternalError(err)
}
has, err := models.LabelI18ns(qm.Where("label_id = ? AND language = ?", label.ID, r.I18n.Language)).Exists(exec)
if err != nil {
return nil, NewInternalError(err)
}
if has {
err = errors.Errorf("translation for label %s for language %s is contained", uid, r.I18n.Language)
return nil, NewBadRequestError(err)
}
i18n := &models.LabelI18n{
Language: r.I18n.Language,
Name: r.I18n.Name,
UserID: null.Int64From(user.ID),
}
if err := label.AddLabelI18ns(exec, true, i18n); err != nil {
return nil, NewInternalError(err)
}
return label, nil
}
func handleDeleteLabelState(exec boil.Executor, id int64) (*models.Label, *HttpError) {
label, err := models.FindLabel(exec, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, NewNotFoundError()
} else {
return nil, NewInternalError(err)
}
}
if _, err = label.Delete(exec); err != nil {
return nil, NewInternalError(err)
}
return label, nil
}
// Query Helpers
func appendListMods(mods *[]qm.QueryMod, r ListRequest) error {
// group by id to remove duplicates
*mods = append(*mods, qm.GroupBy("id"))
if r.OrderBy == "" {
*mods = append(*mods, qm.OrderBy("id desc"))
} else {
*mods = append(*mods, qm.OrderBy(r.OrderBy))
}
var limit, offset int
if r.StartIndex == 0 {
// pagination style
if r.PageSize == 0 {
limit = DEFAULT_PAGE_SIZE
} else {
limit = utils.Min(r.PageSize, MAX_PAGE_SIZE)
}
if r.PageNumber > 1 {
offset = (r.PageNumber - 1) * limit
}
} else {
// start & stop index style for "infinite" lists
offset = r.StartIndex - 1
if r.StopIndex == 0 {
limit = MAX_PAGE_SIZE
} else if r.StopIndex < r.StartIndex {
return errors.Errorf("Invalid range [%d-%d]", r.StartIndex, r.StopIndex)
} else {
limit = r.StopIndex - r.StartIndex + 1
}
}
*mods = append(*mods, qm.Limit(limit))
if offset != 0 {
*mods = append(*mods, qm.Offset(offset))
}
return nil
}
func appendPermissionsMods(cp utils.ContextProvider, mods *[]qm.QueryMod) {
*mods = append(*mods, qm.Where("secure <= ?", allowedRead(cp)))
}
func appendSearchTermFilterMods(exec boil.Executor, mods *[]qm.QueryMod, f SearchTermFilter, entityType int) error {
if f.Query == "" {
return nil
}
var whereParts []string
// id field - must be unsigned int
if id, err := strconv.ParseUint(f.Query, 10, 64); err == nil {
whereParts = append(whereParts, fmt.Sprintf("id = %d", id))
}
// uid field
if len(f.Query) == 8 {
whereParts = append(whereParts, fmt.Sprintf("uid = '%s'", f.Query))
}
switch entityType {
case SEARCH_IN_FILES:
// file name field
whereParts = append(whereParts, fmt.Sprintf("name ~~ '%%%s%%'", f.Query))
// file sha1
if len(f.Query) == 40 {
_, err := hex.DecodeString(f.Query) // make sure it's a hex string
if err == nil {
whereParts = append(whereParts, fmt.Sprintf("sha1 = '\\x%s'", f.Query))
}
}
case SEARCH_IN_CONTENT_UNITS:
// get CU IDs from search in i18ns
var ids pq.Int64Array
q := `select array_agg(cui.content_unit_id)
from content_units cu
left join content_unit_i18n cui on cu.id = cui.content_unit_id
where cui.name ~ $1 or cui .description ~ $1 limit $2`
err := queries.Raw(q, f.Query, MAX_PAGE_SIZE).QueryRow(exec).Scan(&ids)
if err != nil {
return err
}
if ids != nil && len(ids) != 0 {
intListStr := strings.Trim(strings.Replace(fmt.Sprint(ids), " ", ",", -1), "[]")
whereParts = append(whereParts, fmt.Sprintf("id in (%s)", intListStr))
}
case SEARCH_IN_CONTENT_UNITS_TYPE_SOURCE:
// get CU UIDs from search in i18ns of sources with same uid
var uids pq.StringArray
q := `select array_agg(s.uid)
from sources s
left join source_i18n si on s.id = si.source_id
where si.name ~ $1 limit $2`
err := queries.Raw(q, f.Query, MAX_PAGE_SIZE).QueryRow(exec).Scan(&uids)
if err != nil {
return err
}
if uids != nil && len(uids) != 0 {
listAsStr := strings.Join(uids, "', '")
whereParts = append(whereParts, fmt.Sprintf("uid in ('%s')", listAsStr))
}
case SEARCH_IN_COLLECTIONS:
// get Collection IDs from search in i18ns
var ids pq.Int64Array
q := `select array_agg(c.id)
from collections c
left join collection_i18n ci on c.id = ci.collection_id
where ci.name ~ $1 or ci.description ~ $1 limit $2`
err := queries.Raw(q, f.Query, MAX_PAGE_SIZE).QueryRow(exec).Scan(&ids)
if err != nil {
return err
}
if ids != nil && len(ids) != 0 {
intListStr := strings.Trim(strings.Replace(fmt.Sprint(ids), " ", ",", -1), "[]")
whereParts = append(whereParts, fmt.Sprintf("id in (%s)", intListStr))
}
}
if len(whereParts) > 0 {
whereQuery := fmt.Sprintf("(%s)", strings.Join(whereParts, " or "))
*mods = append(*mods, qm.And(whereQuery))
} else {
*mods = append(*mods, qm.Where("id < 0")) // so we get back empty results
}
return nil
}
func appendIDsFilterMods(mods *[]qm.QueryMod, f IDsFilter) error {
if len(f.IDs) == 0 {
return nil
}
*mods = append(*mods, qm.WhereIn("id IN ?", utils.ConvertArgsInt64(f.IDs)...))
return nil
}
func appendPatternsFilterMods(mods *[]qm.QueryMod, f PatternsFilter) error {
if utils.IsEmpty(f.Patterns) {
return nil
}
*mods = append(*mods, qm.WhereIn("pattern IN ?", utils.ConvertArgsString(f.Patterns)...))
return nil
}
func appendUIDsFilterMods(mods *[]qm.QueryMod, f UIDsFilter) error {
if utils.IsEmpty(f.UIDs) {
return nil
}
*mods = append(*mods, qm.WhereIn("uid IN ?", utils.ConvertArgsString(f.UIDs)...))
return nil
}
func appendSHA1sFilterMods(mods *[]qm.QueryMod, f SHA1sFilter) error {
if utils.IsEmpty(f.SHA1s) {
return nil
}
hexSHA1s := make([][]byte, 0)
for i := range f.SHA1s {
s, err := hex.DecodeString(f.SHA1s[i])
if err != nil {
return errors.Wrapf(err, "hex.DecodeString [%d]: %s", i, f.SHA1s[i])
}
hexSHA1s = append(hexSHA1s, s)
}
*mods = append(*mods, qm.WhereIn("sha1 IN ?", utils.ConvertArgsBytes(hexSHA1s)...))
return nil
}
func appendDateRangeFilterMods(mods *[]qm.QueryMod, f DateRangeFilter, field string) error {
s, e, err := f.Range()
if err != nil {
return err
}
if f.StartDate != "" && f.EndDate != "" && e.Before(s) {
return errors.New("Invalid date range")
}
if field == "" {
field = "created_at"
}
if f.StartDate != "" {
*mods = append(*mods, qm.Where(fmt.Sprintf("%s >= ?", field), s))
}
if f.EndDate != "" {
*mods = append(*mods, qm.Where(fmt.Sprintf("%s <= ?", field), e))
}
return nil
}
func appendContentTypesFilterMods(mods *[]qm.QueryMod, f ContentTypesFilter) error {
if utils.IsEmpty(f.ContentTypes) {
return nil
}
a := make([]interface{}, len(f.ContentTypes))
for i, x := range f.ContentTypes {
ct, ok := common.CONTENT_TYPE_REGISTRY.ByName[strings.ToUpper(x)]
if ok {
a[i] = ct.ID
} else {
return errors.Errorf("Unknown content type: %s", x)
}
}
*mods = append(*mods, qm.WhereIn("type_id in ?", a...))
return nil
}
func appendSecureFilterMods(mods *[]qm.QueryMod, f SecureFilter) error {
if len(f.Levels) == 0 {
return nil
}
a := make([]interface{}, len(f.Levels))
for i, x := range f.Levels {
if x == common.SEC_PUBLIC || x == common.SEC_SENSITIVE || x == common.SEC_PRIVATE {
a[i] = x
} else {
return errors.Errorf("Unknown security level: %d", x)
}
}
*mods = append(*mods, qm.WhereIn("secure in ?", a...))
return nil
}
func appendPublishedFilterMods(mods *[]qm.QueryMod, f PublishedFilter) {
var val null.Bool
val.UnmarshalText([]byte(f.Published))
if val.Valid {
*mods = append(*mods, qm.Where("published = ?", val.Bool))
}
}
func appendOperationTypesFilterMods(mods *[]qm.QueryMod, f OperationTypesFilter) error {
if utils.IsEmpty(f.OperationTypes) {
return nil
}
a := make([]interface{}, len(f.OperationTypes))
for i, x := range f.OperationTypes {
ot, ok := common.OPERATION_TYPE_REGISTRY.ByName[strings.ToLower(x)]
if ok {
a[i] = ot.ID
} else {
return errors.Errorf("Unknown operation type: %s", x)
}
}
*mods = append(*mods, qm.WhereIn("type_id in ?", a...))
return nil
}
func appendSourcesFilterMods(exec boil.Executor, mods *[]qm.QueryMod, f SourcesFilter) error {
if utils.IsEmpty(f.Authors) && len(f.Sources) == 0 {
return nil
}
// slice of all source ids we want
source_ids := make([]int64, 0)
// fetch source ids by authors
if !utils.IsEmpty(f.Authors) {
for _, x := range f.Authors {
if _, ok := common.AUTHOR_REGISTRY.ByCode[strings.ToLower(x)]; !ok {
return NewBadRequestError(errors.Errorf("Unknown author: %s", x))
}
}
var ids pq.Int64Array
q := `SELECT array_agg(DISTINCT "as".source_id)
FROM authors a INNER JOIN authors_sources "as" ON a.id = "as".author_id
WHERE a.code = ANY($1)`
err := queries.Raw(q, pq.Array(f.Authors)).QueryRow(exec).Scan(&ids)
if err != nil {
return err
}
source_ids = append(source_ids, ids...)
}
// blend in requested sources
source_ids = append(source_ids, f.Sources...)
// find all nested source_ids
q := `WITH RECURSIVE rec_sources AS (
SELECT s.id FROM sources s WHERE s.id = ANY($1)
UNION
SELECT s.id FROM sources s INNER JOIN rec_sources rs ON s.parent_id = rs.id
)
SELECT array_agg(distinct id) FROM rec_sources`
var ids pq.Int64Array
err := queries.Raw(q, pq.Array(source_ids)).QueryRow(exec).Scan(&ids)
if err != nil {
return err
}
if ids == nil || len(ids) == 0 {
*mods = append(*mods, qm.Where("id < 0")) // so results would be empty
} else {
*mods = append(*mods,
qm.InnerJoin("content_units_sources cus ON id = cus.content_unit_id"),
qm.WhereIn("cus.source_id in ?", utils.ConvertArgsInt64(ids)...))
}
return nil
}
func appendTagsFilterMods(exec boil.Executor, mods *[]qm.QueryMod, f TagsFilter) error {
if len(f.Tags) == 0 {
return nil
}
// find all nested tag_ids
q := `WITH RECURSIVE rec_tags AS (
SELECT t.id FROM tags t WHERE t.id = ANY($1)
UNION
SELECT t.id FROM tags t INNER JOIN rec_tags rt ON t.parent_id = rt.id
)
SELECT array_agg(distinct id) FROM rec_tags`
var ids pq.Int64Array
err := queries.Raw(q, pq.Array(f.Tags)).QueryRow(exec).Scan(&ids)
if err != nil {
return err
}
if ids == nil || len(ids) == 0 {
*mods = append(*mods, qm.Where("id < 0")) // so results would be empty
} else {
*mods = append(*mods,
qm.InnerJoin("content_units_tags cut ON id = cut.content_unit_id"),
qm.WhereIn("cut.tag_id in ?", utils.ConvertArgsInt64(ids)...))
}
return nil
}
func appendOriginalFilterMods(mods *[]qm.QueryMod, f OriginalLanguageFilter) {
if f.OriginalLanguage == "" {
return
}
*mods = append(*mods, qm.Where("properties->>'original_language' = ?", f.OriginalLanguage))
}
// mustBeginTx begins a transaction, panics on error.
func mustBeginTx(c utils.ContextProvider) *sql.Tx {
tx, err := c.MustGet("MDB").(*sql.DB).Begin()
utils.Must(err)
return tx
}
// mustConcludeTx commits or rollback the given transaction according to given error.
// Panics if Commit() or Rollback() fails.
func mustConcludeTx(tx *sql.Tx, err *HttpError) {
if err == nil {
utils.Must(tx.Commit())
} else {
utils.Must(tx.Rollback())
}
}
func mdbReplicationLocation(c utils.ContextProvider) (string, error) {
var loc string
db := c.MustGet("MDB").(*sql.DB)
err := db.QueryRow("SELECT pg_current_wal_insert_lsn();").Scan(&loc)
if err != nil {
return "", errors.Wrap(err, "Fetch Replication Position")
}
return loc, nil
}
// concludeRequest responds with JSON of given response or aborts the request with the given error.
func concludeRequest(c *gin.Context, resp interface{}, err *HttpError) {
if err == nil {
c.JSON(http.StatusOK, resp)
} else {
err.Abort(c)
}
}
func emitEvents(cp utils.ContextProvider, evnts ...events.Event) {
if len(evnts) == 0 {
return
}
// We attach postgresql replication log location
// so that clients could verify that their stand-bys are synced
// see:
// https://blog.2ndquadrant.com/postgresql-10-transaction-traceability/
// https://www.postgresql.org/docs/9.6/static/functions-admin.html
// https://www.postgresql.org/docs/9.6/static/datatype-pg-lsn.html
if rLoc, err := mdbReplicationLocation(cp); err != nil {
log.Errorf("emitEvents: rLoc: %+v", err)
} else {
for i := range evnts {
evnts[i].ReplicationLocation = rLoc
}
}
cp.MustGet("EVENTS_EMITTER").(events.EventEmitter).Emit(evnts...)
}
func can(cp utils.ContextProvider, obj string, act string) bool {
sub := []string{""}
if v, ok := cp.Get("ID_TOKEN_CLAIMS"); ok {
claims := v.(permissions.IDTokenClaims)
sub = claims.RealmAccess.Roles
//log.Infof("Subject is %s %s with roles %v", claims.Sub, claims.Name, sub)
} else {
// bypass hack for workflow insert station
if act == common.PERM_READ && cp.(*gin.Context).ClientIP() == "146.185.60.45" {
log.Info("Workflow Insert station read")
return true
}
log.Infof("No subject.")
}
enforcer := cp.MustGet("PERMISSIONS_ENFORCER").(*casbin.Enforcer)
for i := range sub {
if enforcer.Enforce(sub[i], obj, act) {
log.Infof("ALLOW %s, %s, %s", sub[i], obj, act)
return true
}
}
//log.Warnf("DENY %v, %s, %s", sub, obj, act)
return false
}
func isAdmin(cp utils.ContextProvider) bool {
if v, ok := cp.Get("ID_TOKEN_CLAIMS"); ok {
claims := v.(permissions.IDTokenClaims)
for i := range claims.RealmAccess.Roles {
if "archive_admin" == claims.RealmAccess.Roles[i] {
return true
}
}
}
return false
}
func secureToPermission(secure int16) string {
switch secure {
case common.SEC_PRIVATE:
return "data_private"
case common.SEC_SENSITIVE:
return "data_sensitive"
default:
return "data_public"
}
}
func allowedRead(cp utils.ContextProvider) int16 {
return allowedSecure(cp, common.PERM_READ)
}
func allowedWrite(cp utils.ContextProvider) int16 {
return allowedSecure(cp, common.PERM_WRITE)
}
func allowedSecure(cp utils.ContextProvider, act string) int16 {
if can(cp, secureToPermission(common.SEC_PRIVATE), act) {
return common.SEC_PRIVATE
} else if can(cp, secureToPermission(common.SEC_SENSITIVE), act) {
return common.SEC_SENSITIVE
} else if can(cp, secureToPermission(common.SEC_PUBLIC), act) {
return common.SEC_PUBLIC
}
if ginCtx, ok := cp.(*gin.Context); ok {
clientIP := ginCtx.ClientIP()
// workflow insert station
if clientIP == "146.185.60.45" {
//log.Info("Workflow Insert station secure level")
return common.SEC_PRIVATE
}
// internal network (hopefully MDB-CIT [aka rename])
if strings.HasPrefix(clientIP, "10.") {
//log.Infof("Internal network secure level: %s", clientIP)
return common.SEC_PRIVATE
}
}
return -1
}