From 55c1b7006451fe375d34d719896aa436feb99f39 Mon Sep 17 00:00:00 2001 From: mr Date: Thu, 28 May 2026 08:15:02 +0200 Subject: [PATCH] Set up --- controllers/booking.go | 74 ++++++++++++++++--- go.mod | 2 +- go.sum | 2 + infrastructure/nats/nats_handlers.go | 4 +- infrastructure/scheduler/scheduler.go | 2 + .../scheduling_resources/service.go | 19 +++++ infrastructure/session/session.go | 13 +++- 7 files changed, 97 insertions(+), 19 deletions(-) diff --git a/controllers/booking.go b/controllers/booking.go index fa812b3..394819c 100644 --- a/controllers/booking.go +++ b/controllers/booking.go @@ -9,6 +9,7 @@ import ( 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" + "cloud.o-forge.io/core/oc-lib/models/peer" beego "github.com/beego/beego/v2/server/web" "github.com/gorilla/websocket" "go.mongodb.org/mongo-driver/bson/primitive" @@ -21,41 +22,77 @@ type BookingController struct { var BookingExample booking.Booking +// bookingDestFilter builds a dest_peer_id filter for booking queries. +// Default: filter by self (master's own bookings). +// With nanoID: verify the ID belongs to a NANO peer, then filter by it. +// Returns nil when nanoID is provided but does not resolve to a NANO peer +// (caller should return HTTP 404 or empty result). +func bookingDestFilter(nanoID string) *dbs.Filters { + targetID := "" + if nanoID != "" { + d := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil).Search(&dbs.Filters{ + And: map[string][]dbs.Filter{ + "id": {{Operator: dbs.EQUAL.String(), Value: nanoID}}, + "relation": {{Operator: dbs.EQUAL.String(), Value: peer.NANO}}, + }, + }, "", false, 0, 1) + if len(d.Data) == 0 { + return nil + } + targetID = nanoID + } else { + if self, err := oclib.GetMySelf(); err == nil && self != nil { + targetID = self.GetID() + } + } + if targetID == "" { + return nil + } + return &dbs.Filters{ + And: map[string][]dbs.Filter{ + "dest_peer_id": {{Operator: dbs.EQUAL.String(), Value: targetID}}, + }, + } +} + // @Title Search // @Description search bookings // @Param start_date path string true "the word search you want to get" // @Param end_date path string true "the word search you want to get" // @Param is_draft query string false "draft wished" +// @Param nano_id query string false "nano peer UUID — if set, return bookings for that nano instead of self" // @Param offset query string false // @Param limit query string false // @Success 200 {workspace} models.workspace // @router /search/:start_date/:end_date [get] func (o *BookingController) Search() { - - /* - * This is a sample of how to use the search function - * The search function is used to search for data in the database - * The search function takes in a filter and a data type - * The filter is a struct that contains the search parameters - * The data type is an enum that specifies the type of data to search for - * The search function returns a list of data that matches the filter - * The data is then returned as a json object - */ - // store and return Id or post with UUID user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request) offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset")) limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit")) start_date, _ := time.ParseInLocation("2006-01-02", o.Ctx.Input.Param(":start_date"), time.UTC) end_date, _ := time.ParseInLocation("2006-01-02", o.Ctx.Input.Param(":end_date"), time.UTC) isDraft := o.Ctx.Input.Query("is_draft") + nanoID := o.Ctx.Input.Query("nano_id") sd := primitive.NewDateTimeFromTime(start_date) ed := primitive.NewDateTimeFromTime(end_date) fmt.Println("SEARCH START END", start_date, end_date) + df := bookingDestFilter(nanoID) + if nanoID != "" && df == nil { + o.Ctx.Output.SetStatus(http.StatusNotFound) + o.Data["json"] = map[string]string{"error": "nano peer not found: " + nanoID} + o.ServeJSON() + return + } f := dbs.Filters{ And: map[string][]dbs.Filter{ "expected_start_date": {{Operator: "gte", Value: sd}, {Operator: "lte", Value: ed}}, }, } + if df != nil { + for k, v := range df.And { + f.And[k] = v + } + } o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).Search(&f, "", isDraft == "true", int64(offset), int64(limit)) o.ServeJSON() } @@ -63,6 +100,7 @@ func (o *BookingController) Search() { // @Title GetAll // @Description find booking by id // @Param is_draft query string false "draft wished" +// @Param nano_id query string false "nano peer UUID — if set, return bookings for that nano instead of self" // @Param offset query string false // @Param limit query string false // @Success 200 {booking} models.booking @@ -72,7 +110,19 @@ func (o *BookingController) GetAll() { offset, _ := strconv.Atoi(o.Ctx.Input.Query("offset")) limit, _ := strconv.Atoi(o.Ctx.Input.Query("limit")) isDraft := o.Ctx.Input.Query("is_draft") - o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).LoadAll(isDraft == "true", int64(offset), int64(limit)) + nanoID := o.Ctx.Input.Query("nano_id") + df := bookingDestFilter(nanoID) + if nanoID != "" && df == nil { + o.Ctx.Output.SetStatus(http.StatusNotFound) + o.Data["json"] = map[string]string{"error": "nano peer not found: " + nanoID} + o.ServeJSON() + return + } + if df != nil { + o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).Search(df, "", isDraft == "true", int64(offset), int64(limit)) + } else { + o.Data["json"] = oclib.NewRequest(oclib.LibDataEnum(oclib.BOOKING), user, peerID, groups, nil).LoadAll(isDraft == "true", int64(offset), int64(limit)) + } o.ServeJSON() } diff --git a/go.mod b/go.mod index c39e32a..419e1f6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module oc-scheduler go 1.25.0 require ( - cloud.o-forge.io/core/oc-lib v0.0.0-20260429050913-47d487ea8011 + cloud.o-forge.io/core/oc-lib v0.0.0-20260527135023-cef23b5f307b github.com/beego/beego/v2 v2.3.8 github.com/google/uuid v1.6.0 github.com/robfig/cron v1.2.0 diff --git a/go.sum b/go.sum index 9236e4c..6bd4281 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ cloud.o-forge.io/core/oc-lib v0.0.0-20260428065508-e3fbe7688ad5 h1:CVwlE1JgIcTAv cloud.o-forge.io/core/oc-lib v0.0.0-20260428065508-e3fbe7688ad5/go.mod h1:JynnOb3eMr9VZW1mHq+Vsl3tzx6gPhPsGKpQD/dtEBc= cloud.o-forge.io/core/oc-lib v0.0.0-20260429050913-47d487ea8011 h1:owV5pQ+mS5xDCKEcGTO+BgsyYrKjkISL8LDsmjEb/3s= cloud.o-forge.io/core/oc-lib v0.0.0-20260429050913-47d487ea8011/go.mod h1:JynnOb3eMr9VZW1mHq+Vsl3tzx6gPhPsGKpQD/dtEBc= +cloud.o-forge.io/core/oc-lib v0.0.0-20260527135023-cef23b5f307b h1:TWhmHeurbBmdyevREh4+mHWOBehO2AK587RCIjCfvOc= +cloud.o-forge.io/core/oc-lib v0.0.0-20260527135023-cef23b5f307b/go.mod h1:JynnOb3eMr9VZW1mHq+Vsl3tzx6gPhPsGKpQD/dtEBc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= diff --git a/infrastructure/nats/nats_handlers.go b/infrastructure/nats/nats_handlers.go index faf7fe6..6662641 100644 --- a/infrastructure/nats/nats_handlers.go +++ b/infrastructure/nats/nats_handlers.go @@ -82,7 +82,7 @@ func handleCreateResource(resp tools.NATSResponse) { if bk.FromNano != "" { access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil) pp := access.LoadOne(bk.FromNano) - if p := pp.ToPeer(); p == nil || p.Relation == peer.NANO { + if p := pp.ToPeer(); p == nil || p.Relation != peer.NANO { return } access = oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.BOOKING), nil) @@ -107,7 +107,7 @@ func handleCreateResource(resp tools.NATSResponse) { if pr.FromNano != "" { access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil) pp := access.LoadOne(pr.FromNano) - if p := pp.ToPeer(); p == nil || p.Relation == peer.NANO { + if p := pp.ToPeer(); p == nil || p.Relation != peer.NANO { return } access = oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PURCHASE_RESOURCE), nil) diff --git a/infrastructure/scheduler/scheduler.go b/infrastructure/scheduler/scheduler.go index fe37445..bd6277a 100644 --- a/infrastructure/scheduler/scheduler.go +++ b/infrastructure/scheduler/scheduler.go @@ -226,6 +226,8 @@ func (ws *WorkflowSchedule) GenerateExecutions(wf *workflow.Workflow, isPreempti SelectedPartnerships: ws.SelectedPartnerships, SelectedBuyings: ws.SelectedBuyings, SelectedStrategies: ws.SelectedStrategies, + SelectedPaymentMode: ws.SelectedPaymentType, + SelectedBillingStrategy: ws.SelectedBillingStrategy, SelectedEmbeddedStorages: ws.SelectedEmbeddedStorages, Priority: 1, ExecutionsID: ws.UUID, diff --git a/infrastructure/scheduling_resources/service.go b/infrastructure/scheduling_resources/service.go index 47270f2..a116aa3 100644 --- a/infrastructure/scheduling_resources/service.go +++ b/infrastructure/scheduling_resources/service.go @@ -356,6 +356,25 @@ func (s *SchedulingResourcesService) HandleCreateBooking(bk *booking.Booking, ad return !bk.IsDraft } + // Booking destined for a known NANO — store as local proxy without planner + // check (the resource belongs to the NANO, not us). oc-discovery will + // forward to the NANO via a DTN-critical stream. + if bk.DestPeerID != "" && bk.DestPeerID != self.GetID() { + d := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil).Search(&dbs.Filters{ + And: map[string][]dbs.Filter{ + "id": {{Operator: dbs.EQUAL.String(), Value: bk.DestPeerID}}, + "relation": {{Operator: dbs.EQUAL.String(), Value: peer.NANO}}, + }, + }, "", false, 0, 1) + if len(d.Data) > 0 { + bk.IsDraft = true + if stored, _, err := booking.NewAccessor(adminReq).StoreOne(bk); err == nil { + time.AfterFunc(10*time.Minute, func() { DraftTimeout(stored.GetID(), tools.BOOKING) }) + } + } + return false + } + // New booking if !bk.ExpectedStartDate.IsZero() && bk.ExpectedStartDate.Before(time.Now().UTC()) { fmt.Println("HandleCreateBooking: start date in the past, discarding") diff --git a/infrastructure/session/session.go b/infrastructure/session/session.go index f113365..7223658 100644 --- a/infrastructure/session/session.go +++ b/infrastructure/session/session.go @@ -9,9 +9,10 @@ import ( "time" "cloud.o-forge.io/core/oc-lib/dbs" - "cloud.o-forge.io/core/oc-lib/models/bill" + "cloud.o-forge.io/core/oc-lib/models/billing" "cloud.o-forge.io/core/oc-lib/models/booking" "cloud.o-forge.io/core/oc-lib/models/common/enum" + "cloud.o-forge.io/core/oc-lib/models/common/pricing" "cloud.o-forge.io/core/oc-lib/models/order" "cloud.o-forge.io/core/oc-lib/models/resources/purchase_resource" "cloud.o-forge.io/core/oc-lib/models/utils" @@ -261,6 +262,7 @@ func GenerateOrder( ExecutionsID: executionsID, Purchases: []*purchase_resource.PurchaseResource{}, Bookings: []*booking.Booking{}, + Billing: map[pricing.BillingStrategy][]*booking.Booking{}, Status: enum.PENDING, } for _, purch := range purchases { @@ -268,14 +270,17 @@ func GenerateOrder( scheduling_resources.FromSchedulerObject(tools.PURCHASE_RESOURCE, purch).(*purchase_resource.PurchaseResource)) } for _, b := range bookings { - newOrder.Bookings = append(newOrder.Bookings, - scheduling_resources.FromSchedulerObject(tools.BOOKING, b).(*booking.Booking)) + bk := scheduling_resources.FromSchedulerObject(tools.BOOKING, b).(*booking.Booking) + newOrder.Bookings = append(newOrder.Bookings, bk) + // Groupe les bookings par stratégie de facturation pour permettre + // à DraftFirstBill de savoir quand et comment facturer chaque item. + newOrder.Billing[bk.BillingStrategy] = append(newOrder.Billing[bk.BillingStrategy], bk) } res, _, err := order.NewAccessor(request).StoreOne(newOrder) if err != nil { return "", err } - if _, err := bill.DraftFirstBill(res.(*order.Order), request); err != nil { + if _, err := billing.DraftFirstBill(res.(*order.Order), request); err != nil { return res.GetID(), err } return res.GetID(), nil