package store import ( "context" "fmt" "strings" "gitea.mynodes.xyz/ilker/llmgw/internal/secrets" ) // Open picks a backend from what the address looks like. // // A path is SQLite and a postgres:// URL is PostgreSQL, so an existing // configuration keeps working untouched and moving to PostgreSQL is one line // changed rather than a new setting to discover. A gateway on one machine has // no reason to run a database server, and a gateway that has outgrown that // should not have to be reconfigured twice. // // An address of the form @/path/to/file reads the real address out of that // file, which is how a DSN carrying a password stays out of a configuration // file that gets copied, diffed and pasted into issues. func Open(ctx context.Context, address string) (*DB, error) { address, err := resolveAddress(address) if err != nil { return nil, err } if isPostgres(address) { return OpenPostgres(ctx, address) } return OpenSQLite(ctx, address) } // isPostgres reports whether an address names a PostgreSQL server. // // By scheme, because that is the only part of a DSN that is reliably there. // A bare host:port would be ambiguous with a relative file path, so it is not // accepted: an address this cannot classify should fail loudly rather than // quietly create a SQLite file named after someone's database server. func isPostgres(address string) bool { return strings.HasPrefix(address, "postgres://") || strings.HasPrefix(address, "postgresql://") } // resolveAddress expands an @file indirection. // // The file is read whole and trimmed, so a trailing newline — which every // editor and every `echo` adds — does not become part of a password. func resolveAddress(address string) (string, error) { if !strings.HasPrefix(address, "@") { return address, nil } path := strings.TrimPrefix(address, "@") // A file, or env: or vault: (the secrets package says how). b, err := secrets.Read(context.Background(), path) if err != nil { return "", fmt.Errorf("reading the store address from %s: %w", path, err) } out := strings.TrimSpace(string(b)) if out == "" { return "", fmt.Errorf("%s is empty, so there is no store address in it", path) } return out, nil } // ResolveAddress expands an @file store address, as Open does. func ResolveAddress(address string) (string, error) { return resolveAddress(address) } // IsPostgres reports whether a resolved address names a PostgreSQL server. func IsPostgres(address string) bool { return isPostgres(address) }