Improve bus (#3)

* Improve bus
* Add ticket log
This commit is contained in:
Jonas Plum
2021-12-27 19:08:07 +01:00
committed by GitHub
parent 1fade14ba5
commit b5dd0cfacd
50 changed files with 756 additions and 456 deletions
-26
View File
@@ -1,26 +0,0 @@
package automation
import (
"context"
"log"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/role"
)
func New(apiurl, apikey string, bus *bus.Bus, db *database.Database) error {
if err := jobAutomation(jobContext(), apiurl, apikey, bus, db); err != nil {
log.Fatal(err)
}
return resultAutomation(bus, db)
}
func jobContext() context.Context {
// TODO: change roles?
bot := &models.UserResponse{ID: "bot", Roles: []string{role.Admin}}
return busdb.UserContext(context.Background(), bot)
}
-116
View File
@@ -1,116 +0,0 @@
package automation
import (
"encoding/json"
"fmt"
"log"
"golang.org/x/net/context"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/models"
)
func jobAutomation(ctx context.Context, apiurl, apikey string, catalystBus *bus.Bus, db *database.Database) error {
return catalystBus.SubscribeJob(func(automationMsg *bus.JobMsg) {
job, err := db.JobCreate(ctx, automationMsg.ID, &models.JobForm{
Automation: automationMsg.Automation,
Payload: automationMsg.Message.Payload,
Origin: automationMsg.Origin,
})
if err != nil {
log.Println(err)
return
}
automation, err := db.AutomationGet(ctx, automationMsg.Automation)
if err != nil {
log.Println(err)
return
}
if automation.Script == "" {
log.Println("automation is empty")
return
}
if automationMsg.Message.Secrets == nil {
automationMsg.Message.Secrets = map[string]string{}
}
automationMsg.Message.Secrets["catalyst_apikey"] = apikey
automationMsg.Message.Secrets["catalyst_apiurl"] = apiurl
scriptMessage, _ := json.Marshal(automationMsg.Message)
containerID, logs, err := createContainer(ctx, automation.Image, automation.Script, string(scriptMessage))
if err != nil {
log.Println(err)
return
}
if _, err := db.JobUpdate(ctx, automationMsg.ID, &models.Job{
Automation: job.Automation,
Container: &containerID,
Origin: job.Origin,
Output: job.Output,
Log: &logs,
Payload: job.Payload,
Status: job.Status,
}); err != nil {
log.Println(err)
return
}
var result map[string]interface{}
stdout, _, err := runDocker(ctx, automationMsg.ID, containerID, db)
if err != nil {
result = map[string]interface{}{"error": fmt.Sprintf("error running script %s %s", err, string(stdout))}
} else {
var data map[string]interface{}
if err := json.Unmarshal(stdout, &data); err != nil {
result = map[string]interface{}{"error": string(stdout)}
} else {
result = data
}
}
if err := catalystBus.PublishResult(automationMsg.Automation, result, automationMsg.Origin); err != nil {
log.Println(err)
}
if err := db.JobComplete(ctx, automationMsg.ID, result); err != nil {
log.Println(err)
return
}
})
}
/*
func getAutomation(automationID string, config *Config) (*models.AutomationResponse, error) {
req, err := http.NewRequest(http.MethodGet, config.CatalystAPIUrl+"/automations/"+automationID, nil)
if err != nil {
return nil, err
}
req.Header.Set("PRIVATE-TOKEN", config.CatalystAPIKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var automation models.AutomationResponse
if err := json.Unmarshal(b, &automation); err != nil {
return nil, err
}
return &automation, nil
}
*/
-38
View File
@@ -1,38 +0,0 @@
package automation
import (
"log"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/models"
)
func resultAutomation(catalystBus *bus.Bus, db *database.Database) error {
return catalystBus.SubscribeResult(func(resultMsg *bus.ResultMsg) {
if resultMsg.Target != nil {
ctx := jobContext()
switch {
case resultMsg.Target.TaskOrigin != nil:
if _, err := db.TaskComplete(
ctx,
resultMsg.Target.TaskOrigin.TicketId,
resultMsg.Target.TaskOrigin.PlaybookId,
resultMsg.Target.TaskOrigin.TaskId,
resultMsg.Data,
); err != nil {
log.Println(err)
}
case resultMsg.Target.ArtifactOrigin != nil:
enrichment := &models.EnrichmentForm{
Data: resultMsg.Data,
Name: resultMsg.Automation,
}
_, err := db.EnrichArtifact(ctx, resultMsg.Target.ArtifactOrigin.TicketId, resultMsg.Target.ArtifactOrigin.Artifact, enrichment)
if err != nil {
log.Println(err)
}
}
}
})
}
+12 -82
View File
@@ -4,16 +4,7 @@ import (
"encoding/json" "encoding/json"
"log" "log"
"github.com/arangodb/go-driver"
emitter "github.com/emitter-io/go/v2" emitter "github.com/emitter-io/go/v2"
"github.com/SecurityBrewery/catalyst/generated/models"
)
const (
channelUpdate = "data"
channelJob = "job"
channelResult = "result"
) )
type Bus struct { type Bus struct {
@@ -22,25 +13,13 @@ type Bus struct {
} }
type Config struct { type Config struct {
Host string Host string
Key string Key string
resultBusKey string databaseUpdateBusKey string
jobBusKey string jobBusKey string
dataBusKey string resultBusKey string
APIUrl string requestKey string
} APIUrl string
type JobMsg struct {
ID string `json:"id"`
Automation string `json:"automation"`
Origin *models.Origin `json:"origin"`
Message *models.Message `json:"message"`
}
type ResultMsg struct {
Automation string `json:"automation"`
Data map[string]interface{} `json:"data,omitempty"`
Target *models.Origin `json:"target"`
} }
func New(c *Config) (*Bus, error) { func New(c *Config) (*Bus, error) {
@@ -51,7 +30,7 @@ func New(c *Config) (*Bus, error) {
return nil, err return nil, err
} }
c.dataBusKey, err = client.GenerateKey(c.Key, channelUpdate+"/", "rwls", 0) c.databaseUpdateBusKey, err = client.GenerateKey(c.Key, channelDatabaseUpdate+"/", "rwls", 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -63,30 +42,14 @@ func New(c *Config) (*Bus, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
c.requestKey, err = client.GenerateKey(c.Key, ChannelRequest+"/", "rwls", 0)
if err != nil {
return nil, err
}
return &Bus{config: c, client: client}, err return &Bus{config: c, client: client}, err
} }
func (b *Bus) PublishUpdate(ids []driver.DocumentID) error {
return b.jsonPublish(ids, channelUpdate, b.config.dataBusKey)
}
func (b *Bus) PublishJob(id, automation string, payload interface{}, context *models.Context, origin *models.Origin) error {
return b.jsonPublish(&JobMsg{
ID: id,
Automation: automation,
Origin: origin,
Message: &models.Message{
Context: context,
Payload: payload,
},
}, channelJob, b.config.jobBusKey)
}
func (b *Bus) PublishResult(automation string, data map[string]interface{}, target *models.Origin) error {
return b.jsonPublish(&ResultMsg{Automation: automation, Data: data, Target: target}, channelResult, b.config.resultBusKey)
}
func (b *Bus) jsonPublish(msg interface{}, channel, key string) error { func (b *Bus) jsonPublish(msg interface{}, channel, key string) error {
payload, err := json.Marshal(msg) payload, err := json.Marshal(msg)
if err != nil { if err != nil {
@@ -96,39 +59,6 @@ func (b *Bus) jsonPublish(msg interface{}, channel, key string) error {
return b.client.Publish(key, channel, payload) return b.client.Publish(key, channel, payload)
} }
func (b *Bus) SubscribeUpdate(f func(ids []driver.DocumentID)) error {
return b.safeSubscribe(b.config.dataBusKey, channelUpdate, func(c *emitter.Client, m emitter.Message) {
var msg []driver.DocumentID
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(msg)
})
}
func (b *Bus) SubscribeJob(f func(msg *JobMsg)) error {
return b.safeSubscribe(b.config.jobBusKey, channelJob, func(c *emitter.Client, m emitter.Message) {
var msg JobMsg
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(&msg)
})
}
func (b *Bus) SubscribeResult(f func(msg *ResultMsg)) error {
return b.safeSubscribe(b.config.resultBusKey, channelResult, func(c *emitter.Client, m emitter.Message) {
var msg ResultMsg
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(&msg)
})
}
func (b *Bus) safeSubscribe(key, channel string, handler func(c *emitter.Client, m emitter.Message)) error { func (b *Bus) safeSubscribe(key, channel string, handler func(c *emitter.Client, m emitter.Message)) error {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
+42
View File
@@ -0,0 +1,42 @@
package bus
import (
"encoding/json"
"log"
"github.com/arangodb/go-driver"
emitter "github.com/emitter-io/go/v2"
)
const channelDatabaseUpdate = "databaseupdate"
type DatabaseUpdateType string
const (
DatabaseEntryRead DatabaseUpdateType = "read"
DatabaseEntryCreated DatabaseUpdateType = "created"
DatabaseEntryUpdated DatabaseUpdateType = "updated"
)
type DatabaseUpdateMsg struct {
IDs []driver.DocumentID `json:"ids"`
Type DatabaseUpdateType `json:"type"`
}
func (b *Bus) PublishDatabaseUpdate(ids []driver.DocumentID, databaseUpdateType DatabaseUpdateType) error {
return b.jsonPublish(&DatabaseUpdateMsg{
IDs: ids,
Type: databaseUpdateType,
}, channelDatabaseUpdate, b.config.databaseUpdateBusKey)
}
func (b *Bus) SubscribeDatabaseUpdate(f func(msg *DatabaseUpdateMsg)) error {
return b.safeSubscribe(b.config.databaseUpdateBusKey, channelDatabaseUpdate, func(c *emitter.Client, m emitter.Message) {
var msg DatabaseUpdateMsg
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(&msg)
})
}
+42
View File
@@ -0,0 +1,42 @@
package bus
import (
"encoding/json"
"log"
emitter "github.com/emitter-io/go/v2"
"github.com/SecurityBrewery/catalyst/generated/models"
)
const channelJob = "job"
type JobMsg struct {
ID string `json:"id"`
Automation string `json:"automation"`
Origin *models.Origin `json:"origin"`
Message *models.Message `json:"message"`
}
func (b *Bus) PublishJob(id, automation string, payload interface{}, context *models.Context, origin *models.Origin) error {
return b.jsonPublish(&JobMsg{
ID: id,
Automation: automation,
Origin: origin,
Message: &models.Message{
Context: context,
Payload: payload,
},
}, channelJob, b.config.jobBusKey)
}
func (b *Bus) SubscribeJob(f func(msg *JobMsg)) error {
return b.safeSubscribe(b.config.jobBusKey, channelJob, func(c *emitter.Client, m emitter.Message) {
var msg JobMsg
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(&msg)
})
}
+36
View File
@@ -0,0 +1,36 @@
package bus
import (
"encoding/json"
"log"
"github.com/arangodb/go-driver"
emitter "github.com/emitter-io/go/v2"
)
const ChannelRequest = "request"
type RequestMsg struct {
IDs []driver.DocumentID `json:"ids"`
Function string `json:"function"`
User string `json:"user"`
}
func (b *Bus) PublishRequest(user, f string, ids []driver.DocumentID) error {
return b.jsonPublish(&RequestMsg{
User: user,
Function: f,
IDs: ids,
}, ChannelRequest, b.config.requestKey)
}
func (b *Bus) SubscribeRequest(f func(msg *RequestMsg)) error {
return b.safeSubscribe(b.config.requestKey, ChannelRequest, func(c *emitter.Client, m emitter.Message) {
var msg RequestMsg
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(&msg)
})
}
+33
View File
@@ -0,0 +1,33 @@
package bus
import (
"encoding/json"
"log"
emitter "github.com/emitter-io/go/v2"
"github.com/SecurityBrewery/catalyst/generated/models"
)
const channelResult = "result"
type ResultMsg struct {
Automation string `json:"automation"`
Data map[string]interface{} `json:"data,omitempty"`
Target *models.Origin `json:"target"`
}
func (b *Bus) PublishResult(automation string, data map[string]interface{}, target *models.Origin) error {
return b.jsonPublish(&ResultMsg{Automation: automation, Data: data, Target: target}, channelResult, b.config.resultBusKey)
}
func (b *Bus) SubscribeResult(f func(msg *ResultMsg)) error {
return b.safeSubscribe(b.config.resultBusKey, channelResult, func(c *emitter.Client, m emitter.Message) {
var msg ResultMsg
if err := json.Unmarshal(m.Payload(), &msg); err != nil {
log.Println(err)
return
}
go f(&msg)
})
}
+60
View File
@@ -0,0 +1,60 @@
package busservice
import (
"context"
"log"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/role"
"github.com/SecurityBrewery/catalyst/time"
)
type busService struct {
db *database.Database
apiURL string
apiKey string
catalystBus *bus.Bus
}
func New(apiurl, apikey string, catalystBus *bus.Bus, db *database.Database) error {
h := &busService{db: db, apiURL: apiurl, apiKey: apikey, catalystBus: catalystBus}
if err := catalystBus.SubscribeRequest(h.logRequest); err != nil {
return err
}
if err := catalystBus.SubscribeResult(h.handleResult); err != nil {
return err
}
if err := catalystBus.SubscribeJob(h.handleJob); err != nil {
return err
}
return nil
}
func busContext() context.Context {
// TODO: change roles?
bot := &models.UserResponse{ID: "bot", Roles: []string{role.Admin}}
return busdb.UserContext(context.Background(), bot)
}
func (h *busService) logRequest(msg *bus.RequestMsg) {
var logEntries []*models.LogEntry
for _, i := range msg.IDs {
logEntries = append(logEntries, &models.LogEntry{
Type: bus.ChannelRequest,
Reference: i.String(),
Creator: msg.User,
Message: msg.Function,
Created: time.Now().UTC(),
})
}
if err := h.db.LogBatchCreate(busContext(), logEntries); err != nil {
log.Println(err)
}
}
@@ -1,4 +1,4 @@
package automation package busservice
import ( import (
"archive/tar" "archive/tar"
+113
View File
@@ -0,0 +1,113 @@
package busservice
import (
"encoding/json"
"fmt"
"log"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/generated/models"
)
func (h *busService) handleJob(automationMsg *bus.JobMsg) {
ctx := busContext()
job, err := h.db.JobCreate(ctx, automationMsg.ID, &models.JobForm{
Automation: automationMsg.Automation,
Payload: automationMsg.Message.Payload,
Origin: automationMsg.Origin,
})
if err != nil {
log.Println(err)
return
}
automation, err := h.db.AutomationGet(ctx, automationMsg.Automation)
if err != nil {
log.Println(err)
return
}
if automation.Script == "" {
log.Println("automation is empty")
return
}
if automationMsg.Message.Secrets == nil {
automationMsg.Message.Secrets = map[string]string{}
}
automationMsg.Message.Secrets["catalyst_apikey"] = h.apiKey
automationMsg.Message.Secrets["catalyst_apiurl"] = h.apiURL
scriptMessage, _ := json.Marshal(automationMsg.Message)
containerID, logs, err := createContainer(ctx, automation.Image, automation.Script, string(scriptMessage))
if err != nil {
log.Println(err)
return
}
if _, err := h.db.JobUpdate(ctx, automationMsg.ID, &models.Job{
Automation: job.Automation,
Container: &containerID,
Origin: job.Origin,
Output: job.Output,
Log: &logs,
Payload: job.Payload,
Status: job.Status,
}); err != nil {
log.Println(err)
return
}
var result map[string]interface{}
stdout, _, err := runDocker(ctx, automationMsg.ID, containerID, h.db)
if err != nil {
result = map[string]interface{}{"error": fmt.Sprintf("error running script %s %s", err, string(stdout))}
} else {
var data map[string]interface{}
if err := json.Unmarshal(stdout, &data); err != nil {
result = map[string]interface{}{"error": string(stdout)}
} else {
result = data
}
}
if err := h.catalystBus.PublishResult(automationMsg.Automation, result, automationMsg.Origin); err != nil {
log.Println(err)
}
if err := h.db.JobComplete(ctx, automationMsg.ID, result); err != nil {
log.Println(err)
return
}
}
/*
func getAutomation(automationID string, config *Config) (*models.AutomationResponse, error) {
req, err := http.NewRequest(http.MethodGet, config.CatalystAPIUrl+"/automations/"+automationID, nil)
if err != nil {
return nil, err
}
req.Header.Set("PRIVATE-TOKEN", config.CatalystAPIKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var automation models.AutomationResponse
if err := json.Unmarshal(b, &automation); err != nil {
return nil, err
}
return &automation, nil
}
*/
+35
View File
@@ -0,0 +1,35 @@
package busservice
import (
"log"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/generated/models"
)
func (h *busService) handleResult(resultMsg *bus.ResultMsg) {
if resultMsg.Target != nil {
ctx := busContext()
switch {
case resultMsg.Target.TaskOrigin != nil:
if _, err := h.db.TaskComplete(
ctx,
resultMsg.Target.TaskOrigin.TicketId,
resultMsg.Target.TaskOrigin.PlaybookId,
resultMsg.Target.TaskOrigin.TaskId,
resultMsg.Data,
); err != nil {
log.Println(err)
}
case resultMsg.Target.ArtifactOrigin != nil:
enrichment := &models.EnrichmentForm{
Data: resultMsg.Data,
Name: resultMsg.Automation,
}
_, err := h.db.EnrichArtifact(ctx, resultMsg.Target.ArtifactOrigin.TicketId, resultMsg.Target.ArtifactOrigin.Artifact, enrichment)
if err != nil {
log.Println(err)
}
}
}
}
+3 -4
View File
@@ -6,6 +6,7 @@ import (
"github.com/arangodb/go-driver" "github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database/busdb" "github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models" "github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/time" "github.com/SecurityBrewery/catalyst/time"
@@ -60,11 +61,10 @@ func (db *Database) ArtifactUpdate(ctx context.Context, id int64, name string, a
"name": name, "name": name,
"artifact": artifact, "artifact": artifact,
}, ticketFilterVars), &busdb.Operation{ }, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)), driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
}, },
Msg: fmt.Sprintf("Update artifact %s", name),
}) })
} }
@@ -92,10 +92,9 @@ func (db *Database) EnrichArtifact(ctx context.Context, id int64, name string, e
"enrichmentname": enrichment.Name, "enrichmentname": enrichment.Name,
"enrichment": enrichment, "enrichment": enrichment,
}, ticketFilterVars), &busdb.Operation{ }, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)), driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
}, },
Msg: fmt.Sprintf("Run %s on artifact", enrichment.Name),
}) })
} }
+11 -33
View File
@@ -33,22 +33,13 @@ func NewDatabase(ctx context.Context, internal driver.Database, b *bus.Bus) (*Bu
}, nil }, nil
} }
type OperationType int
const (
Create OperationType = iota
Read = iota
Update = iota
)
type Operation struct { type Operation struct {
OperationType OperationType Type bus.DatabaseUpdateType
Ids []driver.DocumentID Ids []driver.DocumentID
Msg string
} }
var CreateOperation = &Operation{OperationType: Create} var CreateOperation = &Operation{Type: bus.DatabaseEntryCreated}
var ReadOperation = &Operation{OperationType: Read} var ReadOperation = &Operation{Type: bus.DatabaseEntryRead}
func (db BusDatabase) Query(ctx context.Context, query string, vars map[string]interface{}, operation *Operation) (driver.Cursor, *models.LogEntry, error) { func (db BusDatabase) Query(ctx context.Context, query string, vars map[string]interface{}, operation *Operation) (driver.Cursor, *models.LogEntry, error) {
cur, err := db.internal.Query(ctx, query, vars) cur, err := db.internal.Query(ctx, query, vars)
@@ -59,8 +50,8 @@ func (db BusDatabase) Query(ctx context.Context, query string, vars map[string]i
var logs *models.LogEntry var logs *models.LogEntry
switch { switch {
case operation.OperationType == Update: case operation.Type == bus.DatabaseEntryCreated, operation.Type == bus.DatabaseEntryUpdated:
if err := db.LogAndNotify(ctx, operation.Ids, operation.Msg); err != nil { if err := db.bus.PublishDatabaseUpdate(operation.Ids, operation.Type); err != nil {
return nil, nil, err return nil, nil, err
} }
} }
@@ -68,19 +59,6 @@ func (db BusDatabase) Query(ctx context.Context, query string, vars map[string]i
return cur, logs, err return cur, logs, err
} }
func (db BusDatabase) LogAndNotify(ctx context.Context, ids []driver.DocumentID, msg string) error {
var logEntries []*models.LogEntry
for _, i := range ids {
logEntries = append(logEntries, &models.LogEntry{Reference: i.String(), Message: msg})
}
if err := db.LogBatchCreate(ctx, logEntries); err != nil {
return err
}
return db.bus.PublishUpdate(ids)
}
func (db BusDatabase) Remove(ctx context.Context) error { func (db BusDatabase) Remove(ctx context.Context) error {
return db.internal.Remove(ctx) return db.internal.Remove(ctx)
} }
@@ -104,7 +82,7 @@ func (c Collection) CreateDocument(ctx, newctx context.Context, key string, docu
return meta, err return meta, err
} }
err = c.db.LogAndNotify(ctx, []driver.DocumentID{meta.ID}, "Document created") err = c.db.bus.PublishDatabaseUpdate([]driver.DocumentID{meta.ID}, bus.DatabaseEntryCreated)
if err != nil { if err != nil {
return meta, err return meta, err
} }
@@ -117,7 +95,7 @@ func (c Collection) CreateEdge(ctx, newctx context.Context, edge *driver.EdgeDoc
return meta, err return meta, err
} }
err = c.db.LogAndNotify(ctx, []driver.DocumentID{meta.ID}, "Document created") err = c.db.bus.PublishDatabaseUpdate([]driver.DocumentID{meta.ID}, bus.DatabaseEntryCreated)
if err != nil { if err != nil {
return meta, err return meta, err
} }
@@ -138,7 +116,7 @@ func (c Collection) CreateEdges(ctx context.Context, edges []*driver.EdgeDocumen
ids = append(ids, meta.ID) ids = append(ids, meta.ID)
} }
err = c.db.LogAndNotify(ctx, ids, "Document created") err = c.db.bus.PublishDatabaseUpdate(ids, bus.DatabaseEntryCreated)
if err != nil { if err != nil {
return metas, err return metas, err
} }
@@ -160,7 +138,7 @@ func (c Collection) UpdateDocument(ctx context.Context, key string, update inter
return meta, err return meta, err
} }
return meta, c.db.bus.PublishUpdate([]driver.DocumentID{meta.ID}) return meta, c.db.bus.PublishDatabaseUpdate([]driver.DocumentID{meta.ID}, bus.DatabaseEntryUpdated)
} }
func (c Collection) ReplaceDocument(ctx context.Context, key string, document interface{}) (driver.DocumentMeta, error) { func (c Collection) ReplaceDocument(ctx context.Context, key string, document interface{}) (driver.DocumentMeta, error) {
@@ -169,7 +147,7 @@ func (c Collection) ReplaceDocument(ctx context.Context, key string, document in
return meta, err return meta, err
} }
return meta, c.db.bus.PublishUpdate([]driver.DocumentID{meta.ID}) return meta, c.db.bus.PublishDatabaseUpdate([]driver.DocumentID{meta.ID}, bus.DatabaseEntryUpdated)
} }
func (c Collection) RemoveDocument(ctx context.Context, formatInt string) (driver.DocumentMeta, error) { func (c Collection) RemoveDocument(ctx context.Context, formatInt string) (driver.DocumentMeta, error) {
+14 -20
View File
@@ -3,6 +3,8 @@ package busdb
import ( import (
"context" "context"
"errors" "errors"
"github.com/SecurityBrewery/catalyst/bus"
"strings"
"github.com/arangodb/go-driver" "github.com/arangodb/go-driver"
@@ -12,15 +14,16 @@ import (
const LogCollectionName = "logs" const LogCollectionName = "logs"
func (db *BusDatabase) LogCreate(ctx context.Context, reference, message string) (*models.LogEntry, error) { func (db *BusDatabase) LogCreate(ctx context.Context, logType, reference, message string) (*models.LogEntry, error) {
user, ok := UserFromContext(ctx) user, ok := UserFromContext(ctx)
if !ok { if !ok {
return nil, errors.New("no user in context") return nil, errors.New("no user in context")
} }
logentry := &models.LogEntry{ logentry := &models.LogEntry{
Type: logType,
Reference: reference, Reference: reference,
Created: time.Now(), Created: time.Now().UTC(),
Creator: user.ID, Creator: user.ID,
Message: message, Message: message,
} }
@@ -31,27 +34,18 @@ func (db *BusDatabase) LogCreate(ctx context.Context, reference, message string)
return nil, err return nil, err
} }
return &doc, db.bus.PublishUpdate([]driver.DocumentID{driver.DocumentID(logentry.Reference)}) return &doc, nil
} }
func (db *BusDatabase) LogBatchCreate(ctx context.Context, logEntryForms []*models.LogEntry) error { func (db *BusDatabase) LogBatchCreate(ctx context.Context, logentries []*models.LogEntry) error {
user, ok := UserFromContext(ctx)
if !ok {
return errors.New("no user in context")
}
var ids []driver.DocumentID var ids []driver.DocumentID
var logentries []*models.LogEntry for _, entry := range logentries {
for _, logEntryForm := range logEntryForms { if strings.HasPrefix(entry.Reference, "tickets/") {
logentry := &models.LogEntry{ ids = append(ids, driver.DocumentID(entry.Reference))
Reference: logEntryForm.Reference,
Created: time.Now(),
Creator: user.ID,
Message: logEntryForm.Message,
} }
}
logentries = append(logentries, logentry) if ids != nil {
ids = append(ids, driver.DocumentID(logentry.Reference)) go db.bus.PublishDatabaseUpdate(ids, bus.DatabaseEntryCreated)
} }
_, errs, err := db.logCollection.CreateDocuments(ctx, logentries) _, errs, err := db.logCollection.CreateDocuments(ctx, logentries)
@@ -63,7 +57,7 @@ func (db *BusDatabase) LogBatchCreate(ctx context.Context, logEntryForms []*mode
return err return err
} }
return db.bus.PublishUpdate(ids) return nil
} }
func (db *BusDatabase) LogList(ctx context.Context, reference string) ([]*models.LogEntry, error) { func (db *BusDatabase) LogList(ctx context.Context, reference string) ([]*models.LogEntry, error) {
+3 -4
View File
@@ -11,6 +11,7 @@ import (
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/xeipuuv/gojsonschema" "github.com/xeipuuv/gojsonschema"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/caql" "github.com/SecurityBrewery/catalyst/caql"
"github.com/SecurityBrewery/catalyst/database/busdb" "github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models" "github.com/SecurityBrewery/catalyst/generated/models"
@@ -144,11 +145,10 @@ func (db *Database) JobLogAppend(ctx context.Context, id string, logLine string)
"ID": id, "ID": id,
"logline": logLine, "logline": logLine,
}, &busdb.Operation{ }, &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%s", JobCollectionName, id)), driver.DocumentID(fmt.Sprintf("%s/%s", JobCollectionName, id)),
}, },
Msg: fmt.Sprintf("Append logline"),
}) })
if err != nil { if err != nil {
return err return err
@@ -166,11 +166,10 @@ func (db *Database) JobComplete(ctx context.Context, id string, out interface{})
"ID": id, "ID": id,
"out": out, "out": out,
}, &busdb.Operation{ }, &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%s", JobCollectionName, id)), driver.DocumentID(fmt.Sprintf("%s/%s", JobCollectionName, id)),
}, },
Msg: fmt.Sprintf("Set output"),
}) })
if err != nil { if err != nil {
return err return err
+2 -2
View File
@@ -7,6 +7,7 @@ import (
"github.com/arangodb/go-driver" "github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database/busdb" "github.com/SecurityBrewery/catalyst/database/busdb"
) )
@@ -37,12 +38,11 @@ func (db *Database) RelatedRemove(ctx context.Context, id, id2 int64) error {
"id": driver.DocumentID(TicketCollectionName + "/" + strconv.Itoa(int(id))), "id": driver.DocumentID(TicketCollectionName + "/" + strconv.Itoa(int(id))),
"id2": driver.DocumentID(TicketCollectionName + "/" + strconv.Itoa(int(id2))), "id2": driver.DocumentID(TicketCollectionName + "/" + strconv.Itoa(int(id2))),
}, &busdb.Operation{ }, &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.DocumentID(TicketCollectionName + "/" + strconv.Itoa(int(id))), driver.DocumentID(TicketCollectionName + "/" + strconv.Itoa(int(id))),
driver.DocumentID(TicketCollectionName + "/" + strconv.Itoa(int(id2))), driver.DocumentID(TicketCollectionName + "/" + strconv.Itoa(int(id2))),
}, },
Msg: "Removed ticket/artifact relation",
}) })
return err return err
} }
+12 -7
View File
@@ -13,6 +13,7 @@ import (
"github.com/arangodb/go-driver" "github.com/arangodb/go-driver"
"github.com/xeipuuv/gojsonschema" "github.com/xeipuuv/gojsonschema"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/caql" "github.com/SecurityBrewery/catalyst/caql"
"github.com/SecurityBrewery/catalyst/database/busdb" "github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models" "github.com/SecurityBrewery/catalyst/generated/models"
@@ -133,7 +134,7 @@ func toTicketSimpleResponse(key string, ticket *models.Ticket) (*models.TicketSi
}, nil }, nil
} }
func toTicketWithTickets(ticketResponse *models.TicketResponse, tickets []*models.TicketSimpleResponse) *models.TicketWithTickets { func toTicketWithTickets(ticketResponse *models.TicketResponse, tickets []*models.TicketSimpleResponse, logs []*models.LogEntry) *models.TicketWithTickets {
return &models.TicketWithTickets{ return &models.TicketWithTickets{
Artifacts: ticketResponse.Artifacts, Artifacts: ticketResponse.Artifacts,
Comments: ticketResponse.Comments, Comments: ticketResponse.Comments,
@@ -152,6 +153,7 @@ func toTicketWithTickets(ticketResponse *models.TicketResponse, tickets []*model
Type: ticketResponse.Type, Type: ticketResponse.Type,
Write: ticketResponse.Write, Write: ticketResponse.Write,
Logs: logs,
Tickets: tickets, Tickets: tickets,
} }
} }
@@ -244,9 +246,8 @@ func (db *Database) TicketBatchCreate(ctx context.Context, ticketForms []*models
for _, apiTicket := range apiTickets { for _, apiTicket := range apiTickets {
ids = append(ids, driver.NewDocumentID(TicketCollectionName, fmt.Sprint(apiTicket.ID))) ids = append(ids, driver.NewDocumentID(TicketCollectionName, fmt.Sprint(apiTicket.ID)))
} }
if err := db.BusDatabase.LogAndNotify(ctx, ids, "Ticket created"); err != nil {
return nil, err go db.bus.PublishDatabaseUpdate(ids, bus.DatabaseEntryUpdated)
}
ticketResponses, err := toTicketResponses(apiTickets) ticketResponses, err := toTicketResponses(apiTickets)
if err != nil { if err != nil {
@@ -405,7 +406,12 @@ func (db *Database) ticketGetQuery(ctx context.Context, ticketID int64, query st
return nil, err return nil, err
} }
return toTicketWithTickets(ticketResponse, tickets), nil logs, err := db.LogList(ctx, fmt.Sprintf("%s/%d", TicketCollectionName, ticketID))
if err != nil {
return nil, err
}
return toTicketWithTickets(ticketResponse, tickets, logs), nil
} }
func (db *Database) TicketUpdate(ctx context.Context, ticketID int64, ticket *models.Ticket) (*models.TicketWithTickets, error) { func (db *Database) TicketUpdate(ctx context.Context, ticketID int64, ticket *models.Ticket) (*models.TicketWithTickets, error) {
@@ -420,10 +426,9 @@ func (db *Database) TicketUpdate(ctx context.Context, ticketID int64, ticket *mo
RETURN NEW` RETURN NEW`
ticket.Modified = time.Now().UTC() // TODO make setable? ticket.Modified = time.Now().UTC() // TODO make setable?
return db.ticketGetQuery(ctx, ticketID, query, mergeMaps(map[string]interface{}{"ticket": ticket}, ticketFilterVars), &busdb.Operation{ return db.ticketGetQuery(ctx, ticketID, query, mergeMaps(map[string]interface{}{"ticket": ticket}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Ids: []driver.DocumentID{ Type: bus.DatabaseEntryUpdated, Ids: []driver.DocumentID{
driver.NewDocumentID(TicketCollectionName, strconv.FormatInt(ticketID, 10)), driver.NewDocumentID(TicketCollectionName, strconv.FormatInt(ticketID, 10)),
}, },
Msg: "Ticket updated",
}) })
} }
+10 -18
View File
@@ -9,6 +9,7 @@ import (
"github.com/iancoleman/strcase" "github.com/iancoleman/strcase"
"github.com/mingrammer/commonregex" "github.com/mingrammer/commonregex"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database/busdb" "github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models" "github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/pointer" "github.com/SecurityBrewery/catalyst/pointer"
@@ -34,11 +35,10 @@ func (db *Database) AddArtifact(ctx context.Context, id int64, artifact *models.
UPDATE d WITH { "modified": @now, "artifacts": PUSH(NOT_NULL(d.artifacts, []), @artifact) } IN @@collection UPDATE d WITH { "modified": @now, "artifacts": PUSH(NOT_NULL(d.artifacts, []), @artifact) } IN @@collection
RETURN NEW` RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"artifact": artifact, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{ return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"artifact": artifact, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)), driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
}, },
Msg: "Add artifact",
}) })
} }
@@ -74,11 +74,10 @@ func (db *Database) RemoveArtifact(ctx context.Context, id int64, name string) (
UPDATE d WITH { "modified": @now, "artifacts": newartifacts } IN @@collection UPDATE d WITH { "modified": @now, "artifacts": newartifacts } IN @@collection
RETURN NEW` RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"name": name, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{ return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"name": name, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)), driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
}, },
Msg: "Remove artifact",
}) })
} }
@@ -93,11 +92,10 @@ func (db *Database) SetTemplate(ctx context.Context, id int64, schema string) (*
UPDATE d WITH { "schema": @schema } IN @@collection UPDATE d WITH { "schema": @schema } IN @@collection
RETURN NEW` RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"schema": schema}, ticketFilterVars), &busdb.Operation{ return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"schema": schema}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)), driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
}, },
Msg: "Set Template",
}) })
} }
@@ -125,11 +123,10 @@ func (db *Database) AddComment(ctx context.Context, id int64, comment *models.Co
UPDATE d WITH { "modified": @now, "comments": PUSH(NOT_NULL(d.comments, []), @comment) } IN @@collection UPDATE d WITH { "modified": @now, "comments": PUSH(NOT_NULL(d.comments, []), @comment) } IN @@collection
RETURN NEW` RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"comment": comment, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{ return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"comment": comment, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)), driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
}, },
Msg: "Add comment",
}) })
} }
@@ -144,11 +141,10 @@ func (db *Database) RemoveComment(ctx context.Context, id int64, commentID int64
UPDATE d WITH { "modified": @now, "comments": REMOVE_NTH(d.comments, @commentID) } IN @@collection UPDATE d WITH { "modified": @now, "comments": REMOVE_NTH(d.comments, @commentID) } IN @@collection
RETURN NEW` RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"commentID": commentID, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{ return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"commentID": commentID, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)), driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
}, },
Msg: "Remove comment",
}) })
} }
@@ -163,11 +159,10 @@ func (db *Database) SetReferences(ctx context.Context, id int64, references []*m
UPDATE d WITH { "modified": @now, "references": @references } IN @@collection UPDATE d WITH { "modified": @now, "references": @references } IN @@collection
RETURN NEW` RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"references": references, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{ return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"references": references, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)), driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
}, },
Msg: "Changed references",
}) })
} }
@@ -182,11 +177,10 @@ func (db *Database) LinkFiles(ctx context.Context, id int64, files []*models.Fil
UPDATE d WITH { "modified": @now, "files": @files } IN @@collection UPDATE d WITH { "modified": @now, "files": @files } IN @@collection
RETURN NEW` RETURN NEW`
return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"files": files, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{ return db.ticketGetQuery(ctx, id, query, mergeMaps(map[string]interface{}{"files": files, "now": time.Now().UTC()}, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)), driver.DocumentID(fmt.Sprintf("%s/%d", TicketCollectionName, id)),
}, },
Msg: "Linked files",
}) })
} }
@@ -224,11 +218,10 @@ func (db *Database) AddTicketPlaybook(ctx context.Context, id int64, playbookTem
"playbookID": findName(parentTicket.Playbooks, playbookID), "playbookID": findName(parentTicket.Playbooks, playbookID),
"now": time.Now().UTC(), "now": time.Now().UTC(),
}, ticketFilterVars), &busdb.Operation{ }, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.NewDocumentID(TicketCollectionName, fmt.Sprintf("%d", id)), driver.NewDocumentID(TicketCollectionName, fmt.Sprintf("%d", id)),
}, },
Msg: "Added playbook",
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -284,10 +277,9 @@ func (db *Database) RemoveTicketPlaybook(ctx context.Context, id int64, playbook
"playbookID": playbookID, "playbookID": playbookID,
"now": time.Now().UTC(), "now": time.Now().UTC(),
}, ticketFilterVars), &busdb.Operation{ }, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.NewDocumentID(TicketCollectionName, fmt.Sprintf("%d", id)), driver.NewDocumentID(TicketCollectionName, fmt.Sprintf("%d", id)),
}, },
Msg: fmt.Sprintf("Removed playbook %s", playbookID),
}) })
} }
+3 -4
View File
@@ -9,6 +9,7 @@ import (
"github.com/arangodb/go-driver" "github.com/arangodb/go-driver"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database/busdb" "github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models" "github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/time" "github.com/SecurityBrewery/catalyst/time"
@@ -74,11 +75,10 @@ func (db *Database) TaskComplete(ctx context.Context, id int64, playbookID strin
"closed": time.Now().UTC(), "closed": time.Now().UTC(),
"now": time.Now().UTC(), "now": time.Now().UTC(),
}, ticketFilterVars), &busdb.Operation{ }, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.NewDocumentID(TicketCollectionName, fmt.Sprintf("%d", id)), driver.NewDocumentID(TicketCollectionName, fmt.Sprintf("%d", id)),
}, },
Msg: fmt.Sprintf("Completed task %s in playbook %s", taskID, playbookID),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -136,11 +136,10 @@ func (db *Database) TaskUpdate(ctx context.Context, id int64, playbookID string,
"task": task, "task": task,
"now": time.Now().UTC(), "now": time.Now().UTC(),
}, ticketFilterVars), &busdb.Operation{ }, ticketFilterVars), &busdb.Operation{
OperationType: busdb.Update, Type: bus.DatabaseEntryUpdated,
Ids: []driver.DocumentID{ Ids: []driver.DocumentID{
driver.NewDocumentID(TicketCollectionName, fmt.Sprintf("%d", id)), driver.NewDocumentID(TicketCollectionName, fmt.Sprintf("%d", id)),
}, },
Msg: fmt.Sprintf("Saved task %s in playbook %s", taskID, playbookID),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
+3 -2
View File
@@ -16,14 +16,15 @@ paths:
schema: { type: array, items: { $ref: "#/definitions/LogEntry" } } schema: { type: array, items: { $ref: "#/definitions/LogEntry" } }
examples: examples:
test: test:
- { "created": "2021-12-12T12:12:12.000000012Z","creator": "bob","reference": "tickets/294511","message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim." } - { type: "manual", "created": "2021-12-12T12:12:12.000000012Z","creator": "bob","reference": "tickets/294511","message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim." }
security: [ { roles: [ "log:read" ] } ] security: [ { roles: [ "log:read" ] } ]
definitions: definitions:
LogEntry: LogEntry:
type: object type: object
required: [ reference, creator, created, message ] required: [ type, reference, creator, created, message ]
properties: properties:
type: { type: string }
reference: { type: string } reference: { type: string }
creator: { type: string } creator: { type: string }
created: { type: string, format: "date-time" } created: { type: string, format: "date-time" }
+2
View File
@@ -1037,6 +1037,8 @@ definitions:
comments: { type: array, items: { $ref: '#/definitions/Comment' } } comments: { type: array, items: { $ref: '#/definitions/Comment' } }
artifacts: { type: array, items: { $ref: "#/definitions/Artifact" } } artifacts: { type: array, items: { $ref: "#/definitions/Artifact" } }
logs: { type: array, items: { $ref: '#/definitions/LogEntry' } }
created: { type: string, format: "date-time", example: "1985-04-12T23:20:50.52Z" } created: { type: string, format: "date-time", example: "1985-04-12T23:20:50.52Z" }
modified: { type: string, format: "date-time", example: "1985-04-12T23:20:50.52Z" } modified: { type: string, format: "date-time", example: "1985-04-12T23:20:50.52Z" }
+12 -2
View File
@@ -723,7 +723,8 @@
"created" : "2021-12-12T12:12:12.000+0000", "created" : "2021-12-12T12:12:12.000+0000",
"creator" : "bob", "creator" : "bob",
"message" : "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.", "message" : "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.",
"reference" : "tickets/294511" "reference" : "tickets/294511",
"type" : "manual"
} ] } ]
} }
}, },
@@ -5418,9 +5419,12 @@
}, },
"reference" : { "reference" : {
"type" : "string" "type" : "string"
},
"type" : {
"type" : "string"
} }
}, },
"required" : [ "created", "creator", "message", "reference" ], "required" : [ "created", "creator", "message", "reference", "type" ],
"type" : "object" "type" : "object"
}, },
"Message" : { "Message" : {
@@ -6445,6 +6449,12 @@
"format" : "int64", "format" : "int64",
"type" : "integer" "type" : "integer"
}, },
"logs" : {
"items" : {
"$ref" : "#/components/schemas/LogEntry"
},
"type" : "array"
},
"modified" : { "modified" : {
"format" : "date-time", "format" : "date-time",
"type" : "string" "type" : "string"
+8
View File
@@ -312,7 +312,10 @@ definitions:
type: string type: string
reference: reference:
type: string type: string
type:
type: string
required: required:
- type
- reference - reference
- creator - creator
- created - created
@@ -1152,6 +1155,10 @@ definitions:
example: 123 example: 123
format: int64 format: int64
type: integer type: integer
logs:
items:
$ref: '#/definitions/LogEntry'
type: array
modified: modified:
example: 1985-04-12T23:20:50.52Z example: 1985-04-12T23:20:50.52Z
format: date-time format: date-time
@@ -1945,6 +1952,7 @@ paths:
combine wish influence income guess run stand. Charge limit crime combine wish influence income guess run stand. Charge limit crime
ignore statement foundation study issue stop claim. ignore statement foundation study issue stop claim.
reference: tickets/294511 reference: tickets/294511
type: manual
schema: schema:
items: items:
$ref: '#/definitions/LogEntry' $ref: '#/definitions/LogEntry'
+12 -2
View File
@@ -491,7 +491,8 @@
"created" : "2021-12-12T12:12:12.000+0000", "created" : "2021-12-12T12:12:12.000+0000",
"creator" : "bob", "creator" : "bob",
"message" : "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.", "message" : "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.",
"reference" : "tickets/294511" "reference" : "tickets/294511",
"type" : "manual"
} ] } ]
} }
}, },
@@ -4905,9 +4906,12 @@
}, },
"reference" : { "reference" : {
"type" : "string" "type" : "string"
},
"type" : {
"type" : "string"
} }
}, },
"required" : [ "created", "creator", "message", "reference" ], "required" : [ "created", "creator", "message", "reference", "type" ],
"type" : "object" "type" : "object"
}, },
"Message" : { "Message" : {
@@ -5866,6 +5870,12 @@
"format" : "int64", "format" : "int64",
"type" : "integer" "type" : "integer"
}, },
"logs" : {
"items" : {
"$ref" : "#/components/schemas/LogEntry"
},
"type" : "array"
},
"modified" : { "modified" : {
"format" : "date-time", "format" : "date-time",
"type" : "string" "type" : "string"
+8
View File
@@ -247,7 +247,10 @@ definitions:
type: string type: string
reference: reference:
type: string type: string
type:
type: string
required: required:
- type
- reference - reference
- creator - creator
- created - created
@@ -1033,6 +1036,10 @@ definitions:
example: 123 example: 123
format: int64 format: int64
type: integer type: integer
logs:
items:
$ref: '#/definitions/LogEntry'
type: array
modified: modified:
example: 1985-04-12T23:20:50.52Z example: 1985-04-12T23:20:50.52Z
format: date-time format: date-time
@@ -1674,6 +1681,7 @@ paths:
combine wish influence income guess run stand. Charge limit crime combine wish influence income guess run stand. Charge limit crime
ignore statement foundation study issue stop claim. ignore statement foundation study issue stop claim.
reference: tickets/294511 reference: tickets/294511
type: manual
schema: schema:
items: items:
$ref: '#/definitions/LogEntry' $ref: '#/definitions/LogEntry'
+4 -2
View File
@@ -77,7 +77,7 @@ func init() {
gojsonschema.NewStringLoader(`{"type":"object","required":["automation","running","status"],"x-embed":"","properties":{"automation":{"type":"string"},"container":{"type":"string"},"log":{"type":"string"},"origin":{"$ref":"#/definitions/Origin"},"output":{"type":"object"},"payload":{},"running":{"type":"boolean"},"status":{"type":"string"}},"$id":"#/definitions/Job"}`), gojsonschema.NewStringLoader(`{"type":"object","required":["automation","running","status"],"x-embed":"","properties":{"automation":{"type":"string"},"container":{"type":"string"},"log":{"type":"string"},"origin":{"$ref":"#/definitions/Origin"},"output":{"type":"object"},"payload":{},"running":{"type":"boolean"},"status":{"type":"string"}},"$id":"#/definitions/Job"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["automation"],"x-embed":"","properties":{"automation":{"type":"string"},"origin":{"$ref":"#/definitions/Origin"},"payload":{}},"$id":"#/definitions/JobForm"}`), gojsonschema.NewStringLoader(`{"type":"object","required":["automation"],"x-embed":"","properties":{"automation":{"type":"string"},"origin":{"$ref":"#/definitions/Origin"},"payload":{}},"$id":"#/definitions/JobForm"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["id","automation","status"],"x-embed":"","properties":{"automation":{"type":"string"},"container":{"type":"string"},"id":{"type":"string"},"log":{"type":"string"},"origin":{"$ref":"#/definitions/Origin"},"output":{"type":"object"},"payload":{},"status":{"type":"string"}},"$id":"#/definitions/JobResponse"}`), gojsonschema.NewStringLoader(`{"type":"object","required":["id","automation","status"],"x-embed":"","properties":{"automation":{"type":"string"},"container":{"type":"string"},"id":{"type":"string"},"log":{"type":"string"},"origin":{"$ref":"#/definitions/Origin"},"output":{"type":"object"},"payload":{},"status":{"type":"string"}},"$id":"#/definitions/JobResponse"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["reference","creator","created","message"],"x-embed":"","properties":{"created":{"format":"date-time","type":"string"},"creator":{"type":"string"},"message":{"type":"string"},"reference":{"type":"string"}},"$id":"#/definitions/LogEntry"}`), gojsonschema.NewStringLoader(`{"type":"object","required":["type","reference","creator","created","message"],"x-embed":"","properties":{"created":{"format":"date-time","type":"string"},"creator":{"type":"string"},"message":{"type":"string"},"reference":{"type":"string"},"type":{"type":"string"}},"$id":"#/definitions/LogEntry"}`),
gojsonschema.NewStringLoader(`{"type":"object","x-embed":"","properties":{"context":{"$ref":"#/definitions/Context"},"payload":{"type":"object"},"secrets":{"type":"object","additionalProperties":{"type":"string"}}},"$id":"#/definitions/Message"}`), gojsonschema.NewStringLoader(`{"type":"object","x-embed":"","properties":{"context":{"$ref":"#/definitions/Context"},"payload":{"type":"object"},"secrets":{"type":"object","additionalProperties":{"type":"string"}}},"$id":"#/definitions/Message"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["id","blocked","roles"],"x-embed":"","properties":{"blocked":{"type":"boolean"},"id":{"type":"string"},"roles":{"items":{"type":"string"},"type":"array"},"secret":{"type":"string"}},"$id":"#/definitions/NewUserResponse"}`), gojsonschema.NewStringLoader(`{"type":"object","required":["id","blocked","roles"],"x-embed":"","properties":{"blocked":{"type":"boolean"},"id":{"type":"string"},"roles":{"items":{"type":"string"},"type":"array"},"secret":{"type":"string"}},"$id":"#/definitions/NewUserResponse"}`),
gojsonschema.NewStringLoader(`{"type":"object","x-embed":"","properties":{"artifact_origin":{"$ref":"#/definitions/ArtifactOrigin"},"task_origin":{"$ref":"#/definitions/TaskOrigin"}},"$id":"#/definitions/Origin"}`), gojsonschema.NewStringLoader(`{"type":"object","x-embed":"","properties":{"artifact_origin":{"$ref":"#/definitions/ArtifactOrigin"},"task_origin":{"$ref":"#/definitions/TaskOrigin"}},"$id":"#/definitions/Origin"}`),
@@ -105,7 +105,7 @@ func init() {
gojsonschema.NewStringLoader(`{"type":"object","required":["name","icon","default_template","default_playbooks"],"x-embed":"","properties":{"default_groups":{"items":{"type":"string"},"type":"array"},"default_playbooks":{"items":{"type":"string"},"type":"array"},"default_template":{"type":"string"},"icon":{"type":"string"},"name":{"type":"string"}},"$id":"#/definitions/TicketType"}`), gojsonschema.NewStringLoader(`{"type":"object","required":["name","icon","default_template","default_playbooks"],"x-embed":"","properties":{"default_groups":{"items":{"type":"string"},"type":"array"},"default_playbooks":{"items":{"type":"string"},"type":"array"},"default_template":{"type":"string"},"icon":{"type":"string"},"name":{"type":"string"}},"$id":"#/definitions/TicketType"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["name","icon","default_template","default_playbooks"],"x-embed":"","properties":{"default_groups":{"items":{"type":"string"},"type":"array"},"default_playbooks":{"items":{"type":"string"},"type":"array"},"default_template":{"type":"string"},"icon":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"}},"$id":"#/definitions/TicketTypeForm"}`), gojsonschema.NewStringLoader(`{"type":"object","required":["name","icon","default_template","default_playbooks"],"x-embed":"","properties":{"default_groups":{"items":{"type":"string"},"type":"array"},"default_playbooks":{"items":{"type":"string"},"type":"array"},"default_template":{"type":"string"},"icon":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"}},"$id":"#/definitions/TicketTypeForm"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["id","name","icon","default_template","default_playbooks"],"x-embed":"","properties":{"default_groups":{"items":{"type":"string"},"type":"array"},"default_playbooks":{"items":{"type":"string"},"type":"array"},"default_template":{"type":"string"},"icon":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"}},"$id":"#/definitions/TicketTypeResponse"}`), gojsonschema.NewStringLoader(`{"type":"object","required":["id","name","icon","default_template","default_playbooks"],"x-embed":"","properties":{"default_groups":{"items":{"type":"string"},"type":"array"},"default_playbooks":{"items":{"type":"string"},"type":"array"},"default_template":{"type":"string"},"icon":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"}},"$id":"#/definitions/TicketTypeResponse"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["id","name","type","status","created","modified","schema"],"x-embed":"","properties":{"artifacts":{"items":{"$ref":"#/definitions/Artifact"},"type":"array"},"comments":{"items":{"$ref":"#/definitions/Comment"},"type":"array"},"created":{"format":"date-time","type":"string"},"details":{"type":"object"},"files":{"items":{"$ref":"#/definitions/File"},"type":"array"},"id":{"format":"int64","type":"integer"},"modified":{"format":"date-time","type":"string"},"name":{"type":"string"},"owner":{"type":"string"},"playbooks":{"type":"object","additionalProperties":{"$ref":"#/definitions/PlaybookResponse"}},"read":{"items":{"type":"string"},"type":"array"},"references":{"items":{"$ref":"#/definitions/Reference"},"type":"array"},"schema":{"type":"string"},"status":{"type":"string"},"tickets":{"items":{"$ref":"#/definitions/TicketSimpleResponse"},"type":"array"},"type":{"type":"string"},"write":{"items":{"type":"string"},"type":"array"}},"$id":"#/definitions/TicketWithTickets"}`), gojsonschema.NewStringLoader(`{"type":"object","required":["id","name","type","status","created","modified","schema"],"x-embed":"","properties":{"artifacts":{"items":{"$ref":"#/definitions/Artifact"},"type":"array"},"comments":{"items":{"$ref":"#/definitions/Comment"},"type":"array"},"created":{"format":"date-time","type":"string"},"details":{"type":"object"},"files":{"items":{"$ref":"#/definitions/File"},"type":"array"},"id":{"format":"int64","type":"integer"},"logs":{"items":{"$ref":"#/definitions/LogEntry"},"type":"array"},"modified":{"format":"date-time","type":"string"},"name":{"type":"string"},"owner":{"type":"string"},"playbooks":{"type":"object","additionalProperties":{"$ref":"#/definitions/PlaybookResponse"}},"read":{"items":{"type":"string"},"type":"array"},"references":{"items":{"$ref":"#/definitions/Reference"},"type":"array"},"schema":{"type":"string"},"status":{"type":"string"},"tickets":{"items":{"$ref":"#/definitions/TicketSimpleResponse"},"type":"array"},"type":{"type":"string"},"write":{"items":{"type":"string"},"type":"array"}},"$id":"#/definitions/TicketWithTickets"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["id","name","icon"],"x-embed":"","properties":{"color":{"title":"Color","type":"string","enum":["error","info","success","warning"]},"icon":{"title":"Icon (https://materialdesignicons.com)","type":"string"},"id":{"title":"ID","type":"string"},"name":{"title":"Name","type":"string"}},"$id":"#/definitions/Type"}`), gojsonschema.NewStringLoader(`{"type":"object","required":["id","name","icon"],"x-embed":"","properties":{"color":{"title":"Color","type":"string","enum":["error","info","success","warning"]},"icon":{"title":"Icon (https://materialdesignicons.com)","type":"string"},"id":{"title":"ID","type":"string"},"name":{"title":"Name","type":"string"}},"$id":"#/definitions/Type"}`),
gojsonschema.NewStringLoader(`{"type":"object","required":["blocked","apikey","roles"],"x-embed":"","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"roles":{"items":{"type":"string"},"type":"array"},"sha256":{"type":"string"}},"$id":"#/definitions/User"}`), gojsonschema.NewStringLoader(`{"type":"object","required":["blocked","apikey","roles"],"x-embed":"","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"roles":{"items":{"type":"string"},"type":"array"},"sha256":{"type":"string"}},"$id":"#/definitions/User"}`),
gojsonschema.NewStringLoader(`{"type":"object","x-embed":"","properties":{"email":{"type":"string"},"image":{"type":"string"},"name":{"type":"string"},"timeformat":{"title":"Time Format (https://moment.github.io/luxon/docs/manual/formatting.html#table-of-tokens)","type":"string"}},"$id":"#/definitions/UserData"}`), gojsonschema.NewStringLoader(`{"type":"object","x-embed":"","properties":{"email":{"type":"string"},"image":{"type":"string"},"name":{"type":"string"},"timeformat":{"title":"Time Format (https://moment.github.io/luxon/docs/manual/formatting.html#table-of-tokens)","type":"string"}},"$id":"#/definitions/UserData"}`),
@@ -271,6 +271,7 @@ type LogEntry struct {
Creator string `json:"creator"` Creator string `json:"creator"`
Message string `json:"message"` Message string `json:"message"`
Reference string `json:"reference"` Reference string `json:"reference"`
Type string `json:"type"`
} }
type Message struct { type Message struct {
@@ -530,6 +531,7 @@ type TicketWithTickets struct {
Details interface{} `json:"details,omitempty"` Details interface{} `json:"details,omitempty"`
Files []*File `json:"files,omitempty"` Files []*File `json:"files,omitempty"`
ID int64 `json:"id"` ID int64 `json:"id"`
Logs []*LogEntry `json:"logs,omitempty"`
Modified time.Time `json:"modified"` Modified time.Time `json:"modified"`
Name string `json:"name"` Name string `json:"name"`
Owner *string `json:"owner,omitempty"` Owner *string `json:"owner,omitempty"`
+24 -2
View File
@@ -640,7 +640,8 @@ func init() {
"created": "2021-12-12T12:12:12.000000012Z", "created": "2021-12-12T12:12:12.000000012Z",
"creator": "bob", "creator": "bob",
"message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.", "message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.",
"reference": "tickets/294511" "reference": "tickets/294511",
"type": "manual"
} }
] ]
} }
@@ -5680,6 +5681,7 @@ func init() {
"LogEntry": { "LogEntry": {
"type": "object", "type": "object",
"required": [ "required": [
"type",
"reference", "reference",
"creator", "creator",
"created", "created",
@@ -5698,6 +5700,9 @@ func init() {
}, },
"reference": { "reference": {
"type": "string" "type": "string"
},
"type": {
"type": "string"
} }
} }
}, },
@@ -6816,6 +6821,12 @@ func init() {
"format": "int64", "format": "int64",
"example": 123 "example": 123
}, },
"logs": {
"type": "array",
"items": {
"$ref": "#/definitions/LogEntry"
}
},
"modified": { "modified": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
@@ -7668,7 +7679,8 @@ func init() {
"created": "2021-12-12T12:12:12.000000012Z", "created": "2021-12-12T12:12:12.000000012Z",
"creator": "bob", "creator": "bob",
"message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.", "message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.",
"reference": "tickets/294511" "reference": "tickets/294511",
"type": "manual"
} }
] ]
} }
@@ -12708,6 +12720,7 @@ func init() {
"LogEntry": { "LogEntry": {
"type": "object", "type": "object",
"required": [ "required": [
"type",
"reference", "reference",
"creator", "creator",
"created", "created",
@@ -12726,6 +12739,9 @@ func init() {
}, },
"reference": { "reference": {
"type": "string" "type": "string"
},
"type": {
"type": "string"
} }
} }
}, },
@@ -13844,6 +13860,12 @@ func init() {
"format": "int64", "format": "int64",
"example": 123 "example": 123
}, },
"logs": {
"type": "array",
"items": {
"$ref": "#/definitions/LogEntry"
}
},
"modified": { "modified": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
+1 -1
View File
@@ -233,7 +233,7 @@ func TestService(t *testing.T) {
args: args{method: "GET", url: "/api/logs/tickets%252F294511"}, args: args{method: "GET", url: "/api/logs/tickets%252F294511"},
want: want{ want: want{
status: 200, status: 200,
body: []interface{}{map[string]interface{}{"created": "2021-12-12T12:12:12.000000012Z", "creator": "bob", "message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.", "reference": "tickets/294511"}}, body: []interface{}{map[string]interface{}{"created": "2021-12-12T12:12:12.000000012Z", "creator": "bob", "message": "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim.", "reference": "tickets/294511", "type": "manual"}},
}, },
}, },
{ {
+1 -1
View File
@@ -55,7 +55,7 @@ require (
go.etcd.io/bbolt v1.3.6 // indirect go.etcd.io/bbolt v1.3.6 // indirect
go.mongodb.org/mongo-driver v1.7.4 // indirect go.mongodb.org/mongo-driver v1.7.4 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.0.0-20211105192438-b53810dc28af golang.org/x/net v0.0.0-20211105192438-b53810dc28af // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c // indirect golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie" "github.com/gin-contrib/sessions/cookie"
"github.com/SecurityBrewery/catalyst/automation"
"github.com/SecurityBrewery/catalyst/bus" "github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/busservice"
"github.com/SecurityBrewery/catalyst/database" "github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/database/busdb" "github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models" "github.com/SecurityBrewery/catalyst/generated/models"
@@ -72,7 +72,7 @@ func New(hooks *hooks.Hooks, config *Config) (*Server, error) {
return nil, err return nil, err
} }
err = automation.New(config.Bus.APIUrl, config.InitialAPIKey, catalystBus, catalystDatabase) err = busservice.New(config.Bus.APIUrl, config.InitialAPIKey, catalystBus, catalystDatabase)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+10 -6
View File
@@ -10,25 +10,29 @@ import (
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/tickets" "github.com/SecurityBrewery/catalyst/generated/restapi/operations/tickets"
) )
func (s *Service) RunArtifact(ctx context.Context, params *tickets.RunArtifactParams) *api.Response { func (s *Service) RunArtifact(ctx context.Context, params *tickets.RunArtifactParams) (r *api.Response) {
artifact, err := s.database.ArtifactGet(ctx, params.ID, params.Name) artifact, err := s.database.ArtifactGet(ctx, params.ID, params.Name)
if err != nil { if err != nil {
return response(nil, err) return s.response(ctx, "RunArtifact", ticketID(params.ID), nil, err)
} }
jobID := uuid.NewString() jobID := uuid.NewString()
origin := &models.Origin{ArtifactOrigin: &models.ArtifactOrigin{TicketId: params.ID, Artifact: params.Name}} origin := &models.Origin{ArtifactOrigin: &models.ArtifactOrigin{TicketId: params.ID, Artifact: params.Name}}
return response(nil, s.bus.PublishJob(jobID, params.Automation, params.Name, &models.Context{Artifact: artifact}, origin)) err = s.bus.PublishJob(jobID, params.Automation, params.Name, &models.Context{Artifact: artifact}, origin)
return s.response(ctx, "RunArtifact", ticketID(params.ID), nil, err)
} }
func (s *Service) EnrichArtifact(ctx context.Context, params *tickets.EnrichArtifactParams) *api.Response { func (s *Service) EnrichArtifact(ctx context.Context, params *tickets.EnrichArtifactParams) *api.Response {
return response(s.database.EnrichArtifact(ctx, params.ID, params.Name, params.Data)) i, err := s.database.EnrichArtifact(ctx, params.ID, params.Name, params.Data)
return s.response(ctx, "EnrichArtifact", ticketID(params.ID), i, err)
} }
func (s *Service) SetArtifact(ctx context.Context, params *tickets.SetArtifactParams) *api.Response { func (s *Service) SetArtifact(ctx context.Context, params *tickets.SetArtifactParams) *api.Response {
return response(s.database.ArtifactUpdate(ctx, params.ID, params.Name, params.Artifact)) i, err := s.database.ArtifactUpdate(ctx, params.ID, params.Name, params.Artifact)
return s.response(ctx, "SetArtifact", ticketID(params.ID), i, err)
} }
func (s *Service) GetArtifact(ctx context.Context, params *tickets.GetArtifactParams) *api.Response { func (s *Service) GetArtifact(ctx context.Context, params *tickets.GetArtifactParams) *api.Response {
return response(s.database.ArtifactGet(ctx, params.ID, params.Name)) i, err := s.database.ArtifactGet(ctx, params.ID, params.Name)
return s.response(ctx, "GetArtifact", nil, i, err)
} }
+18 -5
View File
@@ -2,27 +2,40 @@ package service
import ( import (
"context" "context"
"fmt"
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/restapi/api" "github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/automations" "github.com/SecurityBrewery/catalyst/generated/restapi/operations/automations"
) )
func automationID(id string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.AutomationCollectionName, id))}
}
func (s *Service) CreateAutomation(ctx context.Context, params *automations.CreateAutomationParams) *api.Response { func (s *Service) CreateAutomation(ctx context.Context, params *automations.CreateAutomationParams) *api.Response {
return response(s.database.AutomationCreate(ctx, params.Automation)) i, err := s.database.AutomationCreate(ctx, params.Automation)
return s.response(ctx, "CreateAutomation", automationID(i.ID), i, err)
} }
func (s *Service) GetAutomation(ctx context.Context, params *automations.GetAutomationParams) *api.Response { func (s *Service) GetAutomation(ctx context.Context, params *automations.GetAutomationParams) *api.Response {
return response(s.database.AutomationGet(ctx, params.ID)) i, err := s.database.AutomationGet(ctx, params.ID)
return s.response(ctx, "GetAutomation", nil, i, err)
} }
func (s *Service) UpdateAutomation(ctx context.Context, params *automations.UpdateAutomationParams) *api.Response { func (s *Service) UpdateAutomation(ctx context.Context, params *automations.UpdateAutomationParams) *api.Response {
return response(s.database.AutomationUpdate(ctx, params.ID, params.Automation)) i, err := s.database.AutomationUpdate(ctx, params.ID, params.Automation)
return s.response(ctx, "UpdateAutomation", automationID(i.ID), i, err)
} }
func (s *Service) DeleteAutomation(ctx context.Context, params *automations.DeleteAutomationParams) *api.Response { func (s *Service) DeleteAutomation(ctx context.Context, params *automations.DeleteAutomationParams) *api.Response {
return response(nil, s.database.AutomationDelete(ctx, params.ID)) err := s.database.AutomationDelete(ctx, params.ID)
return s.response(ctx, "DeleteAutomation", automationID(params.ID), nil, err)
} }
func (s *Service) ListAutomations(ctx context.Context) *api.Response { func (s *Service) ListAutomations(ctx context.Context) *api.Response {
return response(s.database.AutomationList(ctx)) i, err := s.database.AutomationList(ctx)
return s.response(ctx, "ListAutomations", nil, i, err)
} }
+16 -6
View File
@@ -2,28 +2,38 @@ package service
import ( import (
"context" "context"
"fmt"
"github.com/arangodb/go-driver"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/models" "github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/generated/restapi/api" "github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/jobs" "github.com/SecurityBrewery/catalyst/generated/restapi/operations/jobs"
) )
func (s *Service) RunJob(_ context.Context, params *jobs.RunJobParams) *api.Response { func jobID(id string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.JobCollectionName, id))}
}
func (s *Service) RunJob(ctx context.Context, params *jobs.RunJobParams) *api.Response {
msgContext := &models.Context{} msgContext := &models.Context{}
jobID := uuid.NewString() newJobID := uuid.NewString()
return response(nil, s.bus.PublishJob(jobID, params.Job.Automation, params.Job.Payload, msgContext, params.Job.Origin)) return s.response(ctx, "RunJob", jobID(newJobID), nil, s.bus.PublishJob(newJobID, params.Job.Automation, params.Job.Payload, msgContext, params.Job.Origin))
} }
func (s *Service) GetJob(ctx context.Context, params *jobs.GetJobParams) *api.Response { func (s *Service) GetJob(ctx context.Context, params *jobs.GetJobParams) *api.Response {
return response(s.database.JobGet(ctx, params.ID)) i, err := s.database.JobGet(ctx, params.ID)
return s.response(ctx, "GetJob", nil, i, err)
} }
func (s *Service) ListJobs(ctx context.Context) *api.Response { func (s *Service) ListJobs(ctx context.Context) *api.Response {
return response(s.database.JobList(ctx)) i, err := s.database.JobList(ctx)
return s.response(ctx, "ListJobs", nil, i, err)
} }
func (s *Service) UpdateJob(ctx context.Context, params *jobs.UpdateJobParams) *api.Response { func (s *Service) UpdateJob(ctx context.Context, params *jobs.UpdateJobParams) *api.Response {
return response(s.database.JobUpdate(ctx, params.ID, params.Job)) i, err := s.database.JobUpdate(ctx, params.ID, params.Job)
return s.response(ctx, "UpdateJob", jobID(i.ID), i, err)
} }
+2 -1
View File
@@ -10,5 +10,6 @@ import (
func (s *Service) GetLogs(ctx context.Context, params *logs.GetLogsParams) *api.Response { func (s *Service) GetLogs(ctx context.Context, params *logs.GetLogsParams) *api.Response {
id, _ := url.QueryUnescape(params.Reference) id, _ := url.QueryUnescape(params.Reference)
return response(s.database.LogList(ctx, id)) i, err := s.database.LogList(ctx, id)
return s.response(ctx, "GetLogs", nil, i, err)
} }
+17 -6
View File
@@ -5,35 +5,46 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/arangodb/go-driver"
"github.com/xeipuuv/gojsonschema" "github.com/xeipuuv/gojsonschema"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/models" "github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/generated/restapi/api" "github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/playbooks" "github.com/SecurityBrewery/catalyst/generated/restapi/operations/playbooks"
) )
func playbookID(id string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.PlaybookCollectionName, id))}
}
func (s *Service) CreatePlaybook(ctx context.Context, params *playbooks.CreatePlaybookParams) *api.Response { func (s *Service) CreatePlaybook(ctx context.Context, params *playbooks.CreatePlaybookParams) *api.Response {
return response(s.database.PlaybookCreate(ctx, params.Playbook)) i, err := s.database.PlaybookCreate(ctx, params.Playbook)
return s.response(ctx, "CreatePlaybook", playbookID(i.ID), i, err)
} }
func (s *Service) GetPlaybook(ctx context.Context, params *playbooks.GetPlaybookParams) *api.Response { func (s *Service) GetPlaybook(ctx context.Context, params *playbooks.GetPlaybookParams) *api.Response {
return response(s.database.PlaybookGet(ctx, params.ID)) i, err := s.database.PlaybookGet(ctx, params.ID)
return s.response(ctx, "GetPlaybook", nil, i, err)
} }
func (s *Service) UpdatePlaybook(ctx context.Context, params *playbooks.UpdatePlaybookParams) *api.Response { func (s *Service) UpdatePlaybook(ctx context.Context, params *playbooks.UpdatePlaybookParams) *api.Response {
if err := validate(params.Playbook, models.PlaybookTemplateFormSchema); err != nil { if err := validate(params.Playbook, models.PlaybookTemplateFormSchema); err != nil {
return response(nil, err) return s.response(ctx, "UpdatePlaybook", nil, nil, err)
} }
return response(s.database.PlaybookUpdate(ctx, params.ID, params.Playbook)) i, err := s.database.PlaybookUpdate(ctx, params.ID, params.Playbook)
return s.response(ctx, "UpdatePlaybook", playbookID(i.ID), i, err)
} }
func (s *Service) DeletePlaybook(ctx context.Context, params *playbooks.DeletePlaybookParams) *api.Response { func (s *Service) DeletePlaybook(ctx context.Context, params *playbooks.DeletePlaybookParams) *api.Response {
return response(nil, s.database.PlaybookDelete(ctx, params.ID)) err := s.database.PlaybookDelete(ctx, params.ID)
return s.response(ctx, "DeletePlaybook", playbookID(params.ID), nil, err)
} }
func (s *Service) ListPlaybooks(ctx context.Context) *api.Response { func (s *Service) ListPlaybooks(ctx context.Context) *api.Response {
return response(s.database.PlaybookList(ctx)) i, err := s.database.PlaybookList(ctx)
return s.response(ctx, "ListPlaybooks", nil, i, err)
} }
func validate(e interface{}, schema *gojsonschema.Schema) error { func validate(e interface{}, schema *gojsonschema.Schema) error {
+14 -1
View File
@@ -1,6 +1,7 @@
package service package service
import ( import (
"context"
"errors" "errors"
"log" "log"
"net/http" "net/http"
@@ -10,6 +11,7 @@ import (
"github.com/SecurityBrewery/catalyst/bus" "github.com/SecurityBrewery/catalyst/bus"
"github.com/SecurityBrewery/catalyst/database" "github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/models" "github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/generated/restapi/api" "github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/storage" "github.com/SecurityBrewery/catalyst/storage"
@@ -30,11 +32,22 @@ func (s *Service) Healthy() bool {
return true return true
} }
func response(v interface{}, err error) *api.Response { func (s *Service) response(ctx context.Context, function string, ids []driver.DocumentID, v interface{}, err error) *api.Response {
if err != nil { if err != nil {
log.Println(err) log.Println(err)
return &api.Response{Code: httpStatus(err), Body: gin.H{"error": err.Error()}} return &api.Response{Code: httpStatus(err), Body: gin.H{"error": err.Error()}}
} }
if ids != nil {
userID := "unknown"
user, ok := busdb.UserFromContext(ctx)
if ok {
userID = user.ID
}
go s.bus.PublishRequest(userID, function, ids)
}
if v == nil { if v == nil {
return &api.Response{Code: http.StatusNoContent, Body: v} return &api.Response{Code: http.StatusNoContent, Body: v}
} }
+2 -1
View File
@@ -7,5 +7,6 @@ import (
) )
func (s *Service) GetStatistics(ctx context.Context) *api.Response { func (s *Service) GetStatistics(ctx context.Context) *api.Response {
return response(s.database.Statistics(ctx)) i, err := s.database.Statistics(ctx)
return s.response(ctx, "GetStatistics", nil, i, err)
} }
+2 -1
View File
@@ -7,5 +7,6 @@ import (
) )
func (s *Service) ListTasks(ctx context.Context) *api.Response { func (s *Service) ListTasks(ctx context.Context) *api.Response {
return response(s.database.TaskList(ctx)) i, err := s.database.TaskList(ctx)
return s.response(ctx, "ListTasks", nil, i, err)
} }
+18 -5
View File
@@ -2,27 +2,40 @@ package service
import ( import (
"context" "context"
"fmt"
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/restapi/api" "github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/templates" "github.com/SecurityBrewery/catalyst/generated/restapi/operations/templates"
) )
func templateID(s string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.TemplateCollectionName, s))}
}
func (s *Service) CreateTemplate(ctx context.Context, params *templates.CreateTemplateParams) *api.Response { func (s *Service) CreateTemplate(ctx context.Context, params *templates.CreateTemplateParams) *api.Response {
return response(s.database.TemplateCreate(ctx, params.Template)) i, err := s.database.TemplateCreate(ctx, params.Template)
return s.response(ctx, "CreateTemplate", templateID(i.ID), i, err)
} }
func (s *Service) GetTemplate(ctx context.Context, params *templates.GetTemplateParams) *api.Response { func (s *Service) GetTemplate(ctx context.Context, params *templates.GetTemplateParams) *api.Response {
return response(s.database.TemplateGet(ctx, params.ID)) i, err := s.database.TemplateGet(ctx, params.ID)
return s.response(ctx, "GetTemplate", nil, i, err)
} }
func (s *Service) UpdateTemplate(ctx context.Context, params *templates.UpdateTemplateParams) *api.Response { func (s *Service) UpdateTemplate(ctx context.Context, params *templates.UpdateTemplateParams) *api.Response {
return response(s.database.TemplateUpdate(ctx, params.ID, params.Template)) i, err := s.database.TemplateUpdate(ctx, params.ID, params.Template)
return s.response(ctx, "UpdateTemplate", templateID(i.ID), i, err)
} }
func (s *Service) DeleteTemplate(ctx context.Context, params *templates.DeleteTemplateParams) *api.Response { func (s *Service) DeleteTemplate(ctx context.Context, params *templates.DeleteTemplateParams) *api.Response {
return response(nil, s.database.TemplateDelete(ctx, params.ID)) err := s.database.TemplateDelete(ctx, params.ID)
return s.response(ctx, "DeleteTemplate", templateID(params.ID), nil, err)
} }
func (s *Service) ListTemplates(ctx context.Context) *api.Response { func (s *Service) ListTemplates(ctx context.Context) *api.Response {
return response(s.database.TemplateList(ctx)) i, err := s.database.TemplateList(ctx)
return s.response(ctx, "ListTemplates", nil, i, err)
} }
+28 -9
View File
@@ -4,39 +4,56 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/models" "github.com/SecurityBrewery/catalyst/generated/models"
"github.com/SecurityBrewery/catalyst/generated/restapi/api" "github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/tickets" "github.com/SecurityBrewery/catalyst/generated/restapi/operations/tickets"
) )
func ticketID(id int64) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%d", database.TicketCollectionName, id))}
}
func ticketIDs(ticketResponses []*models.TicketResponse) []driver.DocumentID {
var ids []driver.DocumentID
for _, ticketResponse := range ticketResponses {
ids = append(ids, ticketID(ticketResponse.ID)...)
}
return ids
}
func (s *Service) CreateTicket(ctx context.Context, params *tickets.CreateTicketParams) *api.Response { func (s *Service) CreateTicket(ctx context.Context, params *tickets.CreateTicketParams) *api.Response {
createdTickets, err := s.database.TicketBatchCreate(ctx, []*models.TicketForm{params.Ticket}) createdTickets, err := s.database.TicketBatchCreate(ctx, []*models.TicketForm{params.Ticket})
if len(createdTickets) > 0 { if len(createdTickets) > 0 {
return response(createdTickets[0], err) return s.response(ctx, "CreateTicket", ticketIDs(createdTickets), createdTickets[0], err)
} }
return response(nil, err) return s.response(ctx, "CreateTicket", ticketIDs(createdTickets), nil, err)
} }
func (s *Service) CreateTicketBatch(ctx context.Context, params *tickets.CreateTicketBatchParams) *api.Response { func (s *Service) CreateTicketBatch(ctx context.Context, params *tickets.CreateTicketBatchParams) *api.Response {
_, err := s.database.TicketBatchCreate(ctx, params.Ticket) ticketBatch, err := s.database.TicketBatchCreate(ctx, params.Ticket)
return response(nil, err) return s.response(ctx, "CreateTicketBatch", ticketIDs(ticketBatch), nil, err)
} }
func (s *Service) GetTicket(ctx context.Context, params *tickets.GetTicketParams) *api.Response { func (s *Service) GetTicket(ctx context.Context, params *tickets.GetTicketParams) *api.Response {
return response(s.database.TicketGet(ctx, params.ID)) ticket, err := s.database.TicketGet(ctx, params.ID)
return s.response(ctx, "GetTicket", nil, ticket, err)
} }
func (s *Service) UpdateTicket(ctx context.Context, params *tickets.UpdateTicketParams) *api.Response { func (s *Service) UpdateTicket(ctx context.Context, params *tickets.UpdateTicketParams) *api.Response {
return response(s.database.TicketUpdate(ctx, params.ID, params.Ticket)) ticket, err := s.database.TicketUpdate(ctx, params.ID, params.Ticket)
return s.response(ctx, "UpdateTicket", ticketID(ticket.ID), ticket, err)
} }
func (s *Service) DeleteTicket(ctx context.Context, params *tickets.DeleteTicketParams) *api.Response { func (s *Service) DeleteTicket(ctx context.Context, params *tickets.DeleteTicketParams) *api.Response {
if err := s.database.TicketDelete(ctx, params.ID); err != nil { if err := s.database.TicketDelete(ctx, params.ID); err != nil {
return response(nil, err) return s.response(ctx, "DeleteTicket", ticketID(params.ID), nil, err)
} }
_ = s.storage.DeleteBucket(fmt.Sprint(params.ID)) _ = s.storage.DeleteBucket(fmt.Sprint(params.ID))
return response(nil, nil) return s.response(ctx, "DeleteTicket", ticketID(params.ID), nil, nil)
} }
func (s *Service) ListTickets(ctx context.Context, params *tickets.ListTicketsParams) *api.Response { func (s *Service) ListTickets(ctx context.Context, params *tickets.ListTicketsParams) *api.Response {
@@ -48,5 +65,7 @@ func (s *Service) ListTickets(ctx context.Context, params *tickets.ListTicketsPa
if params.Type != nil && *params.Type != "" { if params.Type != nil && *params.Type != "" {
t = *params.Type t = *params.Type
} }
return response(s.database.TicketList(ctx, t, q, params.Sort, params.Desc, *params.Offset, *params.Count))
ticketList, err := s.database.TicketList(ctx, t, q, params.Sort, params.Desc, *params.Offset, *params.Count)
return s.response(ctx, "ListTickets", nil, ticketList, err)
} }
+30 -16
View File
@@ -8,67 +8,81 @@ import (
) )
func (s *Service) AddArtifact(ctx context.Context, params *tickets.AddArtifactParams) *api.Response { func (s *Service) AddArtifact(ctx context.Context, params *tickets.AddArtifactParams) *api.Response {
return response(s.database.AddArtifact(ctx, params.ID, params.Artifact)) i, err := s.database.AddArtifact(ctx, params.ID, params.Artifact)
return s.response(ctx, "AddArtifact", ticketID(params.ID), i, err)
} }
func (s *Service) RemoveArtifact(ctx context.Context, params *tickets.RemoveArtifactParams) *api.Response { func (s *Service) RemoveArtifact(ctx context.Context, params *tickets.RemoveArtifactParams) *api.Response {
return response(s.database.RemoveArtifact(ctx, params.ID, params.Name)) i, err := s.database.RemoveArtifact(ctx, params.ID, params.Name)
return s.response(ctx, "RemoveArtifact", ticketID(params.ID), i, err)
} }
func (s *Service) SetSchema(ctx context.Context, params *tickets.SetSchemaParams) *api.Response { func (s *Service) SetSchema(ctx context.Context, params *tickets.SetSchemaParams) *api.Response {
return response(s.database.SetTemplate(ctx, params.ID, params.Schema)) i, err := s.database.SetTemplate(ctx, params.ID, params.Schema)
return s.response(ctx, "SetSchema", ticketID(params.ID), i, err)
} }
func (s *Service) AddComment(ctx context.Context, params *tickets.AddCommentParams) *api.Response { func (s *Service) AddComment(ctx context.Context, params *tickets.AddCommentParams) *api.Response {
return response(s.database.AddComment(ctx, params.ID, params.Comment)) i, err := s.database.AddComment(ctx, params.ID, params.Comment)
return s.response(ctx, "AddComment", ticketID(params.ID), i, err)
} }
func (s *Service) RemoveComment(ctx context.Context, params *tickets.RemoveCommentParams) *api.Response { func (s *Service) RemoveComment(ctx context.Context, params *tickets.RemoveCommentParams) *api.Response {
return response(s.database.RemoveComment(ctx, params.ID, params.CommentID)) i, err := s.database.RemoveComment(ctx, params.ID, params.CommentID)
return s.response(ctx, "RemoveComment", ticketID(params.ID), i, err)
} }
func (s *Service) LinkTicket(ctx context.Context, params *tickets.LinkTicketParams) *api.Response { func (s *Service) LinkTicket(ctx context.Context, params *tickets.LinkTicketParams) *api.Response {
err := s.database.RelatedCreate(ctx, params.ID, params.LinkedID) err := s.database.RelatedCreate(ctx, params.ID, params.LinkedID)
if err != nil { if err != nil {
return response(nil, err) return s.response(ctx, "LinkTicket", ticketID(params.ID), nil, err)
} }
return s.GetTicket(ctx, &tickets.GetTicketParams{ID: params.ID}) i, err := s.database.TicketGet(ctx, params.ID)
return s.response(ctx, "LinkTicket", ticketID(params.ID), i, err)
} }
func (s *Service) UnlinkTicket(ctx context.Context, params *tickets.UnlinkTicketParams) *api.Response { func (s *Service) UnlinkTicket(ctx context.Context, params *tickets.UnlinkTicketParams) *api.Response {
err := s.database.RelatedRemove(ctx, params.ID, params.LinkedID) err := s.database.RelatedRemove(ctx, params.ID, params.LinkedID)
if err != nil { if err != nil {
return response(nil, err) return s.response(ctx, "UnlinkTicket", ticketID(params.ID), nil, err)
} }
return s.GetTicket(ctx, &tickets.GetTicketParams{ID: params.ID}) i, err := s.database.TicketGet(ctx, params.ID)
return s.response(ctx, "UnlinkTicket", ticketID(params.ID), i, err)
} }
func (s Service) SetReferences(ctx context.Context, params *tickets.SetReferencesParams) *api.Response { func (s Service) SetReferences(ctx context.Context, params *tickets.SetReferencesParams) *api.Response {
return response(s.database.SetReferences(ctx, params.ID, params.References)) i, err := s.database.SetReferences(ctx, params.ID, params.References)
return s.response(ctx, "SetReferences", ticketID(params.ID), i, err)
} }
func (s Service) LinkFiles(ctx context.Context, params *tickets.LinkFilesParams) *api.Response { func (s Service) LinkFiles(ctx context.Context, params *tickets.LinkFilesParams) *api.Response {
return response(s.database.LinkFiles(ctx, params.ID, params.Files)) i, err := s.database.LinkFiles(ctx, params.ID, params.Files)
return s.response(ctx, "LinkFiles", ticketID(params.ID), i, err)
} }
func (s Service) AddTicketPlaybook(ctx context.Context, params *tickets.AddTicketPlaybookParams) *api.Response { func (s Service) AddTicketPlaybook(ctx context.Context, params *tickets.AddTicketPlaybookParams) *api.Response {
return response(s.database.AddTicketPlaybook(ctx, params.ID, params.Playbook)) i, err := s.database.AddTicketPlaybook(ctx, params.ID, params.Playbook)
return s.response(ctx, "AddTicketPlaybook", ticketID(params.ID), i, err)
} }
func (s Service) RemoveTicketPlaybook(ctx context.Context, params *tickets.RemoveTicketPlaybookParams) *api.Response { func (s Service) RemoveTicketPlaybook(ctx context.Context, params *tickets.RemoveTicketPlaybookParams) *api.Response {
return response(s.database.RemoveTicketPlaybook(ctx, params.ID, params.PlaybookID)) i, err := s.database.RemoveTicketPlaybook(ctx, params.ID, params.PlaybookID)
return s.response(ctx, "RemoveTicketPlaybook", ticketID(params.ID), i, err)
} }
func (s Service) CompleteTask(ctx context.Context, params *tickets.CompleteTaskParams) *api.Response { func (s Service) CompleteTask(ctx context.Context, params *tickets.CompleteTaskParams) *api.Response {
return response(s.database.TaskComplete(ctx, params.ID, params.PlaybookID, params.TaskID, params.Data)) i, err := s.database.TaskComplete(ctx, params.ID, params.PlaybookID, params.TaskID, params.Data)
return s.response(ctx, "CompleteTask", ticketID(params.ID), i, err)
} }
func (s Service) SetTask(ctx context.Context, params *tickets.SetTaskParams) *api.Response { func (s Service) SetTask(ctx context.Context, params *tickets.SetTaskParams) *api.Response {
return response(s.database.TaskUpdate(ctx, params.ID, params.PlaybookID, params.TaskID, params.Task)) i, err := s.database.TaskUpdate(ctx, params.ID, params.PlaybookID, params.TaskID, params.Task)
return s.response(ctx, "SetTask", ticketID(params.ID), i, err)
} }
func (s *Service) RunTask(ctx context.Context, params *tickets.RunTaskParams) *api.Response { func (s *Service) RunTask(ctx context.Context, params *tickets.RunTaskParams) *api.Response {
return response(nil, s.database.TaskRun(ctx, params.ID, params.PlaybookID, params.TaskID)) err := s.database.TaskRun(ctx, params.ID, params.PlaybookID, params.TaskID)
return s.response(ctx, "RunTask", ticketID(params.ID), nil, err)
} }
+18 -5
View File
@@ -2,27 +2,40 @@ package service
import ( import (
"context" "context"
"fmt"
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/generated/restapi/api" "github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/tickettypes" "github.com/SecurityBrewery/catalyst/generated/restapi/operations/tickettypes"
) )
func ticketTypeID(id string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.TicketTypeCollectionName, id))}
}
func (s *Service) CreateTicketType(ctx context.Context, params *tickettypes.CreateTicketTypeParams) *api.Response { func (s *Service) CreateTicketType(ctx context.Context, params *tickettypes.CreateTicketTypeParams) *api.Response {
return response(s.database.TicketTypeCreate(ctx, params.Tickettype)) ticketType, err := s.database.TicketTypeCreate(ctx, params.Tickettype)
return s.response(ctx, "CreateTicketType", ticketTypeID(ticketType.ID), ticketType, err)
} }
func (s *Service) GetTicketType(ctx context.Context, params *tickettypes.GetTicketTypeParams) *api.Response { func (s *Service) GetTicketType(ctx context.Context, params *tickettypes.GetTicketTypeParams) *api.Response {
return response(s.database.TicketTypeGet(ctx, params.ID)) ticketType, err := s.database.TicketTypeGet(ctx, params.ID)
return s.response(ctx, "GetTicketType", nil, ticketType, err)
} }
func (s *Service) UpdateTicketType(ctx context.Context, params *tickettypes.UpdateTicketTypeParams) *api.Response { func (s *Service) UpdateTicketType(ctx context.Context, params *tickettypes.UpdateTicketTypeParams) *api.Response {
return response(s.database.TicketTypeUpdate(ctx, params.ID, params.Tickettype)) ticketType, err := s.database.TicketTypeUpdate(ctx, params.ID, params.Tickettype)
return s.response(ctx, "UpdateTicketType", ticketTypeID(ticketType.ID), ticketType, err)
} }
func (s *Service) DeleteTicketType(ctx context.Context, params *tickettypes.DeleteTicketTypeParams) *api.Response { func (s *Service) DeleteTicketType(ctx context.Context, params *tickettypes.DeleteTicketTypeParams) *api.Response {
return response(nil, s.database.TicketTypeDelete(ctx, params.ID)) err := s.database.TicketTypeDelete(ctx, params.ID)
return s.response(ctx, "DeleteTicketType", ticketTypeID(params.ID), nil, err)
} }
func (s *Service) ListTicketTypes(ctx context.Context) *api.Response { func (s *Service) ListTicketTypes(ctx context.Context) *api.Response {
return response(s.database.TicketTypeList(ctx)) ticketTypes, err := s.database.TicketTypeList(ctx)
return s.response(ctx, "ListTicketTypes", nil, ticketTypes, err)
} }
+4 -4
View File
@@ -14,24 +14,24 @@ import (
func (s *Service) GetSettings(ctx context.Context) *api.Response { func (s *Service) GetSettings(ctx context.Context) *api.Response {
user, ok := busdb.UserFromContext(ctx) user, ok := busdb.UserFromContext(ctx)
if !ok { if !ok {
return response(nil, errors.New("no user in context")) return s.response(ctx, "GetSettings", nil, nil, errors.New("no user in context"))
} }
setting, err := s.database.UserDataGet(ctx, user.ID) setting, err := s.database.UserDataGet(ctx, user.ID)
if err != nil { if err != nil {
return response(nil, err) return s.response(ctx, "GetSettings", nil, nil, err)
} }
settings := mergeSettings(s.settings, setting) settings := mergeSettings(s.settings, setting)
ticketTypeList, err := s.database.TicketTypeList(ctx) ticketTypeList, err := s.database.TicketTypeList(ctx)
if err != nil { if err != nil {
return response(nil, err) return s.response(ctx, "GetSettings", nil, nil, err)
} }
settings.TicketTypes = ticketTypeList settings.TicketTypes = ticketTypeList
return response(settings, nil) return s.response(ctx, "GetSettings", nil, settings, nil)
} }
func mergeSettings(globalSettings *models.Settings, user *models.UserDataResponse) *models.Settings { func mergeSettings(globalSettings *models.Settings, user *models.UserDataResponse) *models.Settings {
+20 -7
View File
@@ -3,36 +3,49 @@ package service
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/database/busdb" "github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/restapi/api" "github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/users" "github.com/SecurityBrewery/catalyst/generated/restapi/operations/users"
) )
func userID(id string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.UserCollectionName, id))}
}
func (s *Service) GetUser(ctx context.Context, params *users.GetUserParams) *api.Response { func (s *Service) GetUser(ctx context.Context, params *users.GetUserParams) *api.Response {
return response(s.database.UserGet(ctx, params.ID)) i, err := s.database.UserGet(ctx, params.ID)
return s.response(ctx, "GetUser", nil, i, err)
} }
func (s *Service) ListUsers(ctx context.Context) *api.Response { func (s *Service) ListUsers(ctx context.Context) *api.Response {
return response(s.database.UserList(ctx)) i, err := s.database.UserList(ctx)
return s.response(ctx, "ListUsers", nil, i, err)
} }
func (s *Service) CreateUser(ctx context.Context, params *users.CreateUserParams) *api.Response { func (s *Service) CreateUser(ctx context.Context, params *users.CreateUserParams) *api.Response {
return response(s.database.UserCreate(ctx, params.User)) i, err := s.database.UserCreate(ctx, params.User)
return s.response(ctx, "CreateUser", userID(i.ID), i, err)
} }
func (s *Service) DeleteUser(ctx context.Context, params *users.DeleteUserParams) *api.Response { func (s *Service) DeleteUser(ctx context.Context, params *users.DeleteUserParams) *api.Response {
return response(nil, s.database.UserDelete(ctx, params.ID)) err := s.database.UserDelete(ctx, params.ID)
return s.response(ctx, "DeleteUser", userID(params.ID), nil, err)
} }
func (s *Service) CurrentUser(ctx context.Context) *api.Response { func (s *Service) CurrentUser(ctx context.Context) *api.Response {
user, ok := busdb.UserFromContext(ctx) user, ok := busdb.UserFromContext(ctx)
if !ok { if !ok {
return response(nil, errors.New("no user in context")) return s.response(ctx, "CurrentUser", nil, nil, errors.New("no user in context"))
} }
return response(user, nil) return s.response(ctx, "CurrentUser", nil, user, nil)
} }
func (s *Service) UpdateUser(ctx context.Context, params *users.UpdateUserParams) *api.Response { func (s *Service) UpdateUser(ctx context.Context, params *users.UpdateUserParams) *api.Response {
return response(s.database.UserUpdate(ctx, params.ID, params.User)) i, err := s.database.UserUpdate(ctx, params.ID, params.User)
return s.response(ctx, "UpdateUser", userID(i.ID), i, err)
} }
+20 -7
View File
@@ -3,37 +3,50 @@ package service
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"github.com/arangodb/go-driver"
"github.com/SecurityBrewery/catalyst/database"
"github.com/SecurityBrewery/catalyst/database/busdb" "github.com/SecurityBrewery/catalyst/database/busdb"
"github.com/SecurityBrewery/catalyst/generated/restapi/api" "github.com/SecurityBrewery/catalyst/generated/restapi/api"
"github.com/SecurityBrewery/catalyst/generated/restapi/operations/userdata" "github.com/SecurityBrewery/catalyst/generated/restapi/operations/userdata"
) )
func userdataID(id string) []driver.DocumentID {
return []driver.DocumentID{driver.DocumentID(fmt.Sprintf("%s/%s", database.UserDataCollectionName, id))}
}
func (s *Service) GetUserData(ctx context.Context, params *userdata.GetUserDataParams) *api.Response { func (s *Service) GetUserData(ctx context.Context, params *userdata.GetUserDataParams) *api.Response {
return response(s.database.UserDataGet(ctx, params.ID)) userData, err := s.database.UserDataGet(ctx, params.ID)
return s.response(ctx, "GetUserData", nil, userData, err)
} }
func (s *Service) ListUserData(ctx context.Context) *api.Response { func (s *Service) ListUserData(ctx context.Context) *api.Response {
return response(s.database.UserDataList(ctx)) userData, err := s.database.UserDataList(ctx)
return s.response(ctx, "ListUserData", nil, userData, err)
} }
func (s *Service) UpdateUserData(ctx context.Context, params *userdata.UpdateUserDataParams) *api.Response { func (s *Service) UpdateUserData(ctx context.Context, params *userdata.UpdateUserDataParams) *api.Response {
return response(s.database.UserDataUpdate(ctx, params.ID, params.Userdata)) userData, err := s.database.UserDataUpdate(ctx, params.ID, params.Userdata)
return s.response(ctx, "UpdateUserData", userdataID(userData.ID), userData, err)
} }
func (s *Service) CurrentUserData(ctx context.Context) *api.Response { func (s *Service) CurrentUserData(ctx context.Context) *api.Response {
user, ok := busdb.UserFromContext(ctx) user, ok := busdb.UserFromContext(ctx)
if !ok { if !ok {
return response(nil, errors.New("no user in context")) return s.response(ctx, "CurrentUserData", userdataID(user.ID), nil, errors.New("no user in context"))
} }
return s.GetUserData(ctx, &userdata.GetUserDataParams{ID: user.ID}) userData, err := s.database.UserDataGet(ctx, user.ID)
return s.response(ctx, "GetUserData", nil, userData, err)
} }
func (s *Service) UpdateCurrentUserData(ctx context.Context, params *userdata.UpdateCurrentUserDataParams) *api.Response { func (s *Service) UpdateCurrentUserData(ctx context.Context, params *userdata.UpdateCurrentUserDataParams) *api.Response {
user, ok := busdb.UserFromContext(ctx) user, ok := busdb.UserFromContext(ctx)
if !ok { if !ok {
return response(nil, errors.New("no user in context")) return s.response(ctx, "UpdateCurrentUserData", userdataID(user.ID), nil, errors.New("no user in context"))
} }
return response(s.database.UserDataUpdate(ctx, user.ID, params.Userdata)) userData, err := s.database.UserDataUpdate(ctx, user.ID, params.Userdata)
return s.response(ctx, "UpdateCurrentUserData", userdataID(user.ID), userData, err)
} }
+1 -1
View File
@@ -77,7 +77,7 @@ func SetupTestData(ctx context.Context, db *database.Database) error {
return err return err
} }
if _, err := db.LogCreate(ctx, "tickets/294511", "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim."); err != nil { if _, err := db.LogCreate(ctx, "manual", "tickets/294511", "Fail run account resist lend solve incident centre priority temperature. Cause change distribution examine location technique shape partner milk customer. Rail tea plate soil report cook railway interpretation breath action. Exercise dream accept park conclusion addition shoot assistance may answer. Gold writer link stop combine hear power name commitment operation. Determine lifespan support grow degree henry exclude detail set religion. Direct library policy convention chain retain discover ride walk student. Gather proposal select march aspect play noise avoid encourage employ. Assessment preserve transport combine wish influence income guess run stand. Charge limit crime ignore statement foundation study issue stop claim."); err != nil {
return err return err
} }
+12
View File
@@ -612,6 +612,12 @@ export interface LogEntry {
* @memberof LogEntry * @memberof LogEntry
*/ */
reference: string; reference: string;
/**
*
* @type {string}
* @memberof LogEntry
*/
type: string;
} }
/** /**
* *
@@ -2026,6 +2032,12 @@ export interface TicketWithTickets {
* @memberof TicketWithTickets * @memberof TicketWithTickets
*/ */
id: number; id: number;
/**
*
* @type {Array<LogEntry>}
* @memberof TicketWithTickets
*/
logs?: Array<LogEntry>;
/** /**
* *
* @type {string} * @type {string}
+13 -3
View File
@@ -200,8 +200,15 @@
</v-btn> </v-btn>
</template> </template>
</v-textarea> </v-textarea>
<div v-for="(comment, id) in ticket.comments" :key="id" class="pb-2"> <div v-for="(comment, id) in logs(ticket)" :key="id" class="pb-2">
<v-card elevation="0" color="cards"> <div v-if="'type' in comment && comment.message !== 'AddComment'" style="text-align: center">
<span class="text--disabled" :title="comment.created">
{{ comment.message }} &middot;
<strong> {{ comment.creator }}</strong> &middot;
{{ relDate(comment.created) }}
</span>
</div>
<v-card v-else-if="!('type' in comment)" elevation="0" color="cards">
<v-card-subtitle class="pb-0"> <v-card-subtitle class="pb-0">
<strong> {{ comment.creator }}</strong> <strong> {{ comment.creator }}</strong>
<span class="text--disabled ml-3" :title="comment.created"> <span class="text--disabled ml-3" :title="comment.created">
@@ -791,7 +798,7 @@ import {
Task, Task,
Type, Type,
TypeColorEnum, TypeColorEnum,
TaskResponse, PlaybookResponse, UserResponse, TaskTypeEnum TaskResponse, PlaybookResponse, UserResponse, TaskTypeEnum, TicketWithTickets,
} from "../client"; } from "../client";
import {API} from "@/services/api"; import {API} from "@/services/api";
@@ -1520,6 +1527,9 @@ export default Vue.extend({
}); });
} }
return relDate; return relDate;
},
logs: function(ticket: TicketWithTickets) {
return this.lodash.reverse(this.lodash.sortBy(this.lodash.union(ticket.comments, ticket.logs), ['created']))
} }
}, },
mounted() { mounted() {
+2 -3
View File
@@ -6,7 +6,6 @@ import (
"net/http" "net/http"
"sync" "sync"
"github.com/arangodb/go-driver"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gobwas/ws" "github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil" "github.com/gobwas/ws/wsutil"
@@ -48,10 +47,10 @@ func handleWebSocket(catalystBus *bus.Bus) func(ctx *gin.Context) {
broker := websocketBroker{clients: map[string]chan []byte{}} broker := websocketBroker{clients: map[string]chan []byte{}}
// send all messages from bus to websocket // send all messages from bus to websocket
err := catalystBus.SubscribeUpdate(func(ids []driver.DocumentID) { err := catalystBus.SubscribeDatabaseUpdate(func(msg *bus.DatabaseUpdateMsg) {
b, err := json.Marshal(map[string]interface{}{ b, err := json.Marshal(map[string]interface{}{
"action": "update", "action": "update",
"ids": ids, "ids": msg.IDs,
}) })
if err != nil { if err != nil {
return return