11package subscriber
22
33import (
4+ "context"
5+
46 "github.com/amp-labs/connectors/common"
7+ "github.com/amp-labs/connectors/internal/datautils"
58)
69
710type (
11+ // RemoteSubscriptions describes the actual state of subscriptions in the remote system.
12+ // Each ObjectName maps to a list of remote SubscriptionResource items.
13+ RemoteSubscriptions RemoteSubsType
14+ RemoteSubsType = datautils.IndexedLists [ObjectName , SubscriptionResource ]
15+
816 // State describes the set of object events that the connector should
917 // try to achieve (desired) and that are also reported back as the observed outcome.
1018 //
@@ -22,3 +30,97 @@ func newState(objectNames []ObjectName) State {
2230
2331 return events
2432}
33+
34+ func (r RemoteSubscriptions ) toState () State {
35+ result := make (State )
36+
37+ for objectName , subscriptions := range r {
38+ eventSet := datautils .NewSet [common.SubscriptionEventType ]()
39+
40+ for _ , subscription := range subscriptions {
41+ eventSet .Add (subscription .ChangeType .EventTypes ())
42+ }
43+
44+ // List of events may be nil.
45+ var events []common.SubscriptionEventType
46+ if list := eventSet .List (); len (list ) != 0 {
47+ events = list
48+ }
49+
50+ result [objectName ] = common.ObjectEvents {
51+ Events : events ,
52+ WatchFields : nil ,
53+ WatchFieldsAll : false ,
54+ PassThroughEvents : nil ,
55+ }
56+ }
57+
58+ return result
59+ }
60+
61+ func (r RemoteSubscriptions ) subset (objects []ObjectName ) RemoteSubscriptions {
62+ result := make (RemoteSubscriptions )
63+
64+ names := datautils .NewSetFromList (objects )
65+ for name , subscriptions := range r {
66+ if names .Has (name ) {
67+ for _ , subscription := range subscriptions {
68+ RemoteSubsType (result ).Add (name , subscription )
69+ }
70+ }
71+ }
72+
73+ return result
74+ }
75+
76+ func (r RemoteSubscriptions ) getIDs () []SubscriptionID {
77+ result := make ([]SubscriptionID , 0 )
78+
79+ for _ , subscriptions := range r {
80+ for _ , subscription := range subscriptions {
81+ result = append (result , subscription .ID )
82+ }
83+ }
84+
85+ return result
86+ }
87+
88+ // fetchSubscriptions retrieves the current Microsoft Graph change‑notification subscriptions for the given objects.
89+ //
90+ // nolint:lll
91+ // According to https://learn.microsoft.com/en-us/graph/change-notifications-delivery-webhooks?tabs=http#subscription-request,
92+ // Graph should guard against duplicate subscriptions (same changeType and resource), but in practice
93+ // multiple subscriptions for the same combination can exist. Therefore, before blindly creating,
94+ // updating, or deleting subscriptions, this method first reads the current state and allows the caller
95+ // to reconcile the desired vs. actual subscriptions.
96+ func (s Strategy ) fetchSubscriptions (ctx context.Context ) (RemoteSubscriptions , error ) {
97+ url , err := s .getSubscriptionURL ()
98+ if err != nil {
99+ return nil , err
100+ }
101+
102+ response , err := s .client .Get (ctx , url .String ())
103+ if err != nil {
104+ return nil , err
105+ }
106+
107+ subscriptions , err := common.UnmarshalJSON [subscriptionResources ](response )
108+ if err != nil {
109+ return nil , err
110+ }
111+
112+ result := make (RemoteSubscriptions )
113+
114+ for _ , resource := range subscriptions .List {
115+ objectName := ObjectName (resource .Resource )
116+ RemoteSubsType (result ).Add (objectName , resource )
117+ }
118+
119+ return result , nil
120+ }
121+
122+ // subscriptionResources is the output of "GET /subscriptions".
123+ // https://learn.microsoft.com/en-us/graph/api/subscription-list?view=graph-rest-1.0&tabs=http#response-1
124+ type subscriptionResources struct {
125+ List []SubscriptionResource `json:"value"`
126+ }
0 commit comments