mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 19:17:32 +00:00
feat: project view (#90)
* init for views (spooler, handler) * init for views (spooler, handler) * start view in management * granted project * implement granted project view * search granted projects * fix search column * update all projects on project change * search roles * filter org * project members * project grant members * fix tests * application view * project grant search * mock * test appendevents * test appendevents * Update internal/view/query.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/eventstore/spooler/spooler.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/view/query.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * merge request changes * Update internal/project/repository/view/model/application.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * merge request changes * Project view sql (#92) * sql and configs * error handling * sql start in eventstore * on error handling, config * read user on members * Update internal/project/repository/view/application_view.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/application.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/application.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/application.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/application.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/application.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/application_query.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update pkg/management/api/grpc/project_grant_converter.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update pkg/management/api/grpc/project_grant_member_converter.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update pkg/management/api/grpc/project_grant_member_converter.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update pkg/management/api/grpc/project_member_converter.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update pkg/management/api/grpc/project_member_converter.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/granted_project.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * return caos errors * Update internal/project/repository/view/model/granted_project_query.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/project_grant_member.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/project_grant_member_query.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/project_member.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/project_member_query.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/project_role.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/project/repository/view/model/project_role_query.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update pkg/management/api/grpc/application_converter.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update pkg/management/api/grpc/application_converter.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update pkg/management/api/grpc/project_converter.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update pkg/management/api/grpc/project_converter.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update pkg/management/api/grpc/project_converter.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update pkg/management/api/grpc/project_converter.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * converter fix Co-authored-by: Livio Amstutz <livio.a@gmail.com> Co-authored-by: Silvan <silvan.reusser@gmail.com>
This commit is contained in:
@@ -1,9 +1,25 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/caos/zitadel/internal/config/types"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
type ViewConfig struct {
|
||||
SQL *types.SQL
|
||||
}
|
||||
|
||||
func Start(conf ViewConfig) (*sql.DB, *gorm.DB, error) {
|
||||
sqlClient, err := sql.Open("postgres", conf.SQL.ConnectionString())
|
||||
if err != nil {
|
||||
return nil, nil, errors.ThrowPreconditionFailed(err, "SQL-9qBtr", "unable to open database connection")
|
||||
}
|
||||
|
||||
client, err := gorm.Open("postgres", sqlClient)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return sqlClient, client, nil
|
||||
}
|
||||
|
@@ -14,6 +14,8 @@ var (
|
||||
expectedGetByQueryCaseSensitive = `SELECT \* FROM "%s" WHERE \(%s %s \$1\) LIMIT 1`
|
||||
expectedSave = `UPDATE "%s" SET "test" = \$1 WHERE "%s"."%s" = \$2`
|
||||
expectedRemove = `DELETE FROM "%s" WHERE \(%s = \$1\)`
|
||||
expectedRemoveByObject = `DELETE FROM "%s" WHERE "%s"."%s" = \$1`
|
||||
expectedRemoveByObjectMultiplePK = `DELETE FROM "%s" WHERE "%s"."%s" = \$1 AND "%s"."%s" = \$2`
|
||||
expectedSearch = `SELECT \* FROM "%s" OFFSET 0`
|
||||
expectedSearchCount = `SELECT count\(\*\) FROM "%s"`
|
||||
expectedSearchLimit = `SELECT \* FROM "%s" LIMIT %v OFFSET 0`
|
||||
@@ -94,10 +96,16 @@ func (key TestSearchKey) ToColumnName() string {
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
ID string `json:"-" gorm:"column:id;primary_key"`
|
||||
ID string `json:"-" gorm:"column:primary_id;primary_key"`
|
||||
Test string `json:"test" gorm:"column:test"`
|
||||
}
|
||||
|
||||
type TestMultiplePK struct {
|
||||
TestID string `gorm:"column:testId;primary_key"`
|
||||
HodorID string `gorm:"column:hodorId;primary_key"`
|
||||
Test string `gorm:"column:test"`
|
||||
}
|
||||
|
||||
type dbMock struct {
|
||||
db *gorm.DB
|
||||
mock sqlmock.Sqlmock
|
||||
@@ -201,7 +209,7 @@ func (db *dbMock) expectGetByQueryErr(table, key, method, value string, err erro
|
||||
}
|
||||
|
||||
func (db *dbMock) expectSave(table string, object Test) *dbMock {
|
||||
query := fmt.Sprintf(expectedSave, table, table, "id")
|
||||
query := fmt.Sprintf(expectedSave, table, table, "primary_id")
|
||||
db.mock.ExpectExec(query).
|
||||
WithArgs(object.Test, object.ID).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
@@ -227,6 +235,24 @@ func (db *dbMock) expectRemove(table, key, value string) *dbMock {
|
||||
return db
|
||||
}
|
||||
|
||||
func (db *dbMock) expectRemoveByObject(table string, object Test) *dbMock {
|
||||
query := fmt.Sprintf(expectedRemoveByObject, table, table, "primary_id")
|
||||
db.mock.ExpectExec(query).
|
||||
WithArgs(object.ID).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func (db *dbMock) expectRemoveByObjectMultiplePKs(table string, object TestMultiplePK) *dbMock {
|
||||
query := fmt.Sprintf(expectedRemoveByObjectMultiplePK, table, table, "testId", table, "hodorId")
|
||||
db.mock.ExpectExec(query).
|
||||
WithArgs(object.TestID, object.HodorID).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func (db *dbMock) expectRemoveErr(table, key, value string, err error) *dbMock {
|
||||
query := fmt.Sprintf(expectedRemove, table, key)
|
||||
db.mock.ExpectExec(query).
|
||||
|
@@ -12,10 +12,10 @@ const (
|
||||
)
|
||||
|
||||
type FailedEvent struct {
|
||||
ViewName string `gorm:"column:view_name;primary_key"`
|
||||
FailedSequnce uint64 `gorm:"column:failed_sequence;primary_key`
|
||||
FailureCount uint64 `gorm:"column:failure_count`
|
||||
ErrMsg uint64 `gorm:"column:err_msg`
|
||||
ViewName string `gorm:"column:view_name;primary_key"`
|
||||
FailedSequence uint64 `gorm:"column:failed_sequence;primary_key`
|
||||
FailureCount uint64 `gorm:"column:failure_count`
|
||||
ErrMsg string `gorm:"column:err_msg`
|
||||
}
|
||||
|
||||
type FailedEventSearchQuery struct {
|
||||
@@ -71,18 +71,18 @@ func LatestFailedEvent(db *gorm.DB, table, viewName string, sequence uint64) (*F
|
||||
failedEvent := new(FailedEvent)
|
||||
queries := []SearchQuery{
|
||||
FailedEventSearchQuery{Key: FAILEDEVENTKEY_VIEW_NAME, Method: model.SEARCHMETHOD_EQUALS_IGNORE_CASE, Value: viewName},
|
||||
FailedEventSearchQuery{Key: FAILEDEVENTKEY_FAILED_SEQUENCE, Method: model.SEARCHMETHOD_EQUALS_IGNORE_CASE, Value: sequence},
|
||||
FailedEventSearchQuery{Key: FAILEDEVENTKEY_FAILED_SEQUENCE, Method: model.SEARCHMETHOD_EQUALS, Value: sequence},
|
||||
}
|
||||
query := PrepareGetByQuery(table, queries...)
|
||||
err := query(db, sequence)
|
||||
err := query(db, failedEvent)
|
||||
|
||||
if err == nil {
|
||||
return failedEvent, nil
|
||||
}
|
||||
|
||||
if gorm.IsRecordNotFoundError(err) {
|
||||
if errors.IsNotFound(err) {
|
||||
failedEvent.ViewName = viewName
|
||||
failedEvent.FailedSequnce = sequence
|
||||
failedEvent.FailedSequence = sequence
|
||||
failedEvent.FailureCount = 0
|
||||
return failedEvent, nil
|
||||
}
|
||||
|
@@ -69,7 +69,7 @@ func SetQuery(query *gorm.DB, key ColumnKey, value interface{}, method model.Sea
|
||||
case model.SEARCHMETHOD_EQUALS_IGNORE_CASE:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-idu8e", "Starts with only possible for strings")
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-idu8e", "Equal ignore case only possible for strings")
|
||||
}
|
||||
query = query.Where("LOWER("+column+") = LOWER(?)", valueText)
|
||||
case model.SEARCHMETHOD_STARTS_WITH:
|
||||
@@ -81,7 +81,7 @@ func SetQuery(query *gorm.DB, key ColumnKey, value interface{}, method model.Sea
|
||||
case model.SEARCHMETHOD_STARTS_WITH_IGNORE_CASE:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-eidus", "Starts with only possible for strings")
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-eidus", "Starts with ignore case only possible for strings")
|
||||
}
|
||||
query = query.Where("LOWER("+column+") LIKE LOWER(?)", valueText+"%")
|
||||
case model.SEARCHMETHOD_CONTAINS:
|
||||
@@ -93,10 +93,11 @@ func SetQuery(query *gorm.DB, key ColumnKey, value interface{}, method model.Sea
|
||||
case model.SEARCHMETHOD_CONTAINS_IGNORE_CASE:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-eid73", "Contains with only possible for strings")
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-eid73", "Contains with ignore case only possible for strings")
|
||||
}
|
||||
query = query.Where("LOWER("+column+") LIKE LOWER(?)", "%"+valueText+"%")
|
||||
|
||||
case model.SEARCHMETHOD_NOT_EQUALS:
|
||||
query = query.Where(""+column+" <> ?", value)
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
|
@@ -58,7 +58,7 @@ func PrepareSave(table string) func(db *gorm.DB, object interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
func PrepareDelete(table string, key ColumnKey, id string) func(db *gorm.DB) error {
|
||||
func PrepareDeleteByKey(table string, key ColumnKey, id string) func(db *gorm.DB) error {
|
||||
return func(db *gorm.DB) error {
|
||||
err := db.Table(table).
|
||||
Where(fmt.Sprintf("%s = ?", key.ToColumnName()), id).
|
||||
@@ -70,3 +70,15 @@ func PrepareDelete(table string, key ColumnKey, id string) func(db *gorm.DB) err
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func PrepareDeleteByObject(table string, object interface{}) func(db *gorm.DB) error {
|
||||
return func(db *gorm.DB) error {
|
||||
err := db.Table(table).
|
||||
Delete(object).
|
||||
Error
|
||||
if err != nil {
|
||||
return caos_errs.ThrowInternal(err, "VIEW-lso9w", "could not delete object")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@@ -372,7 +372,90 @@ func TestPrepareDelete(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
getDelete := PrepareDelete(tt.args.table, tt.args.key, tt.args.value)
|
||||
getDelete := PrepareDeleteByKey(tt.args.table, tt.args.key, tt.args.value)
|
||||
err := getDelete(tt.db.db)
|
||||
|
||||
if !tt.res.wantErr && err != nil {
|
||||
t.Errorf("got wrong err should be nil: %v ", err)
|
||||
}
|
||||
|
||||
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if err := tt.db.mock.ExpectationsWereMet(); !tt.res.wantErr && err != nil {
|
||||
t.Errorf("there were unfulfilled expectations: %s", err)
|
||||
}
|
||||
|
||||
tt.db.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareDeleteByObject(t *testing.T) {
|
||||
type args struct {
|
||||
table string
|
||||
object interface{}
|
||||
}
|
||||
type res struct {
|
||||
result Test
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
db *dbMock
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"delete",
|
||||
mockDB(t).
|
||||
expectBegin(nil).
|
||||
expectRemoveByObject("TESTTABLE", Test{ID: "VALUE", Test: "TEST"}).
|
||||
expectCommit(nil),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
object: &Test{ID: "VALUE", Test: "TEST"},
|
||||
},
|
||||
res{
|
||||
result: Test{ID: "VALUE"},
|
||||
wantErr: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"delete multiple PK",
|
||||
mockDB(t).
|
||||
expectBegin(nil).
|
||||
expectRemoveByObjectMultiplePKs("TESTTABLE", TestMultiplePK{TestID: "TESTID", HodorID: "HODORID", Test: "TEST"}).
|
||||
expectCommit(nil),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
object: &TestMultiplePK{TestID: "TESTID", HodorID: "HODORID", Test: "TEST"},
|
||||
},
|
||||
res{
|
||||
wantErr: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"db error",
|
||||
mockDB(t).
|
||||
expectBegin(nil).
|
||||
expectRemoveErr("TESTTABLE", "id", "VALUE", gorm.ErrUnaddressable).
|
||||
expectCommit(nil),
|
||||
args{
|
||||
table: "TESTTABLE",
|
||||
object: &Test{ID: "VALUE", Test: "TEST"},
|
||||
},
|
||||
res{
|
||||
result: Test{ID: "VALUE"},
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsInternal,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
getDelete := PrepareDeleteByObject(tt.args.table, tt.args.object)
|
||||
err := getDelete(tt.db.db)
|
||||
|
||||
if !tt.res.wantErr && err != nil {
|
||||
|
@@ -51,7 +51,7 @@ func LatestSequence(db *gorm.DB, table, viewName string) (uint64, error) {
|
||||
return sequence.ActualSequence, nil
|
||||
}
|
||||
|
||||
if gorm.IsRecordNotFoundError(err) {
|
||||
if caos_errs.IsNotFound(err) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, caos_errs.ThrowInternalf(err, "VIEW-9LyCB", "unable to get latest sequence of %s", viewName)
|
||||
|
Reference in New Issue
Block a user