Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions openmeter/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type App interface {
GetMetadata() map[string]string
GetListing() MarketplaceListing

GetEventAppData() (EventAppData, error)

UpdateAppConfig(ctx context.Context, input AppConfigUpdate) error

// ValidateCapabilities validates if the app can run for the given capabilities
Expand Down
22 changes: 21 additions & 1 deletion openmeter/app/custominvoicing/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,25 @@ func (c Configuration) Validate() error {
return nil
}

type App struct {
type Meta struct {
app.AppBase
Configuration
}

var _ app.EventAppParser = (*Meta)(nil)

func (m *Meta) FromEventAppData(event app.EventApp) error {
m.AppBase = event.AppBase

if err := event.AppData.ParseInto(&m.Configuration); err != nil {
return fmt.Errorf("error parsing app data: %w", err)
}

return nil
}

type App struct {
Meta

customInvoicingService Service
billingService billing.Service
Expand All @@ -64,6 +80,10 @@ func (a App) UpdateAppConfig(ctx context.Context, input app.AppConfigUpdate) err
})
}

func (a App) GetEventAppData() (app.EventAppData, error) {
return app.NewEventAppData(a.Configuration)
}

// InvoicingApp
// These are no-ops as whatever is meaningful, is handled via the http driver of the custominvoicing app.

Expand Down
10 changes: 6 additions & 4 deletions openmeter/app/custominvoicing/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ func (f *Factory) NewApp(ctx context.Context, appBase app.AppBase) (app.App, err
}

return App{
AppBase: appBase,
Configuration: cfg,
Meta: Meta{
AppBase: appBase,
Configuration: cfg,
},
customInvoicingService: f.customInvoicingService,
billingService: f.billingService,
}, nil
Expand All @@ -115,15 +117,15 @@ func (f *Factory) InstallApp(ctx context.Context, input app.AppFactoryInstallApp
return nil, fmt.Errorf("invalid input: %w", err)
}

appBase, err := f.customInvoicingService.CreateApp(ctx, CreateAppInput{
newApp, err := f.customInvoicingService.CreateApp(ctx, CreateAppInput{
Namespace: input.Namespace,
Name: input.Name,
})
if err != nil {
return nil, fmt.Errorf("failed to create app: %w", err)
}

return f.NewApp(ctx, appBase)
return f.NewApp(ctx, newApp.GetAppBase())
}

func (f *Factory) UninstallApp(ctx context.Context, input app.UninstallAppInput) error {
Expand Down
103 changes: 88 additions & 15 deletions openmeter/app/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,77 @@ package app

import (
"context"
"encoding/json"
"fmt"
"reflect"

"github.com/oklog/ulid/v2"

"github.com/openmeterio/openmeter/openmeter/event/metadata"
"github.com/openmeterio/openmeter/openmeter/session"
)

// EventAppParser should be implemented by the app's meta contents to be parsable from an EventApp
type EventAppParser interface {
FromEventAppData(EventApp) error
}

type EventAppData map[string]any

// NewEventAppData creates a new EventAppData from a given value
// TODO[later]: we need to refactor apps to be able to handle serialization more gracefully, e.g. having a proper
// union type for app instead of the interface
func NewEventAppData(v any) (EventAppData, error) {
jsonBytes, err := json.Marshal(v)
if err != nil {
return nil, err
}

var data EventAppData
if err := json.Unmarshal(jsonBytes, &data); err != nil {
return nil, err
}

return data, nil
}

// ParseInto parses the EventAppData into a given value, the value must be a pointer
func (e EventAppData) ParseInto(v any) error {
if rv := reflect.ValueOf(v); rv.Kind() != reflect.Ptr || rv.IsNil() {
return fmt.Errorf("target must be a non-nil pointer")
}

jsonBytes, err := json.Marshal(e)
if err != nil {
return err
}

if err := json.Unmarshal(jsonBytes, v); err != nil {
return err
}

return nil
}

type EventApp struct {
AppBase
AppData EventAppData `json:"appData"`
}

func NewEventApp(app App) (EventApp, error) {
appBase := app.GetAppBase()

appData, err := app.GetEventAppData()
if err != nil {
return EventApp{}, err
}

return EventApp{
AppBase: appBase,
AppData: appData,
}, nil
}

const (
AppEventSubsystem metadata.EventSubsystem = "app"
AppCreateEventName metadata.EventName = "app.created"
Expand All @@ -18,6 +81,8 @@ const (
)

// NewAppCreateEvent creates a new app create event
// TODO[later]: We should use eventApp instead of AppBase, but the creation flow is somewhat tricky to change as the flow
// is that the app calls the AppCreate without having the configuration presisted.
func NewAppCreateEvent(ctx context.Context, appBase AppBase) AppCreateEvent {
return AppCreateEvent{
AppBase: appBase,
Expand All @@ -27,8 +92,8 @@ func NewAppCreateEvent(ctx context.Context, appBase AppBase) AppCreateEvent {

// AppCreateEvent is an event that is emitted when an app is created
type AppCreateEvent struct {
AppBase AppBase `json:"appBase"`
UserID *string `json:"userId,omitempty"`
AppBase
UserID *string `json:"userId,omitempty"`
}

func (e AppCreateEvent) EventName() string {
Expand Down Expand Up @@ -58,24 +123,29 @@ func (e AppCreateEvent) Validate() error {
}

// NewAppUpdateEvent creates a new app update event
func NewAppUpdateEvent(ctx context.Context, appBase AppBase) AppUpdateEvent {
return AppUpdateEvent{
AppBase: appBase,
UserID: session.GetSessionUserID(ctx),
func NewAppUpdateEvent(ctx context.Context, app App) (AppUpdateEvent, error) {
eventApp, err := NewEventApp(app)
if err != nil {
return AppUpdateEvent{}, err
}

return AppUpdateEvent{
EventApp: eventApp,
UserID: session.GetSessionUserID(ctx),
}, nil
}

// AppUpdateEvent is an event that is emitted when an app is updated
type AppUpdateEvent struct {
AppBase AppBase `json:"appBase"`
UserID *string `json:"userId,omitempty"`
EventApp
UserID *string `json:"userId,omitempty"`
}

func (e AppUpdateEvent) EventName() string {
return metadata.GetEventName(metadata.EventType{
Subsystem: AppEventSubsystem,
Name: AppUpdateEventName,
Version: "v1",
Version: "v2",
})
}

Expand All @@ -100,24 +170,27 @@ func (e AppUpdateEvent) Validate() error {
}

// NewAppDeleteEvent creates a new app delete event
func NewAppDeleteEvent(ctx context.Context, appBase AppBase) AppDeleteEvent {
func NewAppDeleteEvent(ctx context.Context, app AppBase, appData EventAppData) AppDeleteEvent {
return AppDeleteEvent{
AppBase: appBase,
UserID: session.GetSessionUserID(ctx),
EventApp: EventApp{
AppBase: app,
AppData: appData,
},
UserID: session.GetSessionUserID(ctx),
}
}

// AppDeleteEvent is an event that is emitted when an app is deleted
type AppDeleteEvent struct {
AppBase AppBase `json:"appBase"`
UserID *string `json:"userId,omitempty"`
EventApp
UserID *string `json:"userId,omitempty"`
}

func (e AppDeleteEvent) EventName() string {
return metadata.GetEventName(metadata.EventType{
Subsystem: AppEventSubsystem,
Name: AppDeleteEventName,
Version: "v1",
Version: "v2",
})
}

Expand Down
22 changes: 20 additions & 2 deletions openmeter/app/httpdriver/customer.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ func (h *handler) customerAppToAPI(a app.CustomerApp) (api.CustomerAppData, erro
apiStripeCustomerAppData := api.StripeCustomerAppData{
Id: &appId,
Type: api.StripeCustomerAppDataTypeStripe,
App: lo.ToPtr(mapStripeAppToAPI(stripeApp)),
App: lo.ToPtr(mapStripeAppToAPI(stripeApp.Meta)),
StripeCustomerId: customerApp.StripeCustomerID,
StripeDefaultPaymentMethodId: customerApp.StripeDefaultPaymentMethodID,
}
Expand All @@ -361,7 +361,7 @@ func (h *handler) customerAppToAPI(a app.CustomerApp) (api.CustomerAppData, erro
return apiCustomerAppData, fmt.Errorf("error casting app to sandbox app")
}

apiApp := mapSandboxAppToAPI(sandboxApp)
apiApp := mapSandboxAppToAPI(sandboxApp.Meta)

apiSandboxCustomerAppData := api.SandboxCustomerAppData{
Id: &appId,
Expand All @@ -374,6 +374,24 @@ func (h *handler) customerAppToAPI(a app.CustomerApp) (api.CustomerAppData, erro
return apiCustomerAppData, fmt.Errorf("error converting to sandbox customer app: %w", err)
}

case appcustominvoicing.CustomerData:
customInvoicingApp, ok := a.App.(appcustominvoicing.App)
if !ok {
return apiCustomerAppData, fmt.Errorf("error casting app to custom invoicing app")
}

apiApp := mapCustomInvoicingAppToAPI(customInvoicingApp.Meta)

apiCustomInvoicingCustomerAppData := api.CustomInvoicingCustomerAppData{
Id: &appId,
Type: api.CustomInvoicingCustomerAppDataTypeCustomInvoicing,
App: &apiApp,
}

err := apiCustomerAppData.FromCustomInvoicingCustomerAppData(apiCustomInvoicingCustomerAppData)
if err != nil {
return apiCustomerAppData, fmt.Errorf("error converting to custom invoicing customer app: %w", err)
}
default:
return apiCustomerAppData, fmt.Errorf("unsupported customer data for app: %s", appId)
}
Expand Down
55 changes: 49 additions & 6 deletions openmeter/app/httpdriver/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func MapAppToAPI(item app.App) (api.App, error) {
stripeApp := item.(appstripeentityapp.App)

app := api.App{}
if err := app.FromStripeApp(mapStripeAppToAPI(stripeApp)); err != nil {
if err := app.FromStripeApp(mapStripeAppToAPI(stripeApp.Meta)); err != nil {
return app, err
}

Expand All @@ -28,7 +28,7 @@ func MapAppToAPI(item app.App) (api.App, error) {
sandboxApp := item.(appsandbox.App)

app := api.App{}
if err := app.FromSandboxApp(mapSandboxAppToAPI(sandboxApp)); err != nil {
if err := app.FromSandboxApp(mapSandboxAppToAPI(sandboxApp.Meta)); err != nil {
return app, err
}

Expand All @@ -37,7 +37,7 @@ func MapAppToAPI(item app.App) (api.App, error) {
customInvoicingApp := item.(appcustominvoicing.App)

app := api.App{}
if err := app.FromCustomInvoicingApp(mapCustomInvoicingAppToAPI(customInvoicingApp)); err != nil {
if err := app.FromCustomInvoicingApp(mapCustomInvoicingAppToAPI(customInvoicingApp.Meta)); err != nil {
return app, err
}

Expand All @@ -47,7 +47,7 @@ func MapAppToAPI(item app.App) (api.App, error) {
}
}

func mapSandboxAppToAPI(app appsandbox.App) api.SandboxApp {
func mapSandboxAppToAPI(app appsandbox.Meta) api.SandboxApp {
return api.SandboxApp{
Id: app.GetID().ID,
Type: api.SandboxAppTypeSandbox,
Expand All @@ -62,7 +62,7 @@ func mapSandboxAppToAPI(app appsandbox.App) api.SandboxApp {
}

func mapStripeAppToAPI(
stripeApp appstripeentityapp.App,
stripeApp appstripeentityapp.Meta,
) api.StripeApp {
apiStripeApp := api.StripeApp{
Id: stripeApp.GetID().ID,
Expand All @@ -88,7 +88,7 @@ func mapStripeAppToAPI(
return apiStripeApp
}

func mapCustomInvoicingAppToAPI(app appcustominvoicing.App) api.CustomInvoicingApp {
func mapCustomInvoicingAppToAPI(app appcustominvoicing.Meta) api.CustomInvoicingApp {
return api.CustomInvoicingApp{
Id: app.GetID().ID,
Type: api.CustomInvoicingAppTypeCustomInvoicing,
Expand All @@ -106,3 +106,46 @@ func mapCustomInvoicingAppToAPI(app appcustominvoicing.App) api.CustomInvoicingA
EnableIssuingSyncHook: app.Configuration.EnableIssuingSyncHook,
}
}

func MapEventAppToAPI(event app.EventApp) (api.App, error) {
switch event.GetType() {
case app.AppTypeStripe:
target := appstripeentityapp.App{}
if err := target.FromEventAppData(event); err != nil {
return api.App{}, err
}

app := api.App{}
if err := app.FromStripeApp(mapStripeAppToAPI(target.Meta)); err != nil {
return api.App{}, err
}

return app, nil
case app.AppTypeSandbox:
target := appsandbox.Meta{}
if err := target.FromEventAppData(event); err != nil {
return api.App{}, err
}

app := api.App{}
if err := app.FromSandboxApp(mapSandboxAppToAPI(target)); err != nil {
return api.App{}, err
}

return app, nil
case app.AppTypeCustomInvoicing:
target := appcustominvoicing.App{}
if err := target.FromEventAppData(event); err != nil {
return api.App{}, err
}

app := api.App{}
if err := app.FromCustomInvoicingApp(mapCustomInvoicingAppToAPI(target.Meta)); err != nil {
return api.App{}, err
}

return app, nil
default:
return api.App{}, fmt.Errorf("unsupported app type: %s", event.GetType())
}
}
Loading
Loading