From 13df5cd8c1d3e1c925645b7ec732023ec5b38ed4 Mon Sep 17 00:00:00 2001 From: aman Date: Tue, 7 Oct 2025 12:13:39 +0530 Subject: [PATCH 1/2] feat: add audit logging for KYC upsert operations --- internal/store/postgres/kyc_repository.go | 98 ++++++++++++++++++----- 1 file changed, 79 insertions(+), 19 deletions(-) diff --git a/internal/store/postgres/kyc_repository.go b/internal/store/postgres/kyc_repository.go index fcc53ea41..2f8453e45 100644 --- a/internal/store/postgres/kyc_repository.go +++ b/internal/store/postgres/kyc_repository.go @@ -8,6 +8,7 @@ import ( "time" "github.com/doug-martin/goqu/v9" + "github.com/jmoiron/sqlx" "github.com/raystack/frontier/core/kyc" "github.com/raystack/frontier/pkg/db" ) @@ -76,25 +77,53 @@ func (r OrgKycRepository) Upsert(ctx context.Context, input kyc.KYC) (kyc.KYC, e var query string var params []interface{} - var kycModel KYC + // Struct to hold KYC data + org name + type kycWithOrgName struct { + KYC + OrgName string `db:"org_name"` + } + var result kycWithOrgName + _, err := r.GetByOrgID(ctx, input.OrgID) if err == nil { - //kyc for org exists, prepare UPDATE query - query, params, err = dialect.Update(TABLE_ORGANIZATIONS_KYC).Set(goqu.Record{ - "status": input.Status, - "link": input.Link, - }).Where(goqu.Ex{"org_id": input.OrgID}). - Returning(&kycModel).ToSQL() + // kyc for org exists, prepare UPDATE query with JOIN + query, params, err = dialect.Update(TABLE_ORGANIZATIONS_KYC). + From(TABLE_ORGANIZATIONS). + Set(goqu.Record{ + "status": input.Status, + "link": input.Link, + }). + Where( + goqu.Ex{ + TABLE_ORGANIZATIONS_KYC + ".org_id": input.OrgID, + }, + goqu.Ex{ + TABLE_ORGANIZATIONS + ".id": input.OrgID, + }, + ). + Returning( + goqu.I(TABLE_ORGANIZATIONS_KYC+".*"), + goqu.I(TABLE_ORGANIZATIONS+".name").As("org_name"), + ).ToSQL() if err != nil { return kyc.KYC{}, fmt.Errorf("%w: %w", queryErr, err) } } else if err.Error() == kyc.ErrNotExist.Error() { - //kyc for org doesn't exist, so we should prepare INSERT query - query, params, err = dialect.Insert(TABLE_ORGANIZATIONS_KYC).Rows(goqu.Record{ - "org_id": input.OrgID, - "status": input.Status, - "link": input.Link, - }).Returning(&kycModel).ToSQL() + // kyc for org doesn't exist, prepare INSERT query with subquery for org name + orgNameSubquery := dialect.From(TABLE_ORGANIZATIONS). + Select("name"). + Where(goqu.Ex{"id": input.OrgID}) + + query, params, err = dialect.Insert(TABLE_ORGANIZATIONS_KYC). + Rows(goqu.Record{ + "org_id": input.OrgID, + "status": input.Status, + "link": input.Link, + }). + Returning( + goqu.I(TABLE_ORGANIZATIONS_KYC+".*"), + orgNameSubquery.As("org_name"), + ).ToSQL() if err != nil { return kyc.KYC{}, fmt.Errorf("%w: %w", queryErr, err) } @@ -106,11 +135,42 @@ func (r OrgKycRepository) Upsert(ctx context.Context, input kyc.KYC) (kyc.KYC, e return kyc.KYC{}, fmt.Errorf("%w: %w", queryErr, err) } - err = r.dbc.WithTimeout(ctx, TABLE_ORGANIZATIONS_KYC, "Upsert", func(ctx context.Context) error { - return r.dbc.QueryRowxContext(ctx, query, params...).StructScan(&kycModel) - }) - - if err != nil { + if err = r.dbc.WithTxn(ctx, sql.TxOptions{}, func(tx *sqlx.Tx) error { + return r.dbc.WithTimeout(ctx, TABLE_ORGANIZATIONS_KYC, "Upsert", func(ctx context.Context) error { + if err := tx.QueryRowxContext(ctx, query, params...).StructScan(&result); err != nil { + return err + } + + // Determine event based on status + event := "kyc.unverified" + if result.Status { + event = "kyc.verified" + } + + auditRecord := BuildAuditRecord( + ctx, + event, + AuditResource{ + ID: result.OrgID, + Type: "organization", + Name: result.OrgName, + }, + &AuditTarget{ + ID: result.OrgID, + Type: "kyc", + Metadata: map[string]interface{}{ + "status": result.Status, + "link": result.Link, + }, + }, + result.OrgID, + nil, + result.UpdatedAt, + ) + + return InsertAuditRecordInTx(ctx, tx, auditRecord) + }) + }); err != nil { err = checkPostgresError(err) switch { case errors.Is(err, ErrCheckViolation): @@ -122,7 +182,7 @@ func (r OrgKycRepository) Upsert(ctx context.Context, input kyc.KYC) (kyc.KYC, e } } - return kycModel.transformToKyc() + return result.KYC.transformToKyc() } func (r OrgKycRepository) List(ctx context.Context) ([]kyc.KYC, error) { From b9978ae99d0a233573d62d3d1a3c34f92cf62401 Mon Sep 17 00:00:00 2001 From: aman Date: Tue, 7 Oct 2025 13:56:30 +0530 Subject: [PATCH 2/2] refactor: rearrange sql operations --- internal/store/postgres/kyc_repository.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/store/postgres/kyc_repository.go b/internal/store/postgres/kyc_repository.go index 2f8453e45..4fcda1794 100644 --- a/internal/store/postgres/kyc_repository.go +++ b/internal/store/postgres/kyc_repository.go @@ -88,11 +88,11 @@ func (r OrgKycRepository) Upsert(ctx context.Context, input kyc.KYC) (kyc.KYC, e if err == nil { // kyc for org exists, prepare UPDATE query with JOIN query, params, err = dialect.Update(TABLE_ORGANIZATIONS_KYC). - From(TABLE_ORGANIZATIONS). Set(goqu.Record{ "status": input.Status, "link": input.Link, }). + From(TABLE_ORGANIZATIONS). Where( goqu.Ex{ TABLE_ORGANIZATIONS_KYC + ".org_id": input.OrgID,