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/storage/postgres/utils" |
18
|
|
|
db "github.com/Permify/permify/pkg/database/postgres" |
19
|
|
|
base "github.com/Permify/permify/pkg/pb/base/v1" |
20
|
|
|
) |
21
|
|
|
|
22
|
|
|
// TenantWriter - Structure for Tenant Writer |
23
|
|
|
type TenantWriter struct { |
24
|
|
|
database *db.Postgres |
25
|
|
|
// options |
26
|
|
|
txOptions pgx.TxOptions |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
// NewTenantWriter - Creates a new TenantWriter |
30
|
|
|
func NewTenantWriter(database *db.Postgres) *TenantWriter { |
31
|
|
|
return &TenantWriter{ |
32
|
|
|
database: database, |
33
|
|
|
txOptions: pgx.TxOptions{IsoLevel: pgx.ReadCommitted, AccessMode: pgx.ReadWrite}, |
34
|
|
|
} |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
// CreateTenant - Creates a new Tenant |
38
|
|
|
func (w *TenantWriter) CreateTenant(ctx context.Context, id, name string) (result *base.Tenant, err error) { |
39
|
|
|
ctx, span := tracer.Start(ctx, "tenant-writer.create-tenant") |
40
|
|
|
defer span.End() |
41
|
|
|
|
42
|
|
|
slog.DebugContext(ctx, "creating new tenant", slog.Any("id", id), slog.Any("name", name)) |
43
|
|
|
|
44
|
|
|
var createdAt time.Time |
45
|
|
|
err = w.database.WritePool.QueryRow(ctx, utils.InsertTenantTemplate, id, name).Scan(&createdAt) |
46
|
|
|
if err != nil { |
47
|
|
|
if strings.Contains(err.Error(), "duplicate key value") { |
48
|
|
|
span.RecordError(err) |
49
|
|
|
span.SetStatus(codes.Error, err.Error()) |
50
|
|
|
slog.ErrorContext(ctx, "error encountered", slog.Any("error", err)) |
51
|
|
|
return nil, errors.New(base.ErrorCode_ERROR_CODE_UNIQUE_CONSTRAINT.String()) |
52
|
|
|
} |
53
|
|
|
return nil, utils.HandleError(ctx, span, err, base.ErrorCode_ERROR_CODE_EXECUTION) |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
slog.DebugContext(ctx, "successfully created Tenant", slog.Any("id", id), slog.Any("name", name), slog.Any("created_at", createdAt)) |
57
|
|
|
|
58
|
|
|
return &base.Tenant{ |
59
|
|
|
Id: id, |
60
|
|
|
Name: name, |
61
|
|
|
CreatedAt: timestamppb.New(createdAt), |
62
|
|
|
}, nil |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
// DeleteTenant - Deletes a Tenant |
66
|
|
|
func (w *TenantWriter) DeleteTenant(ctx context.Context, tenantID string) (result *base.Tenant, err error) { |
67
|
|
|
ctx, span := tracer.Start(ctx, "tenant-writer.delete-tenant") |
68
|
|
|
defer span.End() |
69
|
|
|
|
70
|
|
|
slog.DebugContext(ctx, "deleting tenant", slog.Any("tenant_id", tenantID)) |
71
|
|
|
|
72
|
|
|
tx, err := w.database.WritePool.Begin(ctx) |
73
|
|
|
if err != nil { |
74
|
|
|
return nil, utils.HandleError(ctx, span, err, base.ErrorCode_ERROR_CODE_EXECUTION) |
75
|
|
|
} |
76
|
|
|
defer tx.Rollback(ctx) |
77
|
|
|
|
78
|
|
|
// Prepare batch operations for deleting tenant-related records from multiple tables |
79
|
|
|
tables := []string{"bundles", "relation_tuples", "attributes", "schema_definitions", "transactions"} |
80
|
|
|
batch := &pgx.Batch{} |
81
|
|
|
var totalDeleted int64 |
82
|
|
|
for _, table := range tables { |
83
|
|
|
query := fmt.Sprintf(utils.DeleteAllByTenantTemplate, table) |
|
|
|
|
84
|
|
|
batch.Queue(query, tenantID) |
85
|
|
|
} |
86
|
|
|
batch.Queue(utils.DeleteTenantTemplate, tenantID) |
87
|
|
|
|
88
|
|
|
// Execute the batch of delete queries |
89
|
|
|
br := tx.SendBatch(ctx, batch) |
90
|
|
|
|
91
|
|
|
for i := 0; i < len(tables); i++ { |
92
|
|
|
tag, err := br.Exec() |
93
|
|
|
if err != nil { |
94
|
|
|
err = br.Close() |
95
|
|
|
if err != nil { |
96
|
|
|
return nil, err |
97
|
|
|
} |
98
|
|
|
err = tx.Commit(ctx) |
99
|
|
|
if err != nil { |
100
|
|
|
return nil, utils.HandleError(ctx, span, err, base.ErrorCode_ERROR_CODE_EXECUTION) |
101
|
|
|
} |
102
|
|
|
return nil, utils.HandleError(ctx, span, err, base.ErrorCode_ERROR_CODE_EXECUTION) |
103
|
|
|
} else { |
104
|
|
|
totalDeleted += tag.RowsAffected() |
105
|
|
|
} |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
// Retrieve the tenant details after deletion |
109
|
|
|
var name string |
110
|
|
|
var createdAt time.Time |
111
|
|
|
err = br.QueryRow().Scan(&name, &createdAt) |
112
|
|
|
|
113
|
|
|
if err != nil { |
114
|
|
|
if totalDeleted > 0 { |
115
|
|
|
name = fmt.Sprintf("Affected rows: %d", totalDeleted) |
116
|
|
|
} else { |
117
|
|
|
return nil, utils.HandleError(ctx, span, err, base.ErrorCode_ERROR_CODE_EXECUTION) |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
err = br.Close() |
122
|
|
|
if err != nil { |
123
|
|
|
return nil, utils.HandleError(ctx, span, err, base.ErrorCode_ERROR_CODE_EXECUTION) |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
err = tx.Commit(ctx) |
127
|
|
|
if err != nil { |
128
|
|
|
return nil, utils.HandleError(ctx, span, err, base.ErrorCode_ERROR_CODE_EXECUTION) |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
// Return the deleted tenant information |
132
|
|
|
return &base.Tenant{ |
133
|
|
|
Id: tenantID, |
134
|
|
|
Name: name, |
135
|
|
|
CreatedAt: timestamppb.New(createdAt), |
136
|
|
|
}, nil |
137
|
|
|
|
138
|
|
|
} |
139
|
|
|
|