mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-03 09:51:39 +00:00
Feature/update dockerfile (#6)
* chore: remove unused new.Dockerfile
* feat: add DID model definitions
* fix: Fix EncodePublicKey method in KeyInfo struct
* feat: Update `EncodePublicKey` to be the inverse of `DecodePublicKey`
* refactor: update AssetInfo protobuf definition
* fix: update default assets with correct asset types
* fix: Initialize IPFS client and check for mounted directories
* feat: Improve IPFS client initialization and mount checking
* feat: Add local filesystem check for IPFS and IPNS
* fix: Use Unixfs().Get() instead of Cat() for IPFS and IPNS content retrieval
* feat: Update GetCID and GetIPNS functions to read data from IPFS node
* fix: Ensure IPFS client is initialized before pinning CID
* feat: Add AddFile and AddFolder methods
* feat: add IPFS file system abstraction
* feat: Implement IPFS file, location, and filesystem abstractions
* refactor: remove unused functions and types
* refactor: remove unused FileSystem interface
* feat: add initial wasm entrypoint
* feat: add basic vault command operations
* docs: add vault module features
* test: remove test for MsgUpdateParams
* refactor: Replace PrimaryKey with Property struct in zkprop.go
* feat: Update the `CreateWitness` and `CreateAccumulator` and `VerifyWitness` and `UpdateAccumulator` to Use the new `Accumulator` and `Witness` types. Then Clean up the code in the file and refactor the marshalling methods
* <no value>
* feat: add KeyCurve and KeyType to KeyInfo in genesis
* feat: add WASM build step to devbox.json
* feat: Add zkgate.go file
* feat: Uncomment and modify zkgate code to work with Property struct
* feat: Merge zkgate.go and zkprop.go logic
* feat: implement API endpoints for profile management
* refactor: remove unused template file
* feat(orm): remove unused ORM models
* feat: add persistent SQLite database support in WASM
* fix: Update module names in protobuf files
* feat: Add method to initialize SQLite database
* fix: update go-sqlite3 dependency to version 1.14.23
* feat: introduce database layer
* feat: Implement database layer for Vault node
* feature/update-dockerfile
* feat: Add keyshares table
* fix: Reorder the SQL statements in the tables.go file
* feat: Update the `createCredentialsTable` method to match the proper Credential struct
* feat: Update createProfilesTable and add createPropertiesTable
* feat: Add constant SQL queries to queries.go and use prepared statements in db.go
* feat: Add createKeysharesTable to internal/db/db.go
* feat: Update `createPermissionsTable` to match Permissions struct
* feat: Add database enum types
* feat: Add DIDNamespace and PermissionScope enums
* feat: Add DBConfig and DBOption types
* feat: Update the db implementation to use the provided go library
* fix: update db implementation to use go-sqlite3 v0.18.2
* fix: Refactor database connection and statement handling
* feat: Simplify db.go implementation
* feat: Convert constant SQL queries to functions in queries.go and update db.go to use prepared statements
* feat: Add models.go file with database table structs
* fix: Remove unused statement map and prepare statements
diff --git a/internal/db/db.go b/internal/db/db.go
index 201d09b..d4d4d4e 100644
--- a/internal/db/db.go
+++ b/internal/db/db.go
@@ -32,11 +32,6 @@ func Open(config *DBConfig) (*DB, error) {
Conn: conn,
}
- if err := createTables(db); err != nil {
- conn.Close()
- return nil, fmt.Errorf("failed to create tables: %w", err)
- }
-
return db, nil
}
@@ -61,114 +56,3 @@ func createTables(db *DB) error {
return nil
}
-// AddAccount adds a new account to the database
-func (db *DB) AddAccount(name, address string) error {
- return db.Exec(insertAccountQuery(name, address))
-}
-
-// AddAsset adds a new asset to the database
-func (db *DB) AddAsset(name, symbol string, decimals int, chainID int64) error {
- return db.Exec(insertAssetQuery(name, symbol, decimals, chainID))
-}
-
-// AddChain adds a new chain to the database
-func (db *DB) AddChain(name, networkID string) error {
- return db.Exec(insertChainQuery(name, networkID))
-}
-
-// AddCredential adds a new credential to the database
-func (db *DB) AddCredential(
- handle, controller, attestationType, origin string,
- credentialID, publicKey []byte,
- transport string,
- signCount uint32,
- userPresent, userVerified, backupEligible, backupState, cloneWarning bool,
-) error {
- return db.Exec(insertCredentialQuery(
- handle,
- controller,
- attestationType,
- origin,
- credentialID,
- publicKey,
- transport,
- signCount,
- userPresent,
- userVerified,
- backupEligible,
- backupState,
- cloneWarning,
- ))
-}
-
-// AddProfile adds a new profile to the database
-func (db *DB) AddProfile(
- id, subject, controller, originURI, publicMetadata, privateMetadata string,
-) error {
- return db.statements["insertProfile"].Exec(
- id, subject, controller, originURI, publicMetadata, privateMetadata,
- )
-}
-
-// AddProperty adds a new property to the database
-func (db *DB) AddProperty(
- profileID, key, accumulator, propertyKey string,
-) error {
- return db.statements["insertProperty"].Exec(
- profileID, key, accumulator, propertyKey,
- )
-}
-
-// AddPermission adds a new permission to the database
-func (db *DB) AddPermission(
- serviceID string,
- grants []DIDNamespace,
- scopes []PermissionScope,
-) error {
- grantsJSON, err := json.Marshal(grants)
- if err != nil {
- return fmt.Errorf("failed to marshal grants: %w", err)
- }
-
- scopesJSON, err := json.Marshal(scopes)
- if err != nil {
- return fmt.Errorf("failed to marshal scopes: %w", err)
- }
-
- return db.statements["insertPermission"].Exec(
- serviceID, string(grantsJSON), string(scopesJSON),
- )
-}
-
-// GetPermission retrieves the permission for the given service ID
-func (db *DB) GetPermission(serviceID string) ([]DIDNamespace, []PermissionScope, error) {
- row := db.statements["getPermission"].QueryRow(serviceID)
-
- var grantsJSON, scopesJSON string
- if err := row.Scan(&grantsJSON, &scopesJSON); err != nil {
- return nil, nil, fmt.Errorf("failed to get permission: %w", err)
- }
-
- var grants []DIDNamespace
- if err := json.Unmarshal([]byte(grantsJSON), &grants); err != nil {
- return nil, nil, fmt.Errorf("failed to unmarshal grants: %w", err)
- }
-
- var scopes []PermissionScope
- if err := json.Unmarshal([]byte(scopesJSON), &scopes); err != nil {
- return nil, nil, fmt.Errorf("failed to unmarshal scopes: %w", err)
- }
-
- return grants, scopes, nil
-}
-
-// Close closes the database connection and finalizes all prepared statements
-func (db *DB) Close() error {
- for _, stmt := range db.statements {
- stmt.Finalize()
- }
- return db.Conn.Close()
-}
diff --git a/internal/db/queries.go b/internal/db/queries.go
index 807d701..e69de29 100644
--- a/internal/db/queries.go
+++ b/internal/db/queries.go
@@ -1,79 +0,0 @@
-package db
-
-import "fmt"
-
-// Account queries
-func insertAccountQuery(name, address string) string {
- return fmt.Sprintf(`INSERT INTO accounts (name, address) VALUES (%s, %s)`, name, address)
-}
-
-// Asset queries
-func insertAssetQuery(name, symbol string, decimals int, chainID int64) string {
- return fmt.Sprintf(
- `INSERT INTO assets (name, symbol, decimals, chain_id) VALUES (%s, %s, %d, %d)`,
- name,
- symbol,
- decimals,
- chainID,
- )
-}
-
-// Chain queries
-func insertChainQuery(name string, networkID string) string {
- return fmt.Sprintf(`INSERT INTO chains (name, network_id) VALUES (%s, %d)`, name, networkID)
-}
-
-// Credential queries
-func insertCredentialQuery(
- handle, controller, attestationType, origin string,
- credentialID, publicKey []byte,
- transport string,
- signCount uint32,
- userPresent, userVerified, backupEligible, backupState, cloneWarning bool,
-) string {
- return fmt.Sprintf(`INSERT INTO credentials (
- handle, controller, attestation_type, origin,
- credential_id, public_key, transport, sign_count,
- user_present, user_verified, backup_eligible,
- backup_state, clone_warning
- ) VALUES (%s, %s, %s, %s, %s, %s, %s, %d, %t, %t, %t, %t, %t)`,
- handle, controller, attestationType, origin,
- credentialID, publicKey, transport, signCount,
- userPresent, userVerified, backupEligible,
- backupState, cloneWarning)
-}
-
-// Profile queries
-func insertProfileQuery(
- id, subject, controller, originURI, publicMetadata, privateMetadata string,
-) string {
- return fmt.Sprintf(`INSERT INTO profiles (
- id, subject, controller, origin_uri,
- public_metadata, private_metadata
- ) VALUES (%s, %s, %s, %s, %s, %s)`,
- id, subject, controller, originURI,
- publicMetadata, privateMetadata)
-}
-
-// Property queries
-func insertPropertyQuery(profileID, key, accumulator, propertyKey string) string {
- return fmt.Sprintf(`INSERT INTO properties (
- profile_id, key, accumulator, property_key
- ) VALUES (%s, %s, %s, %s)`,
- profileID, key, accumulator, propertyKey)
-}
-
-// Permission queries
-func insertPermissionQuery(serviceID, grants, scopes string) string {
- return fmt.Sprintf(
- `INSERT INTO permissions (service_id, grants, scopes) VALUES (%s, %s, %s)`,
- serviceID,
- grants,
- scopes,
- )
-}
-
-// GetPermission query
-func getPermissionQuery(serviceID string) string {
- return fmt.Sprintf(`SELECT grants, scopes FROM permissions WHERE service_id = %s`, serviceID)
-}
* fix: update Makefile to use sonrd instead of wasmd
* feat: Add targets for templ and vault in Makefile and use only make in devbox.json
* feat: add SQLite database support
* bump: version 0.6.0 → 0.7.0
* refactor: upgrade actions to latest versions
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
*sqlite3.Conn
|
||||
}
|
||||
|
||||
func New(opts ...DBOption) *DBConfig {
|
||||
config := &DBConfig{
|
||||
fileName: "vault.db",
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(config)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func Open(config *DBConfig) (*DB, error) {
|
||||
conn, err := sqlite3.Open(config.ConnectionString())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
|
||||
db := &DB{
|
||||
Conn: conn,
|
||||
}
|
||||
|
||||
if err := createTables(db); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("failed to create tables: %w", err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func createTables(db *DB) error {
|
||||
tables := []string{
|
||||
createAccountsTable,
|
||||
createAssetsTable,
|
||||
createChainsTable,
|
||||
createCredentialsTable,
|
||||
createKeysharesTable,
|
||||
createProfilesTable,
|
||||
createPropertiesTable,
|
||||
createPermissionsTable,
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
err := db.Exec(table)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create table: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAccount adds a new account to the database
|
||||
func (db *DB) AddAccount(name, address string) error {
|
||||
return db.Exec(insertAccountQuery(name, address))
|
||||
}
|
||||
|
||||
// AddAsset adds a new asset to the database
|
||||
func (db *DB) AddAsset(name, symbol string, decimals int, chainID int64) error {
|
||||
return db.Exec(insertAssetQuery(name, symbol, decimals, chainID))
|
||||
}
|
||||
|
||||
// AddChain adds a new chain to the database
|
||||
func (db *DB) AddChain(name, networkID string) error {
|
||||
return db.Exec(insertChainQuery(name, networkID))
|
||||
}
|
||||
|
||||
// AddCredential adds a new credential to the database
|
||||
func (db *DB) AddCredential(
|
||||
handle, controller, attestationType, origin string,
|
||||
credentialID, publicKey []byte,
|
||||
transport string,
|
||||
signCount uint32,
|
||||
userPresent, userVerified, backupEligible, backupState, cloneWarning bool,
|
||||
) error {
|
||||
return db.Exec(insertCredentialQuery(
|
||||
handle,
|
||||
controller,
|
||||
attestationType,
|
||||
origin,
|
||||
credentialID,
|
||||
publicKey,
|
||||
transport,
|
||||
signCount,
|
||||
userPresent,
|
||||
userVerified,
|
||||
backupEligible,
|
||||
backupState,
|
||||
cloneWarning,
|
||||
))
|
||||
}
|
||||
|
||||
//
|
||||
// // AddProfile adds a new profile to the database
|
||||
// func (db *DB) AddProfile(
|
||||
// id, subject, controller, originURI string,
|
||||
// publicMetadata, privateMetadata string,
|
||||
// ) error {
|
||||
// return db.statements["insertProfile"].Exec(
|
||||
// id,
|
||||
// subject,
|
||||
// controller,
|
||||
// originURI,
|
||||
// publicMetadata,
|
||||
// privateMetadata,
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// // AddProperty adds a new property to the database
|
||||
// func (db *DB) AddProperty(profileID, key string, accumulator, propertyKey []byte) error {
|
||||
// return db.statements["insertProperty"].Exec(profileID, key, accumulator, propertyKey)
|
||||
// }
|
||||
//
|
||||
// // AddPermission adds a new permission to the database
|
||||
// func (db *DB) AddPermission(
|
||||
// serviceID string,
|
||||
// grants []DIDNamespace,
|
||||
// scopes []PermissionScope,
|
||||
// ) error {
|
||||
// grantsJSON, err := json.Marshal(grants)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to marshal grants: %w", err)
|
||||
// }
|
||||
//
|
||||
// scopesJSON, err := json.Marshal(scopes)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to marshal scopes: %w", err)
|
||||
// }
|
||||
//
|
||||
// return db.statements["insertPermission"].Exec(
|
||||
// serviceID,
|
||||
// string(grantsJSON),
|
||||
// string(scopesJSON),
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// // GetPermission retrieves a permission from the database
|
||||
// func (db *DB) GetPermission(serviceID string) ([]DIDNamespace, []PermissionScope, error) {
|
||||
// stmt := db.statements["getPermission"]
|
||||
// if err := stmt.Exec(serviceID); err != nil {
|
||||
// return nil, nil, fmt.Errorf("failed to execute statement: %w", err)
|
||||
// }
|
||||
//
|
||||
// if !stmt.Step() {
|
||||
// return nil, nil, fmt.Errorf("permission not found")
|
||||
// }
|
||||
//
|
||||
// grantsJSON := stmt.ColumnText(0)
|
||||
// scopesJSON := stmt.ColumnText(1)
|
||||
//
|
||||
// var grants []DIDNamespace
|
||||
// err := json.Unmarshal([]byte(grantsJSON), &grants)
|
||||
// if err != nil {
|
||||
// return nil, nil, fmt.Errorf("failed to unmarshal grants: %w", err)
|
||||
// }
|
||||
//
|
||||
// var scopes []PermissionScope
|
||||
// err = json.Unmarshal([]byte(scopesJSON), &scopes)
|
||||
// if err != nil {
|
||||
// return nil, nil, fmt.Errorf("failed to unmarshal scopes: %w", err)
|
||||
// }
|
||||
//
|
||||
// return grants, scopes, nil
|
||||
// }
|
||||
@@ -0,0 +1,36 @@
|
||||
package db
|
||||
|
||||
// DIDNamespace defines the different namespaces of DID
|
||||
type DIDNamespace int
|
||||
|
||||
const (
|
||||
DIDNamespaceUnspecified DIDNamespace = iota
|
||||
DIDNamespaceIPFS
|
||||
DIDNamespaceSonr
|
||||
DIDNamespaceBitcoin
|
||||
DIDNamespaceEthereum
|
||||
DIDNamespaceIBC
|
||||
DIDNamespaceWebauthn
|
||||
DIDNamespaceDWN
|
||||
DIDNamespaceService
|
||||
)
|
||||
|
||||
// PermissionScope defines the Capabilities Controllers can grant for Services
|
||||
type PermissionScope int
|
||||
|
||||
const (
|
||||
PermissionScopeUnspecified PermissionScope = iota
|
||||
PermissionScopeBasicInfo
|
||||
PermissionScopeRecordsRead
|
||||
PermissionScopeRecordsWrite
|
||||
PermissionScopeTransactionsRead
|
||||
PermissionScopeTransactionsWrite
|
||||
PermissionScopeWalletsRead
|
||||
PermissionScopeWalletsCreate
|
||||
PermissionScopeWalletsSubscribe
|
||||
PermissionScopeWalletsUpdate
|
||||
PermissionScopeTransactionsVerify
|
||||
PermissionScopeTransactionsBroadcast
|
||||
PermissionScopeAdminUser
|
||||
PermissionScopeAdminValidator
|
||||
)
|
||||
@@ -0,0 +1,83 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/gormlite"
|
||||
"github.com/ncruces/go-sqlite3/vfs"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"gorm.io/gorm"
|
||||
"lukechampine.com/adiantum/hbsh"
|
||||
"lukechampine.com/adiantum/hpolyc"
|
||||
)
|
||||
|
||||
type DBOption func(config *DBConfig)
|
||||
|
||||
func WithDir(dir string) DBOption {
|
||||
return func(config *DBConfig) {
|
||||
config.Dir = dir
|
||||
}
|
||||
}
|
||||
|
||||
func WithInMemory() DBOption {
|
||||
return func(config *DBConfig) {
|
||||
config.InMemory = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithSecretKey(secretKey string) DBOption {
|
||||
return func(config *DBConfig) {
|
||||
config.SecretKey = secretKey
|
||||
}
|
||||
}
|
||||
|
||||
func WithOpenFlag(flag vfs.OpenFlag) DBOption {
|
||||
return func(config *DBConfig) {
|
||||
config.OpenFlag = flag
|
||||
}
|
||||
}
|
||||
|
||||
type DBConfig struct {
|
||||
Dir string
|
||||
InMemory bool
|
||||
SecretKey string
|
||||
OpenFlag vfs.OpenFlag
|
||||
|
||||
fileName string
|
||||
}
|
||||
|
||||
func (config *DBConfig) ConnectionString() string {
|
||||
connStr := "file:"
|
||||
if config.InMemory {
|
||||
connStr += ":memory:"
|
||||
} else {
|
||||
connStr += config.Dir + "/" + config.fileName
|
||||
}
|
||||
return connStr
|
||||
}
|
||||
|
||||
// GormDialector creates a gorm dialector for the database.
|
||||
func (config *DBConfig) GormDialector() (*gorm.DB, error) {
|
||||
return gorm.Open(gormlite.Open(config.ConnectionString()))
|
||||
}
|
||||
|
||||
// HBSH creates an HBSH cipher given a key.
|
||||
func (c *DBConfig) HBSH(key []byte) *hbsh.HBSH {
|
||||
if len(key) != 32 {
|
||||
// Key is not appropriate, return nil.
|
||||
return nil
|
||||
}
|
||||
return hpolyc.New(key)
|
||||
}
|
||||
|
||||
// KDF gets a key from a secret.
|
||||
func (c *DBConfig) KDF(secret string) []byte {
|
||||
if secret == "" {
|
||||
// No secret is given, generate a random key.
|
||||
key := make([]byte, 32)
|
||||
n, _ := rand.Read(key)
|
||||
return key[:n]
|
||||
}
|
||||
// Hash the secret with a KDF.
|
||||
return argon2.IDKey([]byte(secret), []byte("hpolyc"), 3, 64*1024, 4, 32)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package db
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Account queries
|
||||
func insertAccountQuery(name, address string) string {
|
||||
return fmt.Sprintf(`INSERT INTO accounts (name, address) VALUES (%s, %s)`, name, address)
|
||||
}
|
||||
|
||||
// Asset queries
|
||||
func insertAssetQuery(name, symbol string, decimals int, chainID int64) string {
|
||||
return fmt.Sprintf(
|
||||
`INSERT INTO assets (name, symbol, decimals, chain_id) VALUES (%s, %s, %d, %d)`,
|
||||
name,
|
||||
symbol,
|
||||
decimals,
|
||||
chainID,
|
||||
)
|
||||
}
|
||||
|
||||
// Chain queries
|
||||
func insertChainQuery(name string, networkID string) string {
|
||||
return fmt.Sprintf(`INSERT INTO chains (name, network_id) VALUES (%s, %d)`, name, networkID)
|
||||
}
|
||||
|
||||
// Credential queries
|
||||
func insertCredentialQuery(
|
||||
handle, controller, attestationType, origin string,
|
||||
credentialID, publicKey []byte,
|
||||
transport string,
|
||||
signCount uint32,
|
||||
userPresent, userVerified, backupEligible, backupState, cloneWarning bool,
|
||||
) string {
|
||||
return fmt.Sprintf(`INSERT INTO credentials (
|
||||
handle, controller, attestation_type, origin,
|
||||
credential_id, public_key, transport, sign_count,
|
||||
user_present, user_verified, backup_eligible,
|
||||
backup_state, clone_warning
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %d, %t, %t, %t, %t, %t)`,
|
||||
handle, controller, attestationType, origin,
|
||||
credentialID, publicKey, transport, signCount,
|
||||
userPresent, userVerified, backupEligible,
|
||||
backupState, cloneWarning)
|
||||
}
|
||||
|
||||
// Profile queries
|
||||
func insertProfileQuery(
|
||||
id, subject, controller, originURI, publicMetadata, privateMetadata string,
|
||||
) string {
|
||||
return fmt.Sprintf(`INSERT INTO profiles (
|
||||
id, subject, controller, origin_uri,
|
||||
public_metadata, private_metadata
|
||||
) VALUES (%s, %s, %s, %s, %s, %s)`,
|
||||
id, subject, controller, originURI,
|
||||
publicMetadata, privateMetadata)
|
||||
}
|
||||
|
||||
// Property queries
|
||||
func insertPropertyQuery(profileID, key, accumulator, propertyKey string) string {
|
||||
return fmt.Sprintf(`INSERT INTO properties (
|
||||
profile_id, key, accumulator, property_key
|
||||
) VALUES (%s, %s, %s, %s)`,
|
||||
profileID, key, accumulator, propertyKey)
|
||||
}
|
||||
|
||||
// Permission queries
|
||||
func insertPermissionQuery(serviceID, grants, scopes string) string {
|
||||
return fmt.Sprintf(
|
||||
`INSERT INTO permissions (service_id, grants, scopes) VALUES (%s, %s, %s)`,
|
||||
serviceID,
|
||||
grants,
|
||||
scopes,
|
||||
)
|
||||
}
|
||||
|
||||
// GetPermission query
|
||||
func getPermissionQuery(serviceID string) string {
|
||||
return fmt.Sprintf(`SELECT grants, scopes FROM permissions WHERE service_id = %s`, serviceID)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package db
|
||||
|
||||
const (
|
||||
createAccountsTable = `
|
||||
CREATE TABLE IF NOT EXISTS accounts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT NOT NULL UNIQUE,
|
||||
public_key BLOB NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`
|
||||
|
||||
createAssetsTable = `
|
||||
CREATE TABLE IF NOT EXISTS assets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
symbol TEXT NOT NULL,
|
||||
decimals INTEGER NOT NULL,
|
||||
chain_id INTEGER,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (chain_id) REFERENCES chains(id)
|
||||
)
|
||||
`
|
||||
|
||||
createChainsTable = `
|
||||
CREATE TABLE IF NOT EXISTS chains (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
network_id TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`
|
||||
|
||||
createCredentialsTable = `
|
||||
CREATE TABLE IF NOT EXISTS credentials (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
handle TEXT NOT NULL,
|
||||
controller TEXT NOT NULL,
|
||||
attestation_type TEXT NOT NULL,
|
||||
origin TEXT NOT NULL,
|
||||
credential_id BLOB NOT NULL,
|
||||
public_key BLOB NOT NULL,
|
||||
transport TEXT NOT NULL,
|
||||
sign_count INTEGER NOT NULL,
|
||||
user_present BOOLEAN NOT NULL,
|
||||
user_verified BOOLEAN NOT NULL,
|
||||
backup_eligible BOOLEAN NOT NULL,
|
||||
backup_state BOOLEAN NOT NULL,
|
||||
clone_warning BOOLEAN NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`
|
||||
|
||||
createProfilesTable = `
|
||||
CREATE TABLE IF NOT EXISTS profiles (
|
||||
id TEXT PRIMARY KEY,
|
||||
subject TEXT NOT NULL,
|
||||
controller TEXT NOT NULL,
|
||||
origin_uri TEXT,
|
||||
public_metadata TEXT,
|
||||
private_metadata TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`
|
||||
|
||||
createPropertiesTable = `
|
||||
CREATE TABLE IF NOT EXISTS properties (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
profile_id TEXT NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
accumulator BLOB NOT NULL,
|
||||
property_key BLOB NOT NULL,
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id)
|
||||
)
|
||||
`
|
||||
|
||||
createKeysharesTable = `
|
||||
CREATE TABLE IF NOT EXISTS keyshares (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
metadata TEXT NOT NULL,
|
||||
payloads TEXT NOT NULL,
|
||||
protocol TEXT NOT NULL,
|
||||
public_key BLOB NOT NULL,
|
||||
role INTEGER NOT NULL,
|
||||
version INTEGER NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`
|
||||
|
||||
createPermissionsTable = `
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
service_id TEXT NOT NULL,
|
||||
grants TEXT NOT NULL,
|
||||
scopes TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (service_id) REFERENCES services(id)
|
||||
)
|
||||
`
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
"github.com/onsonr/sonr/internal/db"
|
||||
)
|
||||
|
||||
var (
|
||||
kServiceWorkerFileName = "sw.js"
|
||||
kVaultFileName = "vault.wasm"
|
||||
kIndexFileName = "index.html"
|
||||
)
|
||||
|
||||
func Assemble(dir string) error {
|
||||
err := os.MkdirAll(dir, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the vault file
|
||||
if err := writeVaultWASM(filepath.Join(dir, kVaultFileName)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the service worker file
|
||||
if err := writeServiceWorkerJS(filepath.Join(dir, kServiceWorkerFileName)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the index file
|
||||
if err := writeIndexHTML(filepath.Join(dir, kIndexFileName)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the database
|
||||
if err := initializeDatabase(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initializeDatabase(dir string) error {
|
||||
db, err := db.Open(db.New(db.WithDir(dir)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// You can add some initial data here if needed
|
||||
// For example:
|
||||
// err = db.AddChain("Ethereum", "1")
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to add initial chain: %w", err)
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"os"
|
||||
)
|
||||
|
||||
//go:embed vault.wasm
|
||||
var vaultWasmData []byte
|
||||
|
||||
func writeServiceWorkerJS(path string) error {
|
||||
// Create the service worker file
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Write the service worker file to the specified path
|
||||
err = VaultServiceWorker(kVaultFileName).Render(context.Background(), file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeVaultWASM(path string) error {
|
||||
// Create the vault file
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Write the embedded vault file to the specified path
|
||||
err = os.WriteFile(file.Name(), vaultWasmData, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeIndexHTML(path string) error {
|
||||
// create the index file
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// write the index file to the specified path
|
||||
err = IndexHTML().Render(context.Background(), file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package files
|
||||
|
||||
templ VaultServiceWorker(path string) {
|
||||
@serviceWorkerJS(path)
|
||||
}
|
||||
|
||||
script serviceWorkerJS(path string) {
|
||||
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.18.4/misc/wasm/wasm_exec.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js')
|
||||
}
|
||||
|
||||
templ IndexHTML() {
|
||||
<html>
|
||||
<head>
|
||||
<title>Sonr ID</title>
|
||||
<script>
|
||||
navigator.serviceWorker.register('sw.js')
|
||||
registerWasmHTTPListener(path)
|
||||
|
||||
// Skip installed stage and jump to activating stage
|
||||
addEventListener('install', (event) => {
|
||||
event.waitUntil(skipWaiting())
|
||||
})
|
||||
|
||||
// Start controlling clients as soon as the SW is activated
|
||||
addEventListener('activate', event => {
|
||||
event.waitUntil(clients.claim())
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.771
|
||||
package files
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
func VaultServiceWorker(path string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = serviceWorkerJS(path).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func serviceWorkerJS(path string) templ.ComponentScript {
|
||||
return templ.ComponentScript{
|
||||
Name: `__templ_serviceWorkerJS_2501`,
|
||||
Function: `function __templ_serviceWorkerJS_2501(path){importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.18.4/misc/wasm/wasm_exec.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js')
|
||||
}`,
|
||||
Call: templ.SafeScript(`__templ_serviceWorkerJS_2501`, path),
|
||||
CallInline: templ.SafeScriptInline(`__templ_serviceWorkerJS_2501`, path),
|
||||
}
|
||||
}
|
||||
|
||||
func IndexHTML() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var2 == nil {
|
||||
templ_7745c5c3_Var2 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<html><head><title>Sonr ID</title><script>\n navigator.serviceWorker.register('sw.js')\n registerWasmHTTPListener(path)\n\n // Skip installed stage and jump to activating stage\n addEventListener('install', (event) => {\n event.waitUntil(skipWaiting())\n })\n\n // Start controlling clients as soon as the SW is activated\n addEventListener('activate', event => {\n event.waitUntil(clients.claim())\n })\n </script></head></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,88 @@
|
||||
package ipfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotInitialized = fmt.Errorf("IPFS client not initialized")
|
||||
ErrNotMounted = fmt.Errorf("IPFS client not mounted")
|
||||
ErrCIDNotFound = fmt.Errorf("CID not found")
|
||||
ErrIPNSNotFound = fmt.Errorf("IPNS not found")
|
||||
ErrInternal = fmt.Errorf("internal error")
|
||||
)
|
||||
|
||||
type FileSystem interface {
|
||||
// NewFile initializes a File on the specified volume at path 'absFilePath'.
|
||||
//
|
||||
// * Accepts volume and an absolute file path.
|
||||
// * Upon success, a vfs.File, representing the file's new path (location path + file relative path), will be returned.
|
||||
// * On error, nil is returned for the file.
|
||||
// * Note that not all file systems will have a "volume" and will therefore be "":
|
||||
// file:///path/to/file has a volume of "" and name /path/to/file
|
||||
// whereas
|
||||
// s3://mybucket/path/to/file has a volume of "mybucket and name /path/to/file
|
||||
// results in /tmp/dir1/newerdir/file.txt for the final vfs.File path.
|
||||
// * The file may or may not already exist.
|
||||
NewFile(volume string, absFilePath string) (File, error)
|
||||
|
||||
// Name returns the name of the FileSystem ie: Amazon S3, os, Google Cloud Storage, etc.
|
||||
Name() string
|
||||
}
|
||||
|
||||
type File interface {
|
||||
io.Closer
|
||||
io.Reader
|
||||
io.Seeker
|
||||
io.Writer
|
||||
fmt.Stringer
|
||||
|
||||
// Exists returns boolean if the file exists on the file system. Returns an error, if any.
|
||||
Exists() (bool, error)
|
||||
|
||||
// CopyToFile will copy the current file to the provided file instance.
|
||||
//
|
||||
// * In the case of an error, nil is returned for the file.
|
||||
// * CopyToLocation should use native functions when possible within the same scheme.
|
||||
// * If the file already exists, the contents will be overwritten with the current file's contents.
|
||||
// * CopyToFile will Close both the source and target Files which therefore can't be appended to without first
|
||||
// calling Seek() to move the cursor to the end of the file.
|
||||
CopyToFile(file File) error
|
||||
|
||||
// MoveToFile will move the current file to the provided file instance.
|
||||
//
|
||||
// * If the file already exists, the contents will be overwritten with the current file's contents.
|
||||
// * The current instance of the file will be removed.
|
||||
// * MoveToFile will Close both the source and target Files which therefore can't be appended to without first
|
||||
// calling Seek() to move the cursor to the end of the file.
|
||||
MoveToFile(file File) error
|
||||
|
||||
// Delete unlinks the File on the file system.
|
||||
Delete() error
|
||||
|
||||
// LastModified returns the timestamp the file was last modified (as *time.Time).
|
||||
LastModified() (*time.Time, error)
|
||||
|
||||
// Size returns the size of the file in bytes.
|
||||
Size() (uint64, error)
|
||||
|
||||
// Path returns absolute path, including filename, ie /some/path/to/file.txt
|
||||
//
|
||||
// If the directory portion of a file is desired, call
|
||||
// someFile.Location().Path()
|
||||
Path() string
|
||||
|
||||
// Name returns the base name of the file path.
|
||||
//
|
||||
// For file:///some/path/to/file.txt, it would return file.txt
|
||||
Name() string
|
||||
|
||||
// Touch creates a zero-length file on the vfs.File if no File exists. Update File's last modified timestamp.
|
||||
// Returns error if unable to touch File.
|
||||
Touch() error
|
||||
|
||||
// URI returns the fully qualified absolute URI for the File. IE, s3://bucket/some/path/to/file.txt
|
||||
URI() string
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package ipfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ipfs/boxo/files"
|
||||
"github.com/ipfs/boxo/path"
|
||||
"github.com/ipfs/kubo/client/rpc"
|
||||
)
|
||||
|
||||
var (
|
||||
initialized bool
|
||||
localMount bool
|
||||
ipfsClient *rpc.HttpApi
|
||||
ipfsFS FileSystem
|
||||
)
|
||||
|
||||
// init initializes the IPFS client and checks for local mounts
|
||||
func init() {
|
||||
var err error
|
||||
ipfsClient, err = rpc.NewLocalApi()
|
||||
if err != nil {
|
||||
initialized = false
|
||||
localMount = false
|
||||
return
|
||||
}
|
||||
|
||||
initialized = true
|
||||
ipfsFS = &IPFSFileSystem{client: ipfsClient}
|
||||
|
||||
// Check if /ipfs and /ipns are mounted using os package
|
||||
_, errIPFS := os.Stat("/ipfs")
|
||||
_, errIPNS := os.Stat("/ipns")
|
||||
localMount = !os.IsNotExist(errIPFS) && !os.IsNotExist(errIPNS)
|
||||
}
|
||||
|
||||
// GetFileSystem returns the IPFS FileSystem implementation
|
||||
func GetFileSystem() FileSystem {
|
||||
return ipfsFS
|
||||
}
|
||||
|
||||
// AddFile adds a single file to IPFS and returns its CID
|
||||
func AddFile(ctx context.Context, filePath string) (string, error) {
|
||||
if !initialized {
|
||||
return "", ErrNotInitialized
|
||||
}
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fileNode := files.NewReaderFile(file)
|
||||
|
||||
cidFile, err := ipfsClient.Unixfs().Add(ctx, fileNode)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to add file to IPFS: %w", err)
|
||||
}
|
||||
|
||||
return cidFile.String(), nil
|
||||
}
|
||||
|
||||
// AddFolder adds a folder and its contents to IPFS and returns the CID of the folder
|
||||
func AddFolder(ctx context.Context, folderPath string) (string, error) {
|
||||
if !initialized {
|
||||
return "", ErrNotInitialized
|
||||
}
|
||||
|
||||
stat, err := os.Stat(folderPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get folder info: %w", err)
|
||||
}
|
||||
|
||||
if !stat.IsDir() {
|
||||
return "", fmt.Errorf("provided path is not a directory")
|
||||
}
|
||||
|
||||
folderNode, err := files.NewSerialFile(folderPath, false, stat)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create folder node: %w", err)
|
||||
}
|
||||
|
||||
cidFolder, err := ipfsClient.Unixfs().Add(ctx, folderNode)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to add folder to IPFS: %w", err)
|
||||
}
|
||||
|
||||
return cidFolder.String(), nil
|
||||
}
|
||||
|
||||
func GetCID(ctx context.Context, cid string) ([]byte, error) {
|
||||
if !initialized {
|
||||
return nil, ErrNotInitialized
|
||||
}
|
||||
|
||||
if localMount {
|
||||
// Try to read from local filesystem first
|
||||
data, err := os.ReadFile(filepath.Join("/ipfs", cid))
|
||||
if err == nil {
|
||||
return data, nil
|
||||
}
|
||||
// If local read fails, fall back to IPFS client
|
||||
}
|
||||
|
||||
// Use IPFS client to fetch the data
|
||||
p, err := path.NewPath("/ipfs/" + cid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n, err := ipfsClient.Unixfs().Get(ctx, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return readNodeData(n)
|
||||
}
|
||||
|
||||
func GetIPNS(ctx context.Context, name string) ([]byte, error) {
|
||||
if !initialized {
|
||||
return nil, ErrNotInitialized
|
||||
}
|
||||
|
||||
if localMount {
|
||||
// Try to read from local filesystem first
|
||||
data, err := os.ReadFile(filepath.Join("/ipns", name))
|
||||
if err == nil {
|
||||
return data, nil
|
||||
}
|
||||
// If local read fails, fall back to IPFS client
|
||||
}
|
||||
|
||||
// Use IPFS client to fetch the data
|
||||
p, err := path.NewPath("/ipns/" + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n, err := ipfsClient.Unixfs().Get(ctx, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return readNodeData(n)
|
||||
}
|
||||
|
||||
func PinCID(ctx context.Context, cid string, name string) error {
|
||||
if !initialized {
|
||||
return ErrNotInitialized
|
||||
}
|
||||
|
||||
p, err := path.NewPath(cid)
|
||||
if err != nil {
|
||||
return ErrNotInitialized
|
||||
}
|
||||
err = ipfsClient.Pin().Add(ctx, p)
|
||||
if err != nil {
|
||||
return ErrInternal
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readNodeData(n files.Node) ([]byte, error) {
|
||||
switch n := n.(type) {
|
||||
case files.File:
|
||||
return io.ReadAll(n)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported node type: %T", n)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package ipfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/boxo/files"
|
||||
"github.com/ipfs/boxo/path"
|
||||
"github.com/ipfs/kubo/client/rpc"
|
||||
)
|
||||
|
||||
type IPFSFile struct {
|
||||
node files.Node
|
||||
path string
|
||||
name string
|
||||
client *rpc.HttpApi
|
||||
}
|
||||
|
||||
func (f *IPFSFile) Close() error {
|
||||
return nil // IPFS nodes don't need to be closed
|
||||
}
|
||||
|
||||
func (f *IPFSFile) Read(p []byte) (n int, err error) {
|
||||
if file, ok := f.node.(files.File); ok {
|
||||
return file.Read(p)
|
||||
}
|
||||
return 0, fmt.Errorf("not a file")
|
||||
}
|
||||
|
||||
func (f *IPFSFile) Seek(offset int64, whence int) (int64, error) {
|
||||
if file, ok := f.node.(files.File); ok {
|
||||
return file.Seek(offset, whence)
|
||||
}
|
||||
return 0, fmt.Errorf("not a file")
|
||||
}
|
||||
|
||||
func (f *IPFSFile) Write(p []byte) (n int, err error) {
|
||||
return 0, fmt.Errorf("write operation not supported for IPFS files")
|
||||
}
|
||||
|
||||
func (f *IPFSFile) String() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
func (f *IPFSFile) Exists() (bool, error) {
|
||||
// In IPFS, if we have the node, it exists
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f *IPFSFile) CopyToFile(file File) error {
|
||||
// Implementation depends on how you want to handle copying between IPFS and other file types
|
||||
return fmt.Errorf("CopyToFile not implemented for IPFS files")
|
||||
}
|
||||
|
||||
func (f *IPFSFile) MoveToFile(file File) error {
|
||||
// Moving files in IPFS doesn't make sense in the traditional way
|
||||
return fmt.Errorf("MoveToFile not applicable for IPFS files")
|
||||
}
|
||||
|
||||
func (f *IPFSFile) Delete() error {
|
||||
// Deleting in IPFS is not straightforward, might need to implement unpinning
|
||||
return fmt.Errorf("Delete operation not supported for IPFS files")
|
||||
}
|
||||
|
||||
func (f *IPFSFile) LastModified() (*time.Time, error) {
|
||||
// IPFS doesn't have a concept of last modified time
|
||||
return nil, fmt.Errorf("LastModified not applicable for IPFS files")
|
||||
}
|
||||
|
||||
func (f *IPFSFile) Size() (uint64, error) {
|
||||
if file, ok := f.node.(files.File); ok {
|
||||
s, _ := file.Size()
|
||||
return uint64(s), nil
|
||||
}
|
||||
return 0, fmt.Errorf("not a file")
|
||||
}
|
||||
|
||||
func (f *IPFSFile) Path() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
func (f *IPFSFile) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f *IPFSFile) Touch() error {
|
||||
return fmt.Errorf("Touch operation not supported for IPFS files")
|
||||
}
|
||||
|
||||
func (f *IPFSFile) URI() string {
|
||||
return fmt.Sprintf("ipfs://%s", f.path)
|
||||
}
|
||||
|
||||
type IPFSFileSystem struct {
|
||||
client *rpc.HttpApi
|
||||
}
|
||||
|
||||
func (fs *IPFSFileSystem) NewFile(volume string, absFilePath string) (File, error) {
|
||||
p, err := path.NewPath(absFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err := fs.client.Unixfs().Get(context.Background(), p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &IPFSFile{
|
||||
node: node,
|
||||
path: absFilePath,
|
||||
name: p.String(),
|
||||
client: fs.client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fs *IPFSFileSystem) Name() string {
|
||||
return "IPFS"
|
||||
}
|
||||
Reference in New Issue
Block a user