Skip to content

Commit f9f3544

Browse files
Peyton-Spencerhaaawk
authored andcommitted
example: remote
1 parent 293fe7f commit f9f3544

File tree

5 files changed

+280
-0
lines changed

5 files changed

+280
-0
lines changed

example/remote/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.env.*
2+
!.env.example
3+
local.db

example/remote/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Run
2+
3+
```
4+
go run main.go
5+
```
6+
7+
- Environment variables are loaded from `envs/.env.local` by default.
8+
- An example `.env.local` will be created if it doesn't exist.
9+
- `local` mode will save a local.db file in this directory.
10+
- `staging` and `prod` can be setup by creating `envs/.env.staging` and `envs/.env.prod` and setting the values from your Turso instance.

example/remote/envs/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
TURSO_URL=file:./local.db
2+
TURSO_AUTH_TOKEN=your-auth-token

example/remote/envs/envs.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package envs
2+
3+
import (
4+
"bufio"
5+
"embed"
6+
"errors"
7+
"fmt"
8+
"log/slog"
9+
"os"
10+
"strings"
11+
)
12+
13+
//go:embed .env.*
14+
var fs embed.FS
15+
16+
// Environment Variables
17+
var (
18+
TURSO_URL string
19+
TURSO_AUTH_TOKEN string
20+
REMOTE_ENV Env
21+
)
22+
23+
type Env string
24+
25+
const (
26+
EnvLocal Env = "local"
27+
EnvStaging Env = "staging"
28+
EnvProd Env = "prod"
29+
)
30+
31+
func (e Env) String() string {
32+
return string(e)
33+
}
34+
35+
type EnvFile string
36+
37+
func (e Env) EnvFile() EnvFile {
38+
return EnvFile(".env." + e.String())
39+
}
40+
41+
func (e EnvFile) Load() error {
42+
file, err := fs.Open(string(e))
43+
if err != nil {
44+
return err
45+
}
46+
defer file.Close()
47+
scanner := bufio.NewScanner(file)
48+
for scanner.Scan() {
49+
line := scanner.Text()
50+
if strings.HasPrefix(line, "#") {
51+
continue
52+
}
53+
parts := strings.SplitN(line, "=", 2)
54+
if len(parts) != 2 {
55+
continue
56+
}
57+
key := strings.TrimSpace(parts[0])
58+
value := strings.TrimSpace(parts[1])
59+
os.Setenv(key, value)
60+
}
61+
return scanner.Err()
62+
}
63+
64+
var didLoad = false
65+
66+
func Load() error {
67+
if didLoad {
68+
return nil
69+
}
70+
71+
env, ok := os.LookupEnv("REMOTE_ENV")
72+
if !ok {
73+
slog.Info("REMOTE_ENV not set, using local")
74+
env = "local"
75+
}
76+
REMOTE_ENV = Env(env)
77+
78+
eFile := REMOTE_ENV.EnvFile()
79+
80+
// Check if .env.local exists, if not create it with default values
81+
if REMOTE_ENV == EnvLocal {
82+
if _, err := fs.Open(".env.local"); errors.Is(err, os.ErrNotExist) {
83+
slog.Info("Creating default .env.local file")
84+
defaultEnv := []byte("TURSO_URL=file:./local.db\nTURSO_AUTH_TOKEN=your-auth-token")
85+
if err := os.WriteFile("envs/.env.local", defaultEnv, 0644); err != nil {
86+
return fmt.Errorf("failed to create default .env.local: %w", err)
87+
}
88+
slog.Info("Created default .env.local file. The defaults will work for local testing, but you'll need to update the variables to connect to your Turso instance")
89+
slog.Info("Run `go run .` to start the application")
90+
os.Exit(1)
91+
}
92+
}
93+
94+
slog.Info("Loading environment variables", "file", eFile)
95+
if err := eFile.Load(); err != nil {
96+
return err
97+
}
98+
99+
envs := []envLookup{
100+
{&TURSO_URL, "TURSO_URL"},
101+
{&TURSO_AUTH_TOKEN, "TURSO_AUTH_TOKEN"},
102+
}
103+
if err := lookupEnvs(envs); err != nil {
104+
return err
105+
}
106+
107+
didLoad = true
108+
slog.Debug("Loaded environment variables",
109+
"REMOTE_ENV", REMOTE_ENV,
110+
"DB_URL", TURSO_URL,
111+
)
112+
return nil
113+
}
114+
115+
type envLookup struct {
116+
ptr *string
117+
key string
118+
}
119+
120+
func lookupEnvs(envs []envLookup) error {
121+
var errorSlice []error
122+
for _, env := range envs {
123+
val, err := lookupEnv(env.key)
124+
if err != nil {
125+
errorSlice = append(errorSlice, err)
126+
}
127+
*env.ptr = val
128+
}
129+
if len(errorSlice) > 0 {
130+
return fmt.Errorf("failed to lookup envs: %w", errors.Join(errorSlice...))
131+
}
132+
return nil
133+
}
134+
135+
func lookupEnv(key string) (string, error) {
136+
val, ok := os.LookupEnv(key)
137+
if !ok {
138+
return "", fmt.Errorf("env %s not set", key)
139+
}
140+
return val, nil
141+
}

example/remote/main.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package main
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
"log/slog"
7+
"os"
8+
"time"
9+
10+
_ "github.com/tursodatabase/go-libsql"
11+
"github.com/tursodatabase/go-libsql/example/remote/envs"
12+
)
13+
14+
func main() {
15+
setupSlog()
16+
if err := run(); err != nil {
17+
slog.Error("error running example", "error", err)
18+
os.Exit(1)
19+
}
20+
}
21+
22+
func run() (err error) {
23+
err = envs.Load()
24+
if err != nil {
25+
return fmt.Errorf("error loading environment variables: %w", err)
26+
}
27+
// Get database URL and auth token from environment variables
28+
dbUrl := envs.TURSO_URL
29+
if dbUrl == "" {
30+
return fmt.Errorf("TURSO_URL environment variable not set")
31+
}
32+
33+
authToken := os.Getenv("TURSO_AUTH_TOKEN")
34+
if authToken != "" {
35+
dbUrl += "?authToken=" + authToken
36+
}
37+
38+
// Open database connection
39+
db, err := sql.Open("libsql", dbUrl)
40+
if err != nil {
41+
return fmt.Errorf("error opening cloud db: %w", err)
42+
}
43+
defer func() {
44+
if closeErr := db.Close(); closeErr != nil {
45+
slog.Error("error closing libsql db", "error", closeErr)
46+
if err == nil {
47+
err = closeErr
48+
}
49+
} else {
50+
slog.Debug("closed libsql db")
51+
}
52+
}()
53+
// Configure connection pool: https://github.com/tursodatabase/go-libsql/issues/13
54+
db.SetConnMaxIdleTime(9 * time.Second)
55+
56+
// Create test table
57+
_, err = db.Exec("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, name TEXT)")
58+
if err != nil {
59+
return fmt.Errorf("error creating table: %w", err)
60+
}
61+
62+
// Check if test data already exists
63+
var exists bool
64+
err = db.QueryRow("SELECT EXISTS(SELECT 1 FROM test WHERE id = 1)").Scan(&exists)
65+
if err != nil {
66+
return fmt.Errorf("error checking existing data: %w", err)
67+
}
68+
69+
// Insert test data only if it doesn't exist
70+
if !exists {
71+
_, err = db.Exec("INSERT INTO test (id, name) VALUES (?, ?)", 1, "remote test")
72+
if err != nil {
73+
return fmt.Errorf("error inserting data: %w", err)
74+
}
75+
slog.Debug("inserted test data")
76+
} else {
77+
slog.Debug("test data already exists, skipping insert")
78+
}
79+
80+
// Query the data
81+
rows, err := db.Query("SELECT * FROM test")
82+
if err != nil {
83+
return fmt.Errorf("error querying data: %w", err)
84+
}
85+
defer func() {
86+
if closeErr := rows.Close(); closeErr != nil {
87+
slog.Error("error closing rows", "error", closeErr)
88+
if err == nil {
89+
err = closeErr
90+
}
91+
}
92+
}()
93+
94+
// Print results
95+
for rows.Next() {
96+
var id int
97+
var name string
98+
if err := rows.Scan(&id, &name); err != nil {
99+
return fmt.Errorf("error scanning row: %w", err)
100+
}
101+
fmt.Printf("Row: id=%d, name=%s\n", id, name)
102+
}
103+
if err := rows.Err(); err != nil {
104+
return fmt.Errorf("error iterating rows: %w", err)
105+
}
106+
107+
slog.Debug("successfully connected and executed queries", "url", envs.TURSO_URL)
108+
return nil
109+
}
110+
111+
func setupSlog() {
112+
// Configure slog to only show log level
113+
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
114+
Level: slog.LevelDebug,
115+
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
116+
// Remove time attribute
117+
if a.Key == "time" {
118+
return slog.Attr{}
119+
}
120+
return a
121+
},
122+
}))
123+
slog.SetDefault(logger)
124+
}

0 commit comments

Comments
 (0)