/
sqlstore.go
250 lines (224 loc) · 8.22 KB
/
sqlstore.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package impl
import (
"context"
"database/sql"
"encoding/base64"
"errors"
"fmt"
"net/url"
"strings"
"github.com/ethereum/go-ethereum/common"
logger "github.com/rs/zerolog/log"
"github.com/textileio/go-tableland/internal/router/middlewares"
"github.com/textileio/go-tableland/internal/system"
"github.com/textileio/go-tableland/internal/tableland"
"github.com/textileio/go-tableland/pkg/sqlstore"
"github.com/textileio/go-tableland/pkg/tables"
)
var log = logger.With().Str("component", "systemsqlstore").Logger()
const (
// SystemTablesPrefix is the prefix used in table names that
// aren't owned by users, but the system.
SystemTablesPrefix = "system_"
// RegistryTableName is a special system table (not owned by user)
// that has information about all tables owned by users.
RegistryTableName = "registry"
// DefaultMetadataImage is the default image for table's metadata.
DefaultMetadataImage = "https://bafkreifhuhrjhzbj4onqgbrmhpysk2mop2jimvdvfut6taiyzt2yqzt43a.ipfs.dweb.link"
// DefaultAnimationURL is an empty string. It means that the attribute will not appear in the JSON metadata.
DefaultAnimationURL = ""
)
// SystemSQLStoreService implements the SystemService interface using SQLStore.
type SystemSQLStoreService struct {
extURLPrefix string
metadataRendererURI string
animationRendererURI string
stores map[tableland.ChainID]sqlstore.SystemStore
}
// NewSystemSQLStoreService creates a new SystemSQLStoreService.
func NewSystemSQLStoreService(
stores map[tableland.ChainID]sqlstore.SystemStore,
extURLPrefix string,
metadataRendererURI string,
animationRendererURI string,
) (system.SystemService, error) {
if _, err := url.ParseRequestURI(extURLPrefix); err != nil {
return nil, fmt.Errorf("invalid external url prefix: %s", err)
}
metadataRendererURI = strings.TrimRight(metadataRendererURI, "/")
if metadataRendererURI != "" {
if _, err := url.ParseRequestURI(metadataRendererURI); err != nil {
return nil, fmt.Errorf("metadata renderer uri could not be parsed: %s", err)
}
}
animationRendererURI = strings.TrimRight(animationRendererURI, "/")
if animationRendererURI != "" {
if _, err := url.ParseRequestURI(animationRendererURI); err != nil {
return nil, fmt.Errorf("animation renderer uri could not be parsed: %s", err)
}
}
return &SystemSQLStoreService{
extURLPrefix: extURLPrefix,
metadataRendererURI: metadataRendererURI,
animationRendererURI: animationRendererURI,
stores: stores,
}, nil
}
// GetTableMetadata returns table's metadata fetched from SQLStore.
func (s *SystemSQLStoreService) GetTableMetadata(
ctx context.Context,
id tables.TableID,
) (sqlstore.TableMetadata, error) {
ctxChainID := ctx.Value(middlewares.ContextKeyChainID)
chainID, ok := ctxChainID.(tableland.ChainID)
if !ok {
return sqlstore.TableMetadata{}, errors.New("no chain id found in context")
}
store, ok := s.stores[chainID]
if !ok {
return sqlstore.TableMetadata{
ExternalURL: fmt.Sprintf("%s/chain/%d/tables/%s", s.extURLPrefix, chainID, id),
Image: s.emptyMetadataImage(),
Message: "Chain isn't supported",
}, nil
}
table, err := store.GetTable(ctx, id)
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
log.Error().Err(err).Msg("error fetching the table")
return sqlstore.TableMetadata{
ExternalURL: fmt.Sprintf("%s/chain/%d/tables/%s", s.extURLPrefix, chainID, id),
Image: s.emptyMetadataImage(),
Message: "Failed to fetch the table",
}, nil
}
return sqlstore.TableMetadata{
ExternalURL: fmt.Sprintf("%s/chain/%d/tables/%s", s.extURLPrefix, chainID, id),
Image: s.emptyMetadataImage(),
Message: "Table not found",
}, system.ErrTableNotFound
}
tableName := fmt.Sprintf("%s_%d_%s", table.Prefix, table.ChainID, table.ID)
schema, err := store.GetSchemaByTableName(ctx, tableName)
if err != nil {
return sqlstore.TableMetadata{}, fmt.Errorf("get table schema information: %s", err)
}
return sqlstore.TableMetadata{
Name: tableName,
ExternalURL: fmt.Sprintf("%s/chain/%d/tables/%s", s.extURLPrefix, table.ChainID, table.ID),
Image: s.getMetadataImage(table.ChainID, table.ID),
AnimationURL: s.getAnimationURL(table.ChainID, table.ID),
Attributes: []sqlstore.TableMetadataAttribute{
{
DisplayType: "date",
TraitType: "created",
Value: table.CreatedAt.Unix(),
},
},
Schema: schema,
}, nil
}
// GetReceiptByTransactionHash returns a receipt by transaction hash.
func (s *SystemSQLStoreService) GetReceiptByTransactionHash(
ctx context.Context,
txnHash common.Hash,
) (sqlstore.Receipt, bool, error) {
ctxChainID := ctx.Value(middlewares.ContextKeyChainID)
chainID, ok := ctxChainID.(tableland.ChainID)
if !ok {
return sqlstore.Receipt{}, false, errors.New("no chain id found in context")
}
store, ok := s.stores[chainID]
if !ok {
return sqlstore.Receipt{}, false, fmt.Errorf("chain id %d isn't supported in the validator", chainID)
}
receipt, exists, err := store.GetReceipt(ctx, txnHash.Hex())
if err != nil {
return sqlstore.Receipt{}, false, fmt.Errorf("transaction receipt lookup: %s", err)
}
if !exists {
return sqlstore.Receipt{}, false, nil
}
return sqlstore.Receipt{
ChainID: chainID,
BlockNumber: receipt.BlockNumber,
IndexInBlock: receipt.IndexInBlock,
TxnHash: receipt.TxnHash,
TableID: receipt.TableID,
Error: receipt.Error,
ErrorEventIdx: receipt.ErrorEventIdx,
}, true, nil
}
// GetTablesByController returns table's fetched from SQLStore by controller address.
func (s *SystemSQLStoreService) GetTablesByController(
ctx context.Context,
controller string,
) ([]sqlstore.Table, error) {
ctxChainID := ctx.Value(middlewares.ContextKeyChainID)
chainID, ok := ctxChainID.(tableland.ChainID)
if !ok {
return nil, errors.New("no chain id found in context")
}
store, ok := s.stores[chainID]
if !ok {
return nil, fmt.Errorf("chain id %d isn't supported in the validator", chainID)
}
tables, err := store.GetTablesByController(ctx, controller)
if err != nil {
return nil, fmt.Errorf("error fetching the tables: %s", err)
}
return tables, nil
}
// GetTablesByStructure returns all tables that share the same structure.
func (s *SystemSQLStoreService) GetTablesByStructure(ctx context.Context, structure string) ([]sqlstore.Table, error) {
ctxChainID := ctx.Value(middlewares.ContextKeyChainID)
chainID, ok := ctxChainID.(tableland.ChainID)
if !ok {
return nil, errors.New("no chain id found in context")
}
store, ok := s.stores[chainID]
if !ok {
return nil, fmt.Errorf("chain id %d isn't supported in the validator", chainID)
}
tables, err := store.GetTablesByStructure(ctx, structure)
if err != nil {
return nil, fmt.Errorf("get tables by structure: %s", err)
}
return tables, nil
}
// GetSchemaByTableName returns the schema of a table by its name.
func (s *SystemSQLStoreService) GetSchemaByTableName(
ctx context.Context,
tableName string,
) (sqlstore.TableSchema, error) {
table, err := tableland.NewTableFromName(tableName)
if err != nil {
return sqlstore.TableSchema{}, fmt.Errorf("new table from name: %s", err)
}
store, ok := s.stores[table.ChainID()]
if !ok {
return sqlstore.TableSchema{}, fmt.Errorf("chain id %d isn't supported in the validator", table.ChainID())
}
schema, err := store.GetSchemaByTableName(ctx, tableName)
if err != nil {
return sqlstore.TableSchema{}, fmt.Errorf("get schema by table name: %s", err)
}
return schema, nil
}
func (s *SystemSQLStoreService) getMetadataImage(chainID tableland.ChainID, tableID tables.TableID) string {
if s.metadataRendererURI == "" {
return DefaultMetadataImage
}
return fmt.Sprintf("%s/%d/%s", s.metadataRendererURI, chainID, tableID)
}
func (s *SystemSQLStoreService) getAnimationURL(chainID tableland.ChainID, tableID tables.TableID) string {
if s.animationRendererURI == "" {
return DefaultAnimationURL
}
return fmt.Sprintf("%s/?chain=%d&id=%s", s.animationRendererURI, chainID, tableID)
}
func (s *SystemSQLStoreService) emptyMetadataImage() string {
svg := `<svg width='512' height='512' xmlns='http://www.w3.org/2000/svg'><rect width='512' height='512' fill='#000'/></svg>` //nolint
svgEncoded := base64.StdEncoding.EncodeToString([]byte(svg))
return fmt.Sprintf("data:image/svg+xml;base64,%s", svgEncoded)
}