3
0

Hook svc level alteration management to DAL

This commit is contained in:
Tomaž Jerman
2023-06-04 09:03:04 +02:00
parent b86f67cf6f
commit 1eb360f43e
24 changed files with 631 additions and 186 deletions
-6
View File
@@ -26,12 +26,6 @@
# Default: sqlite3://file::memory:?cache=shared&mode=memory
# DB_DSN=sqlite3://file::memory:?cache=shared&mode=memory
###############################################################################
# Allow for irreversible changes to the database schema such as dropping columns and tables.
# Type: bool
# Default: <no value>
# DB_ALLOW_DESTRUCTIVE_SCHEMA_CHANGES=<no value>
###############################################################################
###############################################################################
# HTTP Client
+4 -1
View File
@@ -23,6 +23,7 @@ import (
apigwTypes "github.com/cortezaproject/corteza/server/pkg/apigw/types"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/corredor"
"github.com/cortezaproject/corteza/server/pkg/dal"
"github.com/cortezaproject/corteza/server/pkg/eventbus"
"github.com/cortezaproject/corteza/server/pkg/healthcheck"
"github.com/cortezaproject/corteza/server/pkg/http"
@@ -380,11 +381,13 @@ func (app *CortezaApp) InitServices(ctx context.Context) (err error) {
Limit: app.Opt.Limit,
Attachment: app.Opt.Attachment,
})
if err != nil {
return
}
// Add alteration management service the global DAL service
dal.Service().Alterations = service.DefaultDalSchemaAlteration
if app.Opt.Messagebus.Enabled {
// initialize all the queue handlers
messagebus.Service().Init(ctx, service.DefaultQueue)
+1 -5
View File
@@ -11,11 +11,7 @@ DB: schema.#optionsGroup & {
defaultValue: "sqlite3://file::memory:?cache=shared&mode=memory"
description: "Database connection string."
}
allow_destructive_schema_changes: {
type: "bool"
defaultGoExpr: "false"
description: "Allow for irreversible changes to the database schema such as dropping columns and tables."
}
}
title: "Connection to data store backend"
}
-1
View File
@@ -12,7 +12,6 @@ type (
SearchModels(ctx context.Context) (out dal.ModelSet, err error)
ReplaceModel(ctx context.Context, model *dal.Model) (err error)
RemoveModel(ctx context.Context, connectionID, ID uint64) (err error)
ReplaceModelAttribute(ctx context.Context, model *dal.Model, diff *dal.ModelDiff, hasRecords bool, trans ...dal.TransformationFunction) (err error)
GetConnectionByID(uint64) *dal.ConnectionWrap
+3 -39
View File
@@ -80,8 +80,7 @@ type (
ReplaceModel(context.Context, *dal.Model) error
RemoveModel(ctx context.Context, connectionID, ID uint64) error
ReplaceModelAttribute(ctx context.Context, model *dal.Model, diff *dal.ModelDiff, hasRecords bool, trans ...dal.TransformationFunction) (err error)
SearchModelIssues(ID uint64) []error
SearchModelIssues(ID uint64) []dal.Issue
}
)
@@ -307,15 +306,9 @@ func (svc module) procDal(m *types.Module) {
return
}
ii := svc.dal.SearchModelIssues(m.ID)
if len(ii) == 0 {
m.Issues = svc.dal.SearchModelIssues(m.ID)
if len(m.Issues) == 0 {
m.Issues = nil
return
}
m.Issues = make([]string, len(ii))
for i, err := range ii {
m.Issues[i] = err.Error()
}
}
@@ -336,10 +329,6 @@ func (svc module) Create(ctx context.Context, new *types.Module) (*types.Module,
}
}
if err != nil {
}
if ns, err = loadNamespace(ctx, s, new.NamespaceID); err != nil {
return err
}
@@ -633,9 +622,6 @@ func (svc module) updater(ctx context.Context, namespaceID, moduleID uint64, act
if err = DalModelReplace(ctx, svc.dal, ns, old, m); err != nil {
return err
}
if err = dalAttributeReplace(ctx, svc.dal, ns, old, m, hasRecords); err != nil {
return err
}
} else {
if err = svc.eventbus.WaitFor(ctx, event.ModuleAfterDelete(nil, old, ns)); err != nil {
return
@@ -1169,28 +1155,6 @@ func DalModelReplace(ctx context.Context, dmm dalModelManager, ns *types.Namespa
return
}
func dalAttributeReplace(ctx context.Context, dmm dalModelManager, ns *types.Namespace, old, new *types.Module, hasRecords bool) (err error) {
oldModel, err := ModulesToModelSet(dmm, ns, old)
if err != nil {
return
}
newModel, err := ModulesToModelSet(dmm, ns, new)
if err != nil {
return
}
diff := oldModel[0].Diff(newModel[0])
// TODO handle the fact that diff is a list of changes so the same field could be present more than once.
for _, d := range diff {
if err = dmm.ReplaceModelAttribute(ctx, oldModel[0], d, hasRecords); err != nil {
return
}
}
return
}
// Removes a connection from DAL service
func DalModelRemove(ctx context.Context, dmm dalModelManager, mm ...*types.Module) (err error) {
for _, m := range mm {
+1 -1
View File
@@ -32,7 +32,7 @@ type (
Labels map[string]string `json:"labels,omitempty"`
Issues []string `json:"issues,omitempty"`
Issues []dal.Issue `json:"issues,omitempty"`
NamespaceID uint64 `json:"namespaceID,string"`
+2 -4
View File
@@ -14,8 +14,7 @@ import (
type (
DBOpt struct {
DSN string `env:"DB_DSN"`
AllowDestructiveSchemaChanges bool `env:"DB_ALLOW_DESTRUCTIVE_SCHEMA_CHANGES"`
DSN string `env:"DB_DSN"`
}
HTTPClientOpt struct {
@@ -275,8 +274,7 @@ type (
// This function is auto-generated
func DB() (o *DBOpt) {
o = &DBOpt{
DSN: "sqlite3://file::memory:?cache=shared&mode=memory",
AllowDestructiveSchemaChanges: false,
DSN: "sqlite3://file::memory:?cache=shared&mode=memory",
}
// Custom defaults
+33 -13
View File
@@ -353,19 +353,24 @@ type (
// auxDalSchemaAlteration is an auxiliary structure used for transporting to/from RDBMS store
auxDalSchemaAlteration struct {
ID uint64 `db:"id"`
BatchID uint64 `db:"batchID"`
DependsOn uint64 `db:"dependsOn"`
Kind string `db:"kind"`
Params *systemType.DalSchemaAlterationParams `db:"params"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt *time.Time `db:"updated_at"`
DeletedAt *time.Time `db:"deleted_at"`
CompletedAt *time.Time `db:"completed_at"`
CreatedBy uint64 `db:"created_by"`
UpdatedBy uint64 `db:"updated_by"`
DeletedBy uint64 `db:"deleted_by"`
CompletedBy uint64 `db:"completed_by"`
ID uint64 `db:"id"`
BatchID uint64 `db:"batchID"`
DependsOn uint64 `db:"dependsOn"`
Resource string `db:"resource"`
ResourceType string `db:"resourceType"`
ConnectionID uint64 `db:"connectionID"`
Kind string `db:"kind"`
Params *systemType.DalSchemaAlterationParams `db:"params"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt *time.Time `db:"updated_at"`
DeletedAt *time.Time `db:"deleted_at"`
CompletedAt *time.Time `db:"completed_at"`
DismissedAt *time.Time `db:"dismissed_at"`
CreatedBy uint64 `db:"created_by"`
UpdatedBy uint64 `db:"updated_by"`
DeletedBy uint64 `db:"deleted_by"`
CompletedBy uint64 `db:"completed_by"`
DismissedBy uint64 `db:"dismissed_by"`
}
// auxDalSensitivityLevel is an auxiliary structure used for transporting to/from RDBMS store
@@ -1875,16 +1880,21 @@ func (aux *auxDalSchemaAlteration) encode(res *systemType.DalSchemaAlteration) (
aux.ID = res.ID
aux.BatchID = res.BatchID
aux.DependsOn = res.DependsOn
aux.Resource = res.Resource
aux.ResourceType = res.ResourceType
aux.ConnectionID = res.ConnectionID
aux.Kind = res.Kind
aux.Params = res.Params
aux.CreatedAt = res.CreatedAt
aux.UpdatedAt = res.UpdatedAt
aux.DeletedAt = res.DeletedAt
aux.CompletedAt = res.CompletedAt
aux.DismissedAt = res.DismissedAt
aux.CreatedBy = res.CreatedBy
aux.UpdatedBy = res.UpdatedBy
aux.DeletedBy = res.DeletedBy
aux.CompletedBy = res.CompletedBy
aux.DismissedBy = res.DismissedBy
return
}
@@ -1896,16 +1906,21 @@ func (aux auxDalSchemaAlteration) decode() (res *systemType.DalSchemaAlteration,
res.ID = aux.ID
res.BatchID = aux.BatchID
res.DependsOn = aux.DependsOn
res.Resource = aux.Resource
res.ResourceType = aux.ResourceType
res.ConnectionID = aux.ConnectionID
res.Kind = aux.Kind
res.Params = aux.Params
res.CreatedAt = aux.CreatedAt
res.UpdatedAt = aux.UpdatedAt
res.DeletedAt = aux.DeletedAt
res.CompletedAt = aux.CompletedAt
res.DismissedAt = aux.DismissedAt
res.CreatedBy = aux.CreatedBy
res.UpdatedBy = aux.UpdatedBy
res.DeletedBy = aux.DeletedBy
res.CompletedBy = aux.CompletedBy
res.DismissedBy = aux.DismissedBy
return
}
@@ -1917,16 +1932,21 @@ func (aux *auxDalSchemaAlteration) scan(row scanner) error {
&aux.ID,
&aux.BatchID,
&aux.DependsOn,
&aux.Resource,
&aux.ResourceType,
&aux.ConnectionID,
&aux.Kind,
&aux.Params,
&aux.CreatedAt,
&aux.UpdatedAt,
&aux.DeletedAt,
&aux.CompletedAt,
&aux.DismissedAt,
&aux.CreatedBy,
&aux.UpdatedBy,
&aux.DeletedBy,
&aux.CompletedBy,
&aux.DismissedBy,
)
}
+17 -1
View File
@@ -807,12 +807,28 @@ func DalSchemaAlterationFilter(d drivers.Dialect, f systemType.DalSchemaAlterati
ee = append(ee, expr)
}
if expr := stateNilComparison(d, "completed_at", f.Completed); expr != nil {
ee = append(ee, expr)
}
if expr := stateNilComparison(d, "dismissed_at", f.Dismissed); expr != nil {
ee = append(ee, expr)
}
if val := strings.TrimSpace(f.Kind); len(val) > 0 {
ee = append(ee, goqu.C("kind").Eq(f.Kind))
}
if ss := trimStringSlice(f.Resource); len(ss) > 0 {
ee = append(ee, goqu.C("resource").In(ss))
}
if len(f.AlterationID) > 0 {
ee = append(ee, goqu.C("alteration_id").In(f.AlterationID))
ee = append(ee, goqu.C("id").In(f.AlterationID))
}
if f.BatchID > 0 {
ee = append(ee, goqu.C("batch_id").Eq(f.BatchID))
}
return ee, f, err
+59 -39
View File
@@ -2438,18 +2438,23 @@ var (
dalSchemaAlterationSelectQuery = func(d goqu.DialectWrapper) *goqu.SelectDataset {
return d.Select(
"id",
"batchID",
"dependsOn",
"batch_id",
"depends_on",
"resource",
"resource_type",
"connection_id",
"kind",
"params",
"created_at",
"updated_at",
"deleted_at",
"completed_at",
"dismissed_at",
"created_by",
"updated_by",
"deleted_by",
"completed_by",
"dismissed_by",
).From(dalSchemaAlterationTable)
}
@@ -2459,19 +2464,24 @@ var (
dalSchemaAlterationInsertQuery = func(d goqu.DialectWrapper, res *systemType.DalSchemaAlteration) *goqu.InsertDataset {
return d.Insert(dalSchemaAlterationTable).
Rows(goqu.Record{
"id": res.ID,
"batchID": res.BatchID,
"dependsOn": res.DependsOn,
"kind": res.Kind,
"params": res.Params,
"created_at": res.CreatedAt,
"updated_at": res.UpdatedAt,
"deleted_at": res.DeletedAt,
"completed_at": res.CompletedAt,
"created_by": res.CreatedBy,
"updated_by": res.UpdatedBy,
"deleted_by": res.DeletedBy,
"completed_by": res.CompletedBy,
"id": res.ID,
"batch_id": res.BatchID,
"depends_on": res.DependsOn,
"resource": res.Resource,
"resource_type": res.ResourceType,
"connection_id": res.ConnectionID,
"kind": res.Kind,
"params": res.Params,
"created_at": res.CreatedAt,
"updated_at": res.UpdatedAt,
"deleted_at": res.DeletedAt,
"completed_at": res.CompletedAt,
"dismissed_at": res.DismissedAt,
"created_by": res.CreatedBy,
"updated_by": res.UpdatedBy,
"deleted_by": res.DeletedBy,
"completed_by": res.CompletedBy,
"dismissed_by": res.DismissedBy,
})
}
@@ -2485,18 +2495,23 @@ var (
OnConflict(
goqu.DoUpdate(target[1:],
goqu.Record{
"batchID": res.BatchID,
"dependsOn": res.DependsOn,
"kind": res.Kind,
"params": res.Params,
"created_at": res.CreatedAt,
"updated_at": res.UpdatedAt,
"deleted_at": res.DeletedAt,
"completed_at": res.CompletedAt,
"created_by": res.CreatedBy,
"updated_by": res.UpdatedBy,
"deleted_by": res.DeletedBy,
"completed_by": res.CompletedBy,
"batch_id": res.BatchID,
"depends_on": res.DependsOn,
"resource": res.Resource,
"resource_type": res.ResourceType,
"connection_id": res.ConnectionID,
"kind": res.Kind,
"params": res.Params,
"created_at": res.CreatedAt,
"updated_at": res.UpdatedAt,
"deleted_at": res.DeletedAt,
"completed_at": res.CompletedAt,
"dismissed_at": res.DismissedAt,
"created_by": res.CreatedBy,
"updated_by": res.UpdatedBy,
"deleted_by": res.DeletedBy,
"completed_by": res.CompletedBy,
"dismissed_by": res.DismissedBy,
},
),
)
@@ -2508,18 +2523,23 @@ var (
dalSchemaAlterationUpdateQuery = func(d goqu.DialectWrapper, res *systemType.DalSchemaAlteration) *goqu.UpdateDataset {
return d.Update(dalSchemaAlterationTable).
Set(goqu.Record{
"batchID": res.BatchID,
"dependsOn": res.DependsOn,
"kind": res.Kind,
"params": res.Params,
"created_at": res.CreatedAt,
"updated_at": res.UpdatedAt,
"deleted_at": res.DeletedAt,
"completed_at": res.CompletedAt,
"created_by": res.CreatedBy,
"updated_by": res.UpdatedBy,
"deleted_by": res.DeletedBy,
"completed_by": res.CompletedBy,
"batch_id": res.BatchID,
"depends_on": res.DependsOn,
"resource": res.Resource,
"resource_type": res.ResourceType,
"connection_id": res.ConnectionID,
"kind": res.Kind,
"params": res.Params,
"created_at": res.CreatedAt,
"updated_at": res.UpdatedAt,
"deleted_at": res.DeletedAt,
"completed_at": res.CompletedAt,
"dismissed_at": res.DismissedAt,
"created_by": res.CreatedBy,
"updated_by": res.UpdatedBy,
"deleted_by": res.DeletedBy,
"completed_by": res.CompletedBy,
"dismissed_by": res.DismissedBy,
}).
Where(dalSchemaAlterationPrimaryKeys(res))
}
+4
View File
@@ -11786,6 +11786,8 @@ func (Store) sortableDalSchemaAlterationFields() map[string]string {
"createdat": "created_at",
"deleted_at": "deleted_at",
"deletedat": "deleted_at",
"dismissed_at": "dismissed_at",
"dismissedat": "dismissed_at",
"id": "id",
"updated_at": "updated_at",
"updatedat": "updated_at",
@@ -11826,6 +11828,8 @@ func (s *Store) collectDalSchemaAlterationCursorValues(res *systemType.DalSchema
return res.DeletedAt
case "completedAt":
return res.CompletedAt
case "dismissedAt":
return res.DismissedAt
}
return nil
}
+23 -4
View File
@@ -17,12 +17,27 @@ dal_schema_alteration: {
id: schema.IdField
batchID: {
goType: "uint64"
storeIdent: "batch_id"
dal: { type: "ID" }
}
dependsOn: {
goType: "uint64"
storeIdent: "depends_on"
dal: { type: "Ref", refModelResType: "corteza::system:dal-schema-alteration" }
}
resource: {
storeIdent: "resource"
dal: { type: "Text", length: 256 }
}
resourceType: {
storeIdent: "resource_type"
dal: { type: "Text", length: 256 }
}
connectionID: {
goType: "uint64"
storeIdent: "connection_id"
dal: { type: "Ref", refModelResType: "corteza::system:dal-connection" }
}
kind: {
dal: { type: "Text", length: 256 }
@@ -38,10 +53,12 @@ dal_schema_alteration: {
updated_at: schema.SortableTimestampNilField
deleted_at: schema.SortableTimestampNilField
completed_at: schema.SortableTimestampNilField
dismissed_at: schema.SortableTimestampNilField
created_by: schema.AttributeUserRef
updated_by: schema.AttributeUserRef
deleted_by: schema.AttributeUserRef
completed_by: schema.AttributeUserRef
dismissed_by: schema.AttributeUserRef
}
indexes: {
@@ -62,15 +79,17 @@ dal_schema_alteration: {
filter: {
struct: {
alteration_id: {goType: "[]uint64", ident: "alterationID" }
batch_id: {goType: "[]uint64", ident: "batchID" }
resource: {goType: "[]string", ident: "resource" }
alteration_id: {goType: "[]uint64", ident: "alterationID", storeIdent: "id" }
batch_id: {goType: "uint64", ident: "batchID" }
kind: {}
deleted: {goType: "filter.State", storeIdent: "deleted_at"}
completed: {goType: "filter.State", storeIdent: "completed_at"}
dismissed: {goType: "filter.State", storeIdent: "dismissed_at"}
}
byValue: ["kind", "alteration_id"]
byNilState: ["deleted"]
byValue: ["kind", "resource", "alteration_id", "batch_id"]
byNilState: ["deleted", "completed", "dismissed"]
}
store: {
+44 -2
View File
@@ -1131,7 +1131,7 @@ var DalSchemaAlteration = &dal.Model{
&dal.Attribute{
Ident: "BatchID",
Type: &dal.TypeID{},
Store: &dal.CodecAlias{Ident: "batchID"},
Store: &dal.CodecAlias{Ident: "batch_id"},
},
&dal.Attribute{
@@ -1142,7 +1142,30 @@ var DalSchemaAlteration = &dal.Model{
ResourceType: "corteza::system:dal-schema-alteration",
},
},
Store: &dal.CodecAlias{Ident: "dependsOn"},
Store: &dal.CodecAlias{Ident: "depends_on"},
},
&dal.Attribute{
Ident: "Resource",
Type: &dal.TypeText{Length: 256},
Store: &dal.CodecAlias{Ident: "resource"},
},
&dal.Attribute{
Ident: "ResourceType",
Type: &dal.TypeText{Length: 256},
Store: &dal.CodecAlias{Ident: "resource_type"},
},
&dal.Attribute{
Ident: "ConnectionID",
Type: &dal.TypeRef{
RefAttribute: "id",
RefModel: &dal.ModelRef{
ResourceType: "corteza::system:dal-connection",
},
},
Store: &dal.CodecAlias{Ident: "connection_id"},
},
&dal.Attribute{
@@ -1185,6 +1208,12 @@ var DalSchemaAlteration = &dal.Model{
Store: &dal.CodecAlias{Ident: "completed_at"},
},
&dal.Attribute{
Ident: "DismissedAt", Sortable: true,
Type: &dal.TypeTimestamp{Nullable: true, Timezone: true, Precision: -1},
Store: &dal.CodecAlias{Ident: "dismissed_at"},
},
&dal.Attribute{
Ident: "CreatedBy",
Type: &dal.TypeRef{HasDefault: true,
@@ -1236,6 +1265,19 @@ var DalSchemaAlteration = &dal.Model{
},
Store: &dal.CodecAlias{Ident: "completed_by"},
},
&dal.Attribute{
Ident: "DismissedBy",
Type: &dal.TypeRef{HasDefault: true,
DefaultValue: 0,
RefAttribute: "id",
RefModel: &dal.ModelRef{
ResourceType: "corteza::system:user",
},
},
Store: &dal.CodecAlias{Ident: "dismissed_by"},
},
},
Indexes: dal.IndexSet{
+12
View File
@@ -973,6 +973,18 @@ endpoints:
parameters:
path: [ { type: uint64, name: alterationID, required: true, title: "Alteration ID" } ]
- name: apply
method: POST
title: Apply alterations
path: "/apply"
parameters:
get: [ { type: "[]uint64", name: alterationID, required: true, title: "Alteration ID" } ]
- name: dismiss
method: POST
title: Dismiss alterations
path: "/dismiss"
parameters:
get: [ { type: "[]uint64", name: alterationID, required: true, title: "Alteration ID" } ]
- title: Data access layer connections
path: "/dal/connections"
@@ -42,6 +42,8 @@ type (
FindByID(ctx context.Context, ID uint64) (*types.DalSchemaAlteration, error)
DeleteByID(ctx context.Context, ID uint64) error
UndeleteByID(ctx context.Context, ID uint64) error
Apply(context.Context, ...uint64) error
Dismiss(context.Context, ...uint64) error
}
)
@@ -94,6 +96,14 @@ func (ctrl DalSchemaAlteration) Undelete(ctx context.Context, r *request.DalSche
return api.OK(), ctrl.svc.UndeleteByID(ctx, r.AlterationID)
}
func (ctrl DalSchemaAlteration) Apply(ctx context.Context, r *request.DalSchemaAlterationApply) (interface{}, error) {
return api.OK(), ctrl.svc.Apply(ctx, r.AlterationID...)
}
func (ctrl DalSchemaAlteration) Dismiss(ctx context.Context, r *request.DalSchemaAlterationDismiss) (interface{}, error) {
return api.OK(), ctrl.svc.Dismiss(ctx, r.AlterationID...)
}
func (ctrl DalSchemaAlteration) makePayload(ctx context.Context, res *types.DalSchemaAlteration, err error) (*alterationPayload, error) {
if err != nil || res == nil {
return nil, err
@@ -23,6 +23,8 @@ type (
Read(context.Context, *request.DalSchemaAlterationRead) (interface{}, error)
Delete(context.Context, *request.DalSchemaAlterationDelete) (interface{}, error)
Undelete(context.Context, *request.DalSchemaAlterationUndelete) (interface{}, error)
Apply(context.Context, *request.DalSchemaAlterationApply) (interface{}, error)
Dismiss(context.Context, *request.DalSchemaAlterationDismiss) (interface{}, error)
}
// HTTP API interface
@@ -31,6 +33,8 @@ type (
Read func(http.ResponseWriter, *http.Request)
Delete func(http.ResponseWriter, *http.Request)
Undelete func(http.ResponseWriter, *http.Request)
Apply func(http.ResponseWriter, *http.Request)
Dismiss func(http.ResponseWriter, *http.Request)
}
)
@@ -98,6 +102,38 @@ func NewDalSchemaAlteration(h DalSchemaAlterationAPI) *DalSchemaAlteration {
return
}
api.Send(w, r, value)
},
Apply: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewDalSchemaAlterationApply()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Apply(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Dismiss: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewDalSchemaAlterationDismiss()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Dismiss(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
}
@@ -110,5 +146,7 @@ func (h DalSchemaAlteration) MountRoutes(r chi.Router, middlewares ...func(http.
r.Get("/dal/schema/alterations/{alterationID}", h.Read)
r.Delete("/dal/schema/alterations/{alterationID}", h.Delete)
r.Post("/dal/schema/alterations/{alterationID}/undelete", h.Undelete)
r.Post("/dal/schema/alterations/apply", h.Apply)
r.Post("/dal/schema/alterations/dismiss", h.Dismiss)
})
}
@@ -85,6 +85,20 @@ type (
// Alteration ID
AlterationID uint64 `json:",string"`
}
DalSchemaAlterationApply struct {
// AlterationID GET parameter
//
// Alteration ID
AlterationID []uint64
}
DalSchemaAlterationDismiss struct {
// AlterationID GET parameter
//
// Alteration ID
AlterationID []uint64
}
)
// NewDalSchemaAlterationList request
@@ -291,3 +305,83 @@ func (r *DalSchemaAlterationUndelete) Fill(req *http.Request) (err error) {
return err
}
// NewDalSchemaAlterationApply request
func NewDalSchemaAlterationApply() *DalSchemaAlterationApply {
return &DalSchemaAlterationApply{}
}
// Auditable returns all auditable/loggable parameters
func (r DalSchemaAlterationApply) Auditable() map[string]interface{} {
return map[string]interface{}{
"alterationID": r.AlterationID,
}
}
// Auditable returns all auditable/loggable parameters
func (r DalSchemaAlterationApply) GetAlterationID() []uint64 {
return r.AlterationID
}
// Fill processes request and fills internal variables
func (r *DalSchemaAlterationApply) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["alterationID[]"]; ok {
r.AlterationID, err = payload.ParseUint64s(val), nil
if err != nil {
return err
}
} else if val, ok := tmp["alterationID"]; ok {
r.AlterationID, err = payload.ParseUint64s(val), nil
if err != nil {
return err
}
}
}
return err
}
// NewDalSchemaAlterationDismiss request
func NewDalSchemaAlterationDismiss() *DalSchemaAlterationDismiss {
return &DalSchemaAlterationDismiss{}
}
// Auditable returns all auditable/loggable parameters
func (r DalSchemaAlterationDismiss) Auditable() map[string]interface{} {
return map[string]interface{}{
"alterationID": r.AlterationID,
}
}
// Auditable returns all auditable/loggable parameters
func (r DalSchemaAlterationDismiss) GetAlterationID() []uint64 {
return r.AlterationID
}
// Fill processes request and fills internal variables
func (r *DalSchemaAlterationDismiss) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["alterationID[]"]; ok {
r.AlterationID, err = payload.ParseUint64s(val), nil
if err != nil {
return err
}
} else if val, ok := tmp["alterationID"]; ok {
r.AlterationID, err = payload.ParseUint64s(val), nil
if err != nil {
return err
}
}
}
return err
}
+1
View File
@@ -34,6 +34,7 @@ func MountRoutes() func(r chi.Router) {
handlers.NewDalConnection(DalConnection{}.New()).MountRoutes(r)
handlers.NewDalSensitivityLevel(SensitivityLevel{}.New()).MountRoutes(r)
handlers.NewDalDriver(DalDriver{}.New()).MountRoutes(r)
handlers.NewDalSchemaAlteration(DalSchemaAlteration{}.New()).MountRoutes(r)
handlers.NewRole(Role{}.New()).MountRoutes(r)
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
handlers.NewApplication(Application{}.New()).MountRoutes(r)
+3 -9
View File
@@ -40,7 +40,7 @@ type (
dalConnManager interface {
ReplaceConnection(context.Context, *dal.ConnectionWrap, bool) error
RemoveConnection(context.Context, uint64) error
SearchConnectionIssues(uint64) []error
SearchConnectionIssues(uint64) []dal.Issue
}
)
@@ -304,15 +304,9 @@ func (svc *dalConnection) procDal(ctx context.Context, c *types.DalConnection) {
return
}
ii := svc.dal.SearchConnectionIssues(c.ID)
if len(ii) == 0 {
c.Issues = svc.dal.SearchConnectionIssues(c.ID)
if len(c.Issues) == 0 {
c.Issues = nil
return
}
c.Issues = make([]string, len(ii))
for i, err := range ii {
c.Issues[i] = err.Error()
}
}
+240 -56
View File
@@ -4,7 +4,11 @@ import (
"context"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
intAuth "github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/dal"
"github.com/cortezaproject/corteza/server/pkg/errors"
"github.com/cortezaproject/corteza/server/pkg/filter"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/types"
)
@@ -13,19 +17,25 @@ type (
dalSchemaAlteration struct {
actionlog actionlog.Recorder
ac dalSchemaAlterationAccessController
dal dalAltManager
store store.Storer
}
dalAltManager interface {
ApplyAlteration(ctx context.Context, alts ...*dal.Alteration) (errs []error, err error)
}
dalSchemaAlterationAccessController interface {
}
)
func DalSchemaAlteration() *dalSchemaAlteration {
func DalSchemaAlteration(dal dalAltManager) *dalSchemaAlteration {
return &dalSchemaAlteration{
ac: DefaultAccessControl,
store: DefaultStore,
actionlog: DefaultActionlog,
dal: dal,
}
}
@@ -141,7 +151,207 @@ func (svc dalSchemaAlteration) UndeleteByID(ctx context.Context, dalSchemaAltera
}()
return svc.recordAction(ctx, uaProps, DalSchemaAlterationActionUndelete, err)
}
// ModelAlterations returns all non deleted, non completed, and non dismissed alterations for the given model
func (svc dalSchemaAlteration) ModelAlterations(ctx context.Context, m *dal.Model) (out []*dal.Alteration, err error) {
// @todo boilerplate code around this
aux, _, err := store.SearchDalSchemaAlterations(ctx, svc.store, types.DalSchemaAlterationFilter{
Resource: []string{m.Resource},
Deleted: filter.StateExcluded,
Completed: filter.StateExcluded,
Dismissed: filter.StateExcluded,
})
if err != nil {
return nil, err
}
for _, a := range aux {
t := &dal.Alteration{
ID: a.ID,
BatchID: a.BatchID,
DependsOn: a.DependsOn,
}
switch a.Kind {
case "attributeAdd":
t.AttributeAdd = a.Params.AttributeAdd
case "attributeDelete":
t.AttributeDelete = a.Params.AttributeDelete
case "attributeReType":
t.AttributeReType = a.Params.AttributeReType
case "attributeReEncode":
t.AttributeReEncode = a.Params.AttributeReEncode
case "modelAdd":
t.ModelAdd = a.Params.ModelAdd
case "modelDelete":
t.ModelDelete = a.Params.ModelDelete
}
out = append(out, t)
}
return
}
// @todo we can probably do some diffing here to make it more efficient; it'll do for now
func (svc dalSchemaAlteration) SetAlterations(ctx context.Context, m *dal.Model, stale []*dal.Alteration, aa ...*dal.Alteration) (err error) {
// @todo boilerplate code around this
return store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
// Firstly get rid of the old ones
aux := make([]*types.DalSchemaAlteration, len(stale))
for i, a := range stale {
aux[i] = &types.DalSchemaAlteration{
ID: a.ID,
}
}
err = store.DeleteDalSchemaAlteration(ctx, s, aux...)
if err != nil {
return
}
// Now add the new ones
cvt := make(types.DalSchemaAlterationSet, len(aa))
n := *now()
u := intAuth.GetIdentityFromContext(ctx).Identity()
for i, a := range aa {
t := &types.DalSchemaAlteration{
ID: a.ID,
BatchID: a.BatchID,
DependsOn: a.DependsOn,
ConnectionID: a.ConnectionID,
Resource: a.Resource,
ResourceType: a.ResourceType,
Params: &types.DalSchemaAlterationParams{},
}
// @todo we'd need to merge this with the old one probably just to preserve the metadata
t.ID = nextID()
t.CreatedAt = n
t.CreatedBy = u
switch {
case a.AttributeAdd != nil:
t.Kind = "attributeAdd"
t.Params.AttributeAdd = a.AttributeAdd
case a.AttributeDelete != nil:
t.Kind = "attributeDelete"
t.Params.AttributeDelete = a.AttributeDelete
case a.AttributeReType != nil:
t.Kind = "attributeReType"
t.Params.AttributeReType = a.AttributeReType
case a.AttributeReEncode != nil:
t.Kind = "attributeReEncode"
t.Params.AttributeReEncode = a.AttributeReEncode
case a.ModelAdd != nil:
t.Kind = "modelAdd"
t.Params.ModelAdd = a.ModelAdd
case a.ModelDelete != nil:
t.Kind = "modelDelete"
t.Params.ModelDelete = a.ModelDelete
}
cvt[i] = t
}
return store.UpsertDalSchemaAlteration(ctx, svc.store, cvt...)
})
}
func (svc dalSchemaAlteration) Apply(ctx context.Context, ids ...uint64) (err error) {
// @todo boilerplate (RBAC and such); we might have special RBAC rules for this;
// originally, we wanted to hook into ComposeModule resource (or any resource that defined a model)
aux, _, err := store.SearchDalSchemaAlterations(ctx, svc.store, types.DalSchemaAlterationFilter{
AlterationID: id.Strings(ids...),
})
if err != nil {
return
}
aa, err := svc.toPkgAlterations(ctx, svc.appliableAlterations(aux...)...)
if err != nil {
return
}
errors, err := svc.dal.ApplyAlteration(ctx, aa...)
if err != nil {
return
}
for i, e := range errors {
if e != nil {
aux[i].Error = e.Error()
} else {
aux[i].CompletedAt = now()
aux[i].CompletedBy = intAuth.GetIdentityFromContext(ctx).Identity()
}
}
return store.UpdateDalSchemaAlteration(ctx, svc.store, aux...)
}
func (svc dalSchemaAlteration) Dismiss(ctx context.Context, ids ...uint64) (err error) {
// @todo boilerplate (RBAC and such); we might have special RBAC rules for this;
// originally, we wanted to hook into ComposeModule resource (or any resource that defined a model)
return store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
alt, _, err := store.SearchDalSchemaAlterations(ctx, svc.store, types.DalSchemaAlterationFilter{
AlterationID: id.Strings(ids...),
})
if err != nil {
return
}
alt = svc.appliableAlterations(alt...)
for _, a := range alt {
a.Error = ""
a.DismissedAt = now()
a.DismissedBy = intAuth.GetIdentityFromContext(ctx).Identity()
}
return store.UpdateDalSchemaAlteration(ctx, s, alt...)
})
}
func (svc dalSchemaAlteration) appliableAlterations(aa ...*types.DalSchemaAlteration) (out types.DalSchemaAlterationSet) {
out = make(types.DalSchemaAlterationSet, 0, len(aa))
altIndex := make(map[uint64]*types.DalSchemaAlteration, len(aa))
for _, a := range aa {
altIndex[a.ID] = a
}
for _, a := range aa {
// Already completed
if a.CompletedAt != nil {
continue
}
// Dismissed for manual thing
if a.DismissedAt != nil {
continue
}
if a.DependsOn != 0 {
// Check if the dependency is completed or inside of the index
if _, ok := altIndex[a.DependsOn]; !ok {
continue
}
}
out = append(out, a)
}
return
}
func loadDalSchemaAlteration(ctx context.Context, s store.DalSchemaAlterations, ID uint64) (res *types.DalSchemaAlteration, err error) {
@@ -156,61 +366,35 @@ func loadDalSchemaAlteration(ctx context.Context, s store.DalSchemaAlterations,
return
}
// // uniqueDalSchemaAlterationCheck verifies dalSchemaAlteration's email, dalSchemaAlterationname and handle
// func uniqueDalSchemaAlterationCheck(ctx context.Context, s store.Storer, u *types.DalSchemaAlteration) (err error) {
// isUnique := func(field string) bool {
// f := types.DalSchemaAlterationFilter{
// // If dalSchemaAlteration exists and is deleted -- not a dup
// Deleted: filter.StateExcluded,
func (svc dalSchemaAlteration) toPkgAlterations(ctx context.Context, aa ...*types.DalSchemaAlteration) (out []*dal.Alteration, err error) {
out = make([]*dal.Alteration, len(aa))
for i, a := range aa {
t := &dal.Alteration{
ID: a.ID,
BatchID: a.BatchID,
DependsOn: a.DependsOn,
ConnectionID: a.ConnectionID,
Resource: a.Resource,
ResourceType: a.ResourceType,
}
// // If dalSchemaAlteration exists and is suspended -- duplicate
// Suspended: filter.StateInclusive,
// }
switch a.Kind {
case "attributeAdd":
t.AttributeAdd = a.Params.AttributeAdd
case "attributeDelete":
t.AttributeDelete = a.Params.AttributeDelete
case "attributeReType":
t.AttributeReType = a.Params.AttributeReType
case "attributeReEncode":
t.AttributeReEncode = a.Params.AttributeReEncode
case "modelAdd":
t.ModelAdd = a.Params.ModelAdd
case "modelDelete":
t.ModelDelete = a.Params.ModelDelete
}
// f.Limit = 1
out[i] = t
}
// switch field {
// case "email":
// if u.Email == "" {
// return true
// }
// f.Email = u.Email
// case "dalSchemaAlterationname":
// if u.DalSchemaAlterationname == "" {
// return true
// }
// f.DalSchemaAlterationname = u.DalSchemaAlterationname
// case "handle":
// if u.Handle == "" {
// return true
// }
// f.Handle = u.Handle
// }
// set, _, err := store.SearchDalSchemaAlterations(ctx, s, f)
// if err != nil || len(set) > 1 {
// // In case of error or multiple dalSchemaAlterations returned
// return false
// }
// return len(set) == 0 || set[0].ID == u.ID
// }
// if !isUnique("email") {
// return DalSchemaAlterationErrEmailNotUnique()
// }
// if !isUnique("dalSchemaAlterationname") {
// return DalSchemaAlterationErrDalSchemaAlterationnameNotUnique()
// }
// if !isUnique("handle") {
// return DalSchemaAlterationErrHandleNotUnique()
// }
// return nil
// }
return
}
+1 -1
View File
@@ -159,7 +159,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock
DefaultDalSensitivityLevel = SensitivityLevel(ctx, dal.Service())
DefaultDalSchemaAlteration = DalSchemaAlteration()
DefaultDalSchemaAlteration = DalSchemaAlteration(dal.Service())
if DefaultObjectStore == nil {
var (
+2 -1
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"time"
"github.com/cortezaproject/corteza/server/pkg/dal"
"github.com/cortezaproject/corteza/server/pkg/geolocation"
"github.com/cortezaproject/corteza/server/pkg/sql"
@@ -24,7 +25,7 @@ type (
// use this connection and how it affects their behaviour
Config ConnectionConfig `json:"config"`
Issues []string `json:"issues,omitempty"`
Issues []dal.Issue `json:"issues,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
+19 -3
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"time"
"github.com/cortezaproject/corteza/server/pkg/dal"
"github.com/cortezaproject/corteza/server/pkg/sql"
"github.com/cortezaproject/corteza/server/pkg/filter"
@@ -12,13 +13,18 @@ import (
type (
DalSchemaAlteration struct {
ID uint64 `json:"alterationID,string"`
BatchID uint64 `json:"batchID,string"`
DependsOn uint64 `json:"dependsOn,string"`
ID uint64 `json:"alterationID,string"`
BatchID uint64 `json:"batchID,string"`
DependsOn uint64 `json:"dependsOn,string"`
ConnectionID uint64 `json:"connectionID,string"`
Resource string `json:"resource"`
ResourceType string `json:"resourceType"`
Kind string `json:"kind"`
Params *DalSchemaAlterationParams `json:"params"`
Error string `json:"error,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
CreatedBy uint64 `json:"createdBy,string" `
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
@@ -28,18 +34,28 @@ type (
CompletedAt *time.Time `json:"completedAt,omitempty"`
CompletedBy uint64 `json:"completedBy,string,omitempty"`
DismissedAt *time.Time `json:"dismissedAt,omitempty"`
DismissedBy uint64 `json:"dismissedBy,string,omitempty"`
}
DalSchemaAlterationParams struct {
AttributeAdd *dal.AttributeAdd `json:"attributeAdd,omitempty"`
AttributeDelete *dal.AttributeDelete `json:"attributeDelete,omitempty"`
AttributeReType *dal.AttributeReType `json:"attributeReType,omitempty"`
AttributeReEncode *dal.AttributeReEncode `json:"attributeReEncode,omitempty"`
ModelAdd *dal.ModelAdd `json:"modelAdd,omitempty"`
ModelDelete *dal.ModelDelete `json:"modelDelete,omitempty"`
}
DalSchemaAlterationFilter struct {
AlterationID []string `json:"alterationID"`
BatchID uint64 `json:"batchID,string"`
Kind string `json:"kind"`
Resource []string `json:"resource"`
Deleted filter.State `json:"deleted"`
Completed filter.State `json:"completed"`
Dismissed filter.State `json:"dismissed"`
// Standard helpers for paging and sorting
filter.Sorting
+20
View File
@@ -834,6 +834,8 @@ func (r *DalSchemaAlteration) GetValue(name string, pos uint) (any, error) {
return r.CompletedAt, nil
case "completedBy", "CompletedBy":
return r.CompletedBy, nil
case "connectionID", "ConnectionID":
return r.ConnectionID, nil
case "createdAt", "CreatedAt":
return r.CreatedAt, nil
case "createdBy", "CreatedBy":
@@ -844,10 +846,18 @@ func (r *DalSchemaAlteration) GetValue(name string, pos uint) (any, error) {
return r.DeletedBy, nil
case "dependsOn", "DependsOn":
return r.DependsOn, nil
case "dismissedAt", "DismissedAt":
return r.DismissedAt, nil
case "dismissedBy", "DismissedBy":
return r.DismissedBy, nil
case "id", "ID":
return r.ID, nil
case "kind", "Kind":
return r.Kind, nil
case "resource", "Resource":
return r.Resource, nil
case "resourceType", "ResourceType":
return r.ResourceType, nil
case "updatedAt", "UpdatedAt":
return r.UpdatedAt, nil
case "updatedBy", "UpdatedBy":
@@ -865,6 +875,8 @@ func (r *DalSchemaAlteration) SetValue(name string, pos uint, value any) (err er
return cast2.TimePtr(value, &r.CompletedAt)
case "completedBy", "CompletedBy":
return cast2.Uint64(value, &r.CompletedBy)
case "connectionID", "ConnectionID":
return cast2.Uint64(value, &r.ConnectionID)
case "createdAt", "CreatedAt":
return cast2.Time(value, &r.CreatedAt)
case "createdBy", "CreatedBy":
@@ -875,10 +887,18 @@ func (r *DalSchemaAlteration) SetValue(name string, pos uint, value any) (err er
return cast2.Uint64(value, &r.DeletedBy)
case "dependsOn", "DependsOn":
return cast2.Uint64(value, &r.DependsOn)
case "dismissedAt", "DismissedAt":
return cast2.TimePtr(value, &r.DismissedAt)
case "dismissedBy", "DismissedBy":
return cast2.Uint64(value, &r.DismissedBy)
case "id", "ID":
return cast2.Uint64(value, &r.ID)
case "kind", "Kind":
return cast2.String(value, &r.Kind)
case "resource", "Resource":
return cast2.String(value, &r.Resource)
case "resourceType", "ResourceType":
return cast2.String(value, &r.ResourceType)
case "updatedAt", "UpdatedAt":
return cast2.TimePtr(value, &r.UpdatedAt)
case "updatedBy", "UpdatedBy":