This commit is contained in:
Bel LaPointe
2026-02-03 16:56:42 -07:00
parent 01a699186e
commit 72a976bdd0
9 changed files with 377 additions and 0 deletions

13
src/adapt.go Normal file
View File

@@ -0,0 +1,13 @@
package src
import (
"context"
"io"
"net"
)
func adapt(ctx context.Context, config Config, conn net.Conn) error {
for ctx.Err() == nil {
}
return io.EOF
}

53
src/config.go Normal file
View File

@@ -0,0 +1,53 @@
package src
import (
"context"
"encoding/json"
"io"
"os"
)
type Config struct {
Listen string `json:"LISTEN"`
ConnectionDriver string `json:"CONNECTION_DRIVER"`
ConnectionString string `json:"CONNECTION_STRING"`
db DB
}
func NewConfig(ctx context.Context) (Config, error) {
config, err := newConfig()
if err != nil {
return config, err
}
db, err := NewDB(ctx, config.ConnectionDriver, config.ConnectionString)
if err != nil {
return config, err
}
config.db = db
return config, io.EOF
}
func newConfig() (Config, error) {
config := Config{
Listen: ":10000",
ConnectionDriver: "sqlite",
ConnectionString: "/tmp/red-apter.db",
}
b, _ := json.Marshal(config)
var m map[string]any
if err := json.Unmarshal(b, &m); err != nil {
return config, err
}
for k := range m {
if v := os.Getenv(k); v != "" {
m[k] = v
}
}
b2, _ := json.Marshal(m)
if err := json.Unmarshal(b2, &config); err != nil {
return config, err
}
return config, nil
}

94
src/db.go Normal file
View File

@@ -0,0 +1,94 @@
package src
import (
"context"
"database/sql"
"encoding/json"
_ "github.com/lib/pq"
_ "modernc.org/sqlite"
)
type DB struct {
*sql.DB
}
func NewDB(ctx context.Context, driver, conn string) (DB, error) {
sql, err := sql.Open(driver, conn)
if err != nil {
return DB{}, err
}
db := DB{sql}
if err := db.setup(ctx); err != nil {
db.Close()
return db, err
}
return db, nil
}
func (db DB) setup(ctx context.Context) error {
if err := db.PingContext(ctx); err != nil {
return err
}
if _, err := db.ExecContext(ctx, `
CREATE TABLE data (
database INTEGER
, key TEXT
, value TEXT
, PRIMARY KEY (database, key)
)
`); err != nil {
return err
}
return nil
}
func (db DB) Get(ctx context.Context, database int, k string) (any, error) {
row := db.QueryRowContext(ctx, `
SELECT value
FROM data
WHERE database=$1 AND key=$2
`, database, k)
if err := row.Err(); err != nil {
return nil, err
}
var v string
if err := row.Scan(&v); err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
var a any
if err := json.Unmarshal([]byte(v), &a); err != nil {
return nil, err
}
return a, nil
}
func (db DB) Put(ctx context.Context, database int, k string, v any) error {
b, _ := json.Marshal(v)
_, err := db.ExecContext(ctx, `
INSERT INTO data (database, key, value)
VALUES ($1, $2, $3)
ON CONFLICT DO UPDATE
SET value = $3
WHERE database = $1 AND key = $2
`, database, k, string(b))
return err
}
func (db DB) Del(ctx context.Context, database int, k string) error {
_, err := db.ExecContext(ctx, `
DELETE FROM data
WHERE database=$1 AND key=$2
`, database, k)
return err
}

50
src/db_test.go Normal file
View File

@@ -0,0 +1,50 @@
package src_test
import (
"context"
"path"
"red-apter/src"
"testing"
"time"
)
func TestDBSqlite(t *testing.T) {
ctx, can := context.WithTimeout(context.Background(), time.Minute)
defer can()
db, err := src.NewDB(ctx, "sqlite", path.Join(t.TempDir(), "db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
if v, err := db.Get(ctx, 5, "k"); err != nil {
t.Error("failed get 404:", err)
} else if v != nil {
t.Error("got 404:", v)
}
if err := db.Put(ctx, 5, "k", 6); err != nil {
t.Error("failed insert:", err)
} else if err := db.Put(ctx, 5, "k", 7); err != nil {
t.Error("failed update:", err)
}
if v, err := db.Get(ctx, 5, "k"); err != nil {
t.Error("failed get:", err)
} else if v != float64(7) {
t.Errorf("wrong get: (%T) %v but wanted 7", v, v)
}
if err := db.Del(ctx, 5, "k"); err != nil {
t.Error("failed del:", err)
} else if err := db.Del(ctx, 5, "k"); err != nil {
t.Error("failed del 404:", err)
}
if v, err := db.Get(ctx, 5, "k"); err != nil {
t.Error("failed get 410:", err)
} else if v != nil {
t.Error("wrong get 410:", v)
}
}

53
src/listen.go Normal file
View File

@@ -0,0 +1,53 @@
package src
import (
"context"
"log"
"net"
"sync"
)
func listen(ctx context.Context, config Config) error {
wg := &sync.WaitGroup{}
defer wg.Wait()
listener, err := net.Listen("tcp", config.Listen)
if err != nil {
return err
}
defer listener.Close()
wg.Add(1)
go func() {
defer wg.Done()
<-ctx.Done()
listener.Close()
}()
for ctx.Err() == nil {
conn, err := listener.Accept()
if err != nil {
log.Println("error accepting:", err)
} else {
wg.Add(1)
go func() {
defer wg.Done()
handle(ctx, config, conn)
}()
}
}
return ctx.Err()
}
func handle(ctx context.Context, config Config, conn net.Conn) {
if err := _handle(ctx, config, conn); err != nil {
log.Println("error handling:", err)
}
}
func _handle(ctx context.Context, config Config, conn net.Conn) error {
defer conn.Close()
return adapt(ctx, config, conn)
}

19
src/main.go Normal file
View File

@@ -0,0 +1,19 @@
package src
import (
"context"
)
func Main(ctx context.Context) error {
config, err := NewConfig(ctx)
if err != nil {
return err
}
defer config.db.Close()
if err := listen(ctx, config); err != nil && ctx.Err() == nil {
return err
}
return nil
}