Passed
Pull Request — master (#2443)
by Tolga
03:29
created

internal/storage/postgres/tenantWriter.go   A

Size/Duplication

Total Lines 136
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 84
dl 0
loc 136
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
D postgres.*TenantWriter.DeleteTenant 0 70 12
A postgres.*TenantWriter.CreateTenant 0 25 3
A postgres.NewTenantWriter 0 4 1
1
package postgres
2
3
import (
4
	"context"
5
	"errors"
6
	"fmt"
7
	"log/slog"
8
	"strings"
9
	"time"
10
11
	"github.com/jackc/pgx/v5"
12
13
	"go.opentelemetry.io/otel/codes"
14
15
	"google.golang.org/protobuf/types/known/timestamppb"
16
17
	"github.com/Permify/permify/internal"
18
	"github.com/Permify/permify/internal/storage/postgres/utils"
19
	db "github.com/Permify/permify/pkg/database/postgres"
20
	base "github.com/Permify/permify/pkg/pb/base/v1"
21
)
22
23
// TenantWriter - Structure for Tenant Writer
24
type TenantWriter struct {
25
	database *db.Postgres
26
	// options
27
	txOptions pgx.TxOptions
28
}
29
30
// NewTenantWriter - Creates a new TenantWriter
31
func NewTenantWriter(database *db.Postgres) *TenantWriter {
32
	return &TenantWriter{
33
		database:  database,
34
		txOptions: pgx.TxOptions{IsoLevel: pgx.ReadCommitted, AccessMode: pgx.ReadWrite},
35
	}
36
}
37
38
// CreateTenant - Creates a new Tenant
39
func (w *TenantWriter) CreateTenant(ctx context.Context, id, name string) (result *base.Tenant, err error) {
40
	ctx, span := internal.Tracer.Start(ctx, "tenant-writer.create-tenant")
41
	defer span.End()
42
43
	slog.DebugContext(ctx, "creating new tenant", slog.Any("id", id), slog.Any("name", name))
44
45
	var createdAt time.Time
46
	err = w.database.WritePool.QueryRow(ctx, utils.InsertTenantTemplate, id, name).Scan(&createdAt)
47
	if err != nil {
48
		if strings.Contains(err.Error(), "duplicate key value") {
49
			span.RecordError(err)
50
			span.SetStatus(codes.Error, err.Error())
51
			slog.ErrorContext(ctx, "error encountered", slog.Any("error", err))
52
			return nil, errors.New(base.ErrorCode_ERROR_CODE_UNIQUE_CONSTRAINT.String())
53
		}
54
		return nil, utils.HandleError(ctx, span, err, base.ErrorCode_ERROR_CODE_EXECUTION)
55
	}
56
57
	slog.DebugContext(ctx, "successfully created Tenant", slog.Any("id", id), slog.Any("name", name), slog.Any("created_at", createdAt))
58
59
	return &base.Tenant{
60
		Id:        id,
61
		Name:      name,
62
		CreatedAt: timestamppb.New(createdAt),
63
	}, nil
64
}
65
66
// DeleteTenant - Deletes a Tenant
67
func (w *TenantWriter) DeleteTenant(ctx context.Context, tenantID string) (err error) {
68
	ctx, span := internal.Tracer.Start(ctx, "tenant-writer.delete-tenant")
69
	defer span.End()
70
71
	slog.DebugContext(ctx, "deleting tenant", slog.Any("tenant_id", tenantID))
72
73
	tx, err := w.database.WritePool.Begin(ctx)
74
	if err != nil {
75
		return utils.HandleError(ctx, span, err, base.ErrorCode_ERROR_CODE_EXECUTION)
76
	}
77
	defer tx.Rollback(ctx)
78
79
	// Check if tenant exists first
80
	var exists bool
81
	err = tx.QueryRow(ctx, "SELECT EXISTS(SELECT 1 FROM "+TenantsTable+" WHERE id = $1)", tenantID).Scan(&exists)
82
	if err != nil {
83
		return utils.HandleError(ctx, span, err, base.ErrorCode_ERROR_CODE_EXECUTION)
84
	}
85
	if !exists {
86
		return utils.HandleError(ctx, span, errors.New("tenant not found"), base.ErrorCode_ERROR_CODE_NOT_FOUND)
87
	}
88
89
	// Prepare batch operations for deleting tenant-related records from multiple tables
90
	tables := []string{BundlesTable, RelationTuplesTable, AttributesTable, SchemaDefinitionTable, TransactionsTable}
91
	batch := &pgx.Batch{}
92
	for _, table := range tables {
93
		query := fmt.Sprintf(utils.DeleteAllByTenantTemplate, table)
0 ignored issues
show
introduced by
can't check non-constant format in call to Sprintf
Loading history...
94
		batch.Queue(query, tenantID)
95
	}
96
	batch.Queue(utils.DeleteTenantTemplate, tenantID)
97
98
	// Execute the batch of delete queries
99
	br := tx.SendBatch(ctx, batch)
100
101
	for range tables {
102
		_, err := br.Exec()
103
		if err != nil {
104
			originalErr := err
105
			closeErr := br.Close()
106
			if closeErr != nil {
107
				return closeErr
108
			}
109
			// Don't commit on error, let defer tx.Rollback() handle it
110
			return utils.HandleError(ctx, span, originalErr, base.ErrorCode_ERROR_CODE_EXECUTION)
111
		}
112
	}
113
114
	// Execute the tenant deletion
115
	_, err = br.Exec()
116
	if err != nil {
117
		originalErr := err
118
		closeErr := br.Close()
119
		if closeErr != nil {
120
			return closeErr
121
		}
122
		// Don't commit on error, let defer tx.Rollback() handle it
123
		return utils.HandleError(ctx, span, originalErr, base.ErrorCode_ERROR_CODE_EXECUTION)
124
	}
125
126
	err = br.Close()
127
	if err != nil {
128
		return utils.HandleError(ctx, span, err, base.ErrorCode_ERROR_CODE_EXECUTION)
129
	}
130
131
	err = tx.Commit(ctx)
132
	if err != nil {
133
		return utils.HandleError(ctx, span, err, base.ErrorCode_ERROR_CODE_EXECUTION)
134
	}
135
136
	return nil
137
}
138