initial commit
commit
34541d687f
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/credit-based-payments.iml" filepath="$PROJECT_DIR$/.idea/credit-based-payments.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,89 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Migrations struct {
|
||||||
|
name string
|
||||||
|
query string
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed migrations/*.sql
|
||||||
|
var migrationFiles embed.FS
|
||||||
|
|
||||||
|
var migrations []Migrations
|
||||||
|
|
||||||
|
// func NewLibSqlDB is defined in embedded.go and remote-only.go files
|
||||||
|
// these files are used to define the LibSqlDB struct and the NewLibSqlDB function
|
||||||
|
// they have different initializations based on the environment, embedded or remote-only
|
||||||
|
// Windows does not currently support the embedded database, so the remote-only file is used
|
||||||
|
|
||||||
|
// setupMigrations initializes the filesystem and reads the migration files into the migrations variable
|
||||||
|
func setupMigrations() error {
|
||||||
|
// Walk through the embedded files and read their contents
|
||||||
|
err := fs.WalkDir(migrationFiles, ".", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !d.IsDir() {
|
||||||
|
content, err := migrationFiles.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
migration := Migrations{
|
||||||
|
name: filepath.Base(path),
|
||||||
|
query: string(content),
|
||||||
|
}
|
||||||
|
migrations = append(migrations, migration)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error setting up migrations | %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate updates the connected LibSqlDB to the latest schema based on the given migrations
|
||||||
|
func (t *LibSqlDB) Migrate() error {
|
||||||
|
// check if migration table exists
|
||||||
|
var migrationsCheck string
|
||||||
|
//goland:noinspection SqlResolve
|
||||||
|
err := t.db.QueryRow("SELECT name FROM sqlite_master WHERE type='table' AND name='migrations'").Scan(&migrationsCheck)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
_, err := t.db.Exec("CREATE TABLE migrations (name TEXT NOT NULL)")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating migrations table | %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("error checking if migrations table exists | %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, migration := range migrations {
|
||||||
|
var migrationInHistory string
|
||||||
|
err = t.db.QueryRow("SELECT name FROM migrations WHERE name = ?", migration.name).Scan(&migrationInHistory)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
_, err := t.db.Exec(migration.query)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error running migration: %s | %w", migration.name, err)
|
||||||
|
}
|
||||||
|
_, err = t.db.Exec("INSERT INTO migrations (name) VALUES (?)", migration.name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error inserting migration: %s into migrations table | %w", migration.name, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("error checking if migration: %s has been run | %w", migration.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"github.com/tursodatabase/go-libsql"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LibSqlDB struct {
|
||||||
|
db *sql.DB
|
||||||
|
connector *libsql.Connector // only used for embedded replica
|
||||||
|
dir string // only used for embedded replica
|
||||||
|
}
|
||||||
|
|
||||||
|
var syncInterval = 200 * time.Millisecond
|
||||||
|
|
||||||
|
func NewLibSqlDB(primaryUrl, authToken, localDBName string) (*LibSqlDB, error) {
|
||||||
|
dir, err := os.MkdirTemp("", "libsql-*")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error creating temporary directory:", err)
|
||||||
|
return nil, fmt.Errorf("error setting up temporary directory for local database | %w", err)
|
||||||
|
}
|
||||||
|
//defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
dbPath := filepath.Join(dir, localDBName)
|
||||||
|
|
||||||
|
connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl,
|
||||||
|
libsql.WithAuthToken(authToken),
|
||||||
|
libsql.WithSyncInterval(syncInterval),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating connector | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db := sql.OpenDB(connector)
|
||||||
|
|
||||||
|
err = setupMigrations()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error setting up migrations | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LibSqlDB{
|
||||||
|
db: db,
|
||||||
|
connector: connector,
|
||||||
|
dir: dir,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *LibSqlDB) Close() error {
|
||||||
|
var resultError *multierror.Error
|
||||||
|
|
||||||
|
if err := t.db.Close(); err != nil {
|
||||||
|
resultError = multierror.Append(resultError, fmt.Errorf("failed to close database: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.connector != nil {
|
||||||
|
if err := t.connector.Close(); err != nil {
|
||||||
|
resultError = multierror.Append(resultError, fmt.Errorf("failed to close connector: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.dir != "" {
|
||||||
|
if err := os.RemoveAll(t.dir); err != nil {
|
||||||
|
resultError = multierror.Append(resultError, fmt.Errorf("failed to remove directory %s: %w", t.dir, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultError.ErrorOrNil()
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
CREATE TABLE eventlog (
|
||||||
|
eventid TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
body BLOB,
|
||||||
|
metadata BLOB,
|
||||||
|
createdat TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
|
@ -0,0 +1,41 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
_ "github.com/tursodatabase/libsql-client-go/libsql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LibSqlDB struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLibSqlDB(primaryUrl, authToken, localDBName string) (*LibSqlDB, error) {
|
||||||
|
url := primaryUrl + "?authToken=" + authToken
|
||||||
|
db, err := sql.Open("libsql", url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error setting up LibSQL db | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setupMigrations()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error setting up migrations | %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LibSqlDB{
|
||||||
|
db: db,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *LibSqlDB) Close() error {
|
||||||
|
var resultError *multierror.Error
|
||||||
|
|
||||||
|
if err := t.db.Close(); err != nil {
|
||||||
|
resultError = multierror.Append(resultError, fmt.Errorf("failed to close database: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultError.ErrorOrNil()
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
module {{.ModulePath}}
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
|
github.com/tursodatabase/go-libsql v0.0.0-20240429120401-651096bbee0b
|
||||||
|
github.com/tursodatabase/libsql-client-go v0.0.0-20240628122535-1c47b26184e8
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||||
|
github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||||
|
nhooyr.io/websocket v1.8.10 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,24 @@
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
|
github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM=
|
||||||
|
github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06/go.mod h1:FUkZ5OHjlGPjnM2UyGJz9TypXQFgYqw6AFNO1UiROTM=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/tursodatabase/go-libsql v0.0.0-20240429120401-651096bbee0b h1:R7hev4b96zgXjKbS2ZNbHBnDvyFZhH+LlMqtKH6hIkU=
|
||||||
|
github.com/tursodatabase/go-libsql v0.0.0-20240429120401-651096bbee0b/go.mod h1:TjsB2miB8RW2Sse8sdxzVTdeGlx74GloD5zJYUC38d8=
|
||||||
|
github.com/tursodatabase/libsql-client-go v0.0.0-20240628122535-1c47b26184e8 h1:XM3aeBrpNrkvi48EiKCtMNAgsiaAaAOCHAW9SaIWouo=
|
||||||
|
github.com/tursodatabase/libsql-client-go v0.0.0-20240628122535-1c47b26184e8/go.mod h1:fblU7nZYWAROzJzkpln8teKFDtdRvAOmZHeIpahY4jk=
|
||||||
|
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
|
||||||
|
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||||
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||||
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
|
nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
|
||||||
|
nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
|
@ -0,0 +1,26 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"{{.ModulePath}}/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
primaryUrl := os.Getenv("LIBSQL_DATABASE_URL")
|
||||||
|
authToken := os.Getenv("LIBSQL_AUTH_TOKEN")
|
||||||
|
|
||||||
|
tdb, err := db.NewLibSqlDB(primaryUrl, authToken, "local.db")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to open db %s: %s", primaryUrl, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tdb.Migrate()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to migrate db %s: %s", primaryUrl, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tdb.Close()
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
# LibSQL Boilerplate Golang
|
||||||
|
|
||||||
|
Copy this repo to have a golang application that is already set up to work with libSQL.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
1. Copy this repo to your new project
|
||||||
|
2. Modify two files.
|
||||||
|
|
||||||
|
go.mod needs a new package name.
|
||||||
|
main.go needs to use the package name to import from `{{packageName}}/db`
|
||||||
|
|
||||||
|
3. Set up your environment variables
|
||||||
|
|
||||||
|
LIBSQL_AUTH_TOKEN
|
||||||
|
LIBSQL_DATABASE_URL
|
Loading…
Reference in New Issue