255 lines
7.3 KiB
Go
255 lines
7.3 KiB
Go
package controllers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
oclib "cloud.o-forge.io/core/oc-lib"
|
|
"cloud.o-forge.io/core/oc-lib/dbs"
|
|
"cloud.o-forge.io/core/oc-lib/models/booking"
|
|
libutils "cloud.o-forge.io/core/oc-lib/models/utils"
|
|
"cloud.o-forge.io/core/oc-lib/models/workflow_execution"
|
|
"cloud.o-forge.io/core/oc-lib/tools"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
)
|
|
|
|
// streamMsg is the envelope pushed over every stream WebSocket.
|
|
type streamMsg struct {
|
|
Type string `json:"type"` // "snapshot" | "update" | "delete"
|
|
Data interface{} `json:"data,omitempty"`
|
|
Deleted bool `json:"deleted,omitempty"`
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Booking stream
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// BookingStreamHandler opens a WebSocket that:
|
|
// 1. sends an immediate snapshot of matching bookings ("snapshot")
|
|
// 2. pushes each subsequent create/update/delete as an individual "update" or
|
|
// "delete" message.
|
|
//
|
|
// Query params (all optional):
|
|
//
|
|
// executions_id — filter to a specific scheduling session
|
|
// is_draft — "true" | "false" (omit = non-draft)
|
|
// start_date — YYYY-MM-DD (expected_start_date >=)
|
|
// end_date — YYYY-MM-DD (expected_start_date <=)
|
|
func BookingStreamHandler(w http.ResponseWriter, r *http.Request) {
|
|
user, peerID, groups := oclib.ExtractTokenInfoWs(*r)
|
|
|
|
q := r.URL.Query()
|
|
executionID := q.Get("execution_id")
|
|
executionsID := q.Get("executions_id")
|
|
isDraftStr := q.Get("is_draft")
|
|
onlyDraft := isDraftStr == "true"
|
|
filterDraft := isDraftStr != "" // whether the caller wants draft filtering at all
|
|
startDate, _ := time.ParseInLocation("2006-01-02", q.Get("start_date"), time.UTC)
|
|
endDate, _ := time.ParseInLocation("2006-01-02", q.Get("end_date"), time.UTC)
|
|
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
matchesFilter := func(b *booking.Booking) bool {
|
|
if executionID != "" && b.GetID() != executionID {
|
|
return false
|
|
}
|
|
if executionsID != "" && b.ExecutionsID != executionsID {
|
|
return false
|
|
}
|
|
if filterDraft && b.IsDraft != onlyDraft {
|
|
return false
|
|
}
|
|
if !startDate.IsZero() && b.ExpectedStartDate.Before(startDate) {
|
|
return false
|
|
}
|
|
if !endDate.IsZero() && b.ExpectedStartDate.After(endDate) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Build snapshot filters
|
|
andF := map[string][]dbs.Filter{}
|
|
if executionID != "" {
|
|
andF["id"] = []dbs.Filter{{Operator: dbs.EQUAL.String(), Value: executionID}}
|
|
}
|
|
if executionsID != "" {
|
|
andF["executions_id"] = []dbs.Filter{{Operator: dbs.EQUAL.String(), Value: executionsID}}
|
|
}
|
|
if !startDate.IsZero() {
|
|
andF["expected_start_date"] = append(andF["expected_start_date"],
|
|
dbs.Filter{Operator: "gte", Value: primitive.NewDateTimeFromTime(startDate)})
|
|
}
|
|
if !endDate.IsZero() {
|
|
andF["expected_start_date"] = append(andF["expected_start_date"],
|
|
dbs.Filter{Operator: "lte", Value: primitive.NewDateTimeFromTime(endDate)})
|
|
}
|
|
var snapshotFilter *dbs.Filters
|
|
if len(andF) > 0 {
|
|
snapshotFilter = &dbs.Filters{And: andF}
|
|
}
|
|
|
|
snapshot := oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).
|
|
Search(snapshotFilter, "", onlyDraft, 0, 10000)
|
|
if err := conn.WriteJSON(streamMsg{Type: "snapshot", Data: snapshot.Data}); err != nil {
|
|
return
|
|
}
|
|
|
|
changeCh, unsub := libutils.SubscribeChanges(tools.BOOKING)
|
|
defer unsub()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// detect client disconnect
|
|
closeCh := make(chan struct{})
|
|
go func() {
|
|
defer close(closeCh)
|
|
for {
|
|
if _, _, err := conn.ReadMessage(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case evt := <-changeCh:
|
|
b, ok := evt.Object.(*booking.Booking)
|
|
if !ok || !matchesFilter(b) {
|
|
continue
|
|
}
|
|
if evt.Deleted {
|
|
_ = conn.WriteJSON(streamMsg{Type: "delete", Data: b, Deleted: true})
|
|
} else {
|
|
_ = conn.WriteJSON(streamMsg{Type: "update", Data: b})
|
|
}
|
|
case <-closeCh:
|
|
return
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// WorkflowExecution stream
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// ExecutionStreamHandler opens a WebSocket that:
|
|
// 1. sends an immediate snapshot of matching executions ("snapshot")
|
|
// 2. pushes each subsequent create/update/delete as "update" or "delete".
|
|
//
|
|
// Query params (all optional):
|
|
//
|
|
// executions_id — filter to a specific scheduling session
|
|
// is_draft — "true" | "false" (omit = non-draft)
|
|
// start_date — YYYY-MM-DD (execution_date >=)
|
|
// end_date — YYYY-MM-DD (execution_date <=)
|
|
func ExecutionStreamHandler(w http.ResponseWriter, r *http.Request) {
|
|
user, peerID, groups := oclib.ExtractTokenInfoWs(*r)
|
|
|
|
q := r.URL.Query()
|
|
executionID := q.Get("execution_id")
|
|
executionsID := q.Get("executions_id")
|
|
isDraftStr := q.Get("is_draft")
|
|
onlyDraft := isDraftStr == "true"
|
|
filterDraft := isDraftStr != ""
|
|
startDate, _ := time.ParseInLocation("2006-01-02", q.Get("start_date"), time.UTC)
|
|
endDate, _ := time.ParseInLocation("2006-01-02", q.Get("end_date"), time.UTC)
|
|
|
|
conn, err := upgrader.Upgrade(w, r, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
matchesFilter := func(e *workflow_execution.WorkflowExecution) bool {
|
|
if executionID != "" && e.GetID() != executionID {
|
|
return false
|
|
}
|
|
if executionsID != "" && e.ExecutionsID != executionsID {
|
|
return false
|
|
}
|
|
if filterDraft && e.IsDraft != onlyDraft {
|
|
return false
|
|
}
|
|
if !startDate.IsZero() && e.ExecDate.Before(startDate) {
|
|
return false
|
|
}
|
|
if !endDate.IsZero() && e.ExecDate.After(endDate) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Build snapshot filters
|
|
andF := map[string][]dbs.Filter{}
|
|
if executionID != "" {
|
|
andF["id"] = []dbs.Filter{{Operator: dbs.EQUAL.String(), Value: executionID}}
|
|
}
|
|
if executionsID != "" {
|
|
andF["executions_id"] = []dbs.Filter{{Operator: dbs.EQUAL.String(), Value: executionsID}}
|
|
}
|
|
if !startDate.IsZero() {
|
|
andF["execution_date"] = append(andF["execution_date"],
|
|
dbs.Filter{Operator: "gte", Value: primitive.NewDateTimeFromTime(startDate)})
|
|
}
|
|
if !endDate.IsZero() {
|
|
andF["execution_date"] = append(andF["execution_date"],
|
|
dbs.Filter{Operator: "lte", Value: primitive.NewDateTimeFromTime(endDate)})
|
|
}
|
|
var snapshotFilter *dbs.Filters
|
|
if len(andF) > 0 {
|
|
snapshotFilter = &dbs.Filters{And: andF}
|
|
}
|
|
|
|
snapshot := oclib.NewRequest(oclib.LibDataEnum(oclib.WORKFLOW_EXECUTION), user, peerID, groups, nil).
|
|
Search(snapshotFilter, "", onlyDraft, 0, 10000)
|
|
if err := conn.WriteJSON(streamMsg{Type: "snapshot", Data: snapshot.Data}); err != nil {
|
|
return
|
|
}
|
|
|
|
changeCh, unsub := libutils.SubscribeChanges(tools.WORKFLOW_EXECUTION)
|
|
defer unsub()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
closeCh := make(chan struct{})
|
|
go func() {
|
|
defer close(closeCh)
|
|
for {
|
|
if _, _, err := conn.ReadMessage(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case evt := <-changeCh:
|
|
e, ok := evt.Object.(*workflow_execution.WorkflowExecution)
|
|
fmt.Println("CHANGE!", e, ok, matchesFilter(e))
|
|
if !ok || !matchesFilter(e) {
|
|
continue
|
|
}
|
|
if evt.Deleted {
|
|
_ = conn.WriteJSON(streamMsg{Type: "delete", Data: e, Deleted: true})
|
|
} else {
|
|
_ = conn.WriteJSON(streamMsg{Type: "update", Data: e})
|
|
}
|
|
case <-closeCh:
|
|
return
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|