internal/database: start on postgres storage implementation
This commit is contained in:
parent
28bf366c70
commit
a6fce981ef
|
@ -63,7 +63,7 @@ func NewBoltStorage(path string, key *[32]byte) (Storage, error) {
|
|||
b.rs = &boltRouteStorage{b}
|
||||
b.ts = &boltTokenStorage{b}
|
||||
|
||||
return Storage(b), nil
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Certs gets the certificate storage interface.
|
||||
|
|
|
@ -25,3 +25,19 @@ func newTestBoltStorage(t *testing.T) (Storage, string, context.Context, context
|
|||
|
||||
return st, p, ctx, cancel
|
||||
}
|
||||
|
||||
func newTestPostgresStorage(t *testing.T, url string) (Storage, context.Context, context.CancelFunc) {
|
||||
k, err := routecrypto.ParseKey(cryptoKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
st, err := NewPostgresStorage(url, k)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
return st, ctx, cancel
|
||||
}
|
||||
|
|
|
@ -194,7 +194,7 @@ func _1513982254_tokensUpSql() (*asset, error) {
|
|||
return a, nil
|
||||
}
|
||||
|
||||
var _postgresSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x94\x51\xeb\xda\x30\x14\xc5\xdf\xf3\x29\xee\xdb\xdf\x42\xf3\xb0\xa7\xc1\x44\x8a\x68\xc6\x0a\x55\xa1\x76\xdb\xa3\xc4\xe6\xba\x85\xd5\x46\x92\x28\xf3\xdb\x8f\xd4\xa8\x69\xd5\x22\x43\xc1\x07\x49\xc9\x39\xbf\x9c\x7b\x12\x4a\xa1\xe6\x5b\xfc\x02\xb2\x36\xa8\x2d\x2d\x51\x5b\xb9\x91\x25\xb7\x48\x48\x3a\x5f\xb2\xbc\x80\x74\x5e\x2c\x60\x72\xfd\x60\x08\xc0\x40\xa8\x2d\x97\x75\x0c\x82\x5b\x1e\x91\x1f\xe3\xec\x3b\x5b\xba\xf5\x24\x86\x24\x1a\x12\x72\x11\xde\xc8\x5a\x50\x55\x63\x5b\x7a\xc9\x32\x36\x29\xdc\x06\x29\x62\x08\xc5\x62\x28\x35\x72\x8b\x62\xc5\x6d\x0c\x28\xe4\xf9\x2f\x2f\xad\x3c\x60\x44\xbe\xe6\x8b\x59\x1b\xe7\xe7\x37\x96\x33\x2f\x32\x4a\x48\x96\xce\xd2\x02\x3e\x85\x10\x1a\xb7\xea\x80\xb7\x18\x53\x96\xb1\x82\xfd\x97\xe4\x2f\xb4\x94\x57\x55\xa8\x67\x5e\x7b\xae\xd0\xcd\x8f\x47\xab\x7d\x67\x30\x04\x20\x77\x8b\x66\xd0\xc8\x2b\x1d\xc3\x6f\x65\xac\xdb\xf5\xdc\x58\x1a\x49\xba\x3e\x52\x29\xba\xf8\x37\x8a\xcf\x9d\xe1\xc4\xe3\x23\x94\xe2\x7e\x7c\xb7\xfe\xce\xe4\x1d\x04\xe7\xad\x3d\x1c\x6e\x8e\x0d\x87\xa1\x1b\xa5\xe9\xde\xa0\x7e\x07\x89\xd7\x18\x25\x21\x80\xc0\x0a\x2d\x5e\xa3\x68\x97\x32\xdc\x4f\xa0\x49\x13\xc6\xf3\x69\x6f\x33\x7d\x57\xac\xfa\x83\x75\xfb\x12\x17\x6e\xa9\xb9\xbe\x6b\x25\x8e\xc1\xa1\x4c\xa9\x76\x68\x62\xc0\xbf\x3b\xa9\xd1\xac\xb8\xed\x96\xe7\xf4\x8b\xba\x37\xc0\x61\x7b\xa3\x76\x5e\x0f\x0c\x5a\x99\x5d\xcc\x3a\xa1\x79\xcc\xfe\x02\xb5\xdc\x5d\x7f\x9c\xe3\xdb\x28\x9c\x50\xff\x3b\xd0\x70\x3c\xee\xcf\xcb\x48\xee\x96\x28\x78\xdf\xfc\x34\xc2\x12\x3d\x95\xa7\x97\x38\x41\x08\x7f\x9c\x1e\x9d\x2b\x2d\x50\x90\xb5\x45\x7d\xe0\x15\x7c\x7c\x06\xc1\x8f\xe6\x63\x48\xfe\x05\x00\x00\xff\xff\x1c\x82\x69\x4c\x5a\x06\x00\x00")
|
||||
var _postgresSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x94\x4f\x8b\xea\x30\x14\xc5\xf7\xfd\x14\x77\x67\x85\x66\xf1\x56\x0f\xde\xe3\x21\xa2\x79\x4c\xa1\x2a\xd4\xce\xcc\x52\x62\x73\x9d\x09\x53\x1b\x49\xa2\x8c\xdf\x7e\x48\x5b\x35\xd1\xfa\x87\xa1\x03\x5d\x94\x40\xce\xf9\xdd\x73\x4f\x4b\x08\x94\x6c\x8d\x7f\x40\x94\x1a\x95\x21\x39\x2a\x23\x56\x22\x67\x06\x83\x78\x3a\xa7\x69\x06\xf1\x34\x9b\xc1\xe8\x74\xae\x43\x2e\xd7\x4c\x94\x11\x70\x66\x58\x1f\x5e\x86\xc9\x33\x9d\x43\x38\x88\x60\xd0\xff\x1b\x04\x47\xc9\x95\x28\x39\x91\x25\x7a\xa2\x73\x9a\xd0\x51\x06\x82\x47\xe0\xca\x44\x90\x2b\x64\x06\xf9\x82\x99\x08\x90\x8b\xc3\x2b\xcb\x8d\xd8\x21\xfc\x4f\x67\x13\x0f\x02\x5e\x9f\x68\x4a\x1b\x0d\xf8\x07\x03\x48\xe2\x49\x9c\xc1\x2f\x07\x40\xe1\x5a\xee\xf0\x02\x61\x4c\x13\x9a\xd1\xef\x49\xbe\xa1\x21\xac\x28\x5c\x3d\xdd\xd9\x4c\x8e\x4f\xb3\x0e\x25\xb7\x67\x8b\x48\xed\x89\x0e\x2b\x65\xa9\x22\x78\x97\xda\xd8\x2b\x67\x7b\x68\x5b\x43\x25\x46\x96\x7b\x22\xb8\x8b\x7c\x21\xf5\x10\x77\xcd\xd1\x44\x26\xf8\x95\xb8\x2e\xbd\xad\x49\xc7\xee\x87\x9b\xb7\x18\xec\xce\x2a\x06\x4d\x56\x52\x91\xad\x46\xd5\x31\x45\x23\x61\x21\x1c\x73\x8e\x05\x1a\x3c\x45\xe0\x95\xaf\x2d\xc3\xe1\x74\x7c\xbb\x81\x4d\x33\x8c\xfc\xc0\xd2\x6b\x46\x66\x4f\x74\xb8\x94\x7c\xef\x0c\xa4\x73\xb9\x41\x1d\x01\x7e\x6e\x84\x42\xbd\x60\xc6\x6f\x4a\xfd\xf4\xcf\x2a\x6e\x79\x6b\x07\x27\xa4\x2b\xca\x5e\x50\x47\x17\x3f\xa9\x9a\xed\x5e\x5b\x3c\x67\x5b\x16\xeb\xf8\x13\x04\x56\xe7\xce\x07\x5e\x31\xb4\x97\xa5\x2b\x8a\xf6\xc6\x38\x3f\xad\x7a\x03\x6e\x63\x1e\xcb\xb1\x91\xa8\x21\x78\x33\xca\x75\x9d\x13\x2c\x10\x10\xa5\x41\xb5\x63\x05\xf4\x7e\x03\x67\x7b\xdd\x0b\xbe\x02\x00\x00\xff\xff\x49\x2d\x7b\x8a\x23\x06\x00\x00")
|
||||
|
||||
func postgresSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
|
@ -209,7 +209,7 @@ func postgresSql() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "postgres.sql", size: 1626, mode: os.FileMode(420), modTime: time.Unix(1516564385, 0)}
|
||||
info := bindataFileInfo{name: "postgres.sql", size: 1571, mode: os.FileMode(420), modTime: time.Unix(1516571679, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
|
|
@ -1,108 +1,42 @@
|
|||
-- name: insert-certificate
|
||||
|
||||
INSERT INTO Certificates
|
||||
(domain, data)
|
||||
VALUES
|
||||
(?, ?);
|
||||
INSERT INTO Certificates(domain, data) VALUES ($1, $2);
|
||||
|
||||
-- name: find-one-certificate
|
||||
|
||||
SELECT
|
||||
(id, domain, data, created_at, edited_at, active)
|
||||
FROM Certificates
|
||||
WHERE domain=?
|
||||
LIMIT 1;
|
||||
SELECT id, domain, data, created_at, edited_at, active FROM Certificates WHERE domain = $1 LIMIT 1;
|
||||
|
||||
-- name: remove-one-certificate
|
||||
|
||||
DELETE
|
||||
FROM Certificates
|
||||
WHERE domain=?
|
||||
LIMIT 1;
|
||||
DELETE FROM Certificates WHERE domain = $1 LIMIT 1;
|
||||
|
||||
-- name: get-all-certificates
|
||||
|
||||
SELECT
|
||||
(id, domain, data, created_at, edited_at, active)
|
||||
FROM Certificates;
|
||||
SELECT id, domain, data, created_at, edited_at, active FROM Certificates;
|
||||
|
||||
-- name: insert-route
|
||||
|
||||
INSERT INTO
|
||||
Routes(creator, hostname)
|
||||
VALUES
|
||||
(?, ?);
|
||||
INSERT INTO Routes(creator, hostname) VALUES ($1, $2);
|
||||
|
||||
-- name: find-one-route-by-id
|
||||
|
||||
SELECT
|
||||
(id, creator, hostname, created_at, edited_at, active)
|
||||
FROM Routes
|
||||
WHERE id=?
|
||||
LIMIT 1;
|
||||
SELECT id, creator, hostname, created_at, edited_at, active FROM Routes WHERE id = $1 LIMIT 1;
|
||||
|
||||
-- name: find-one-route-by-host
|
||||
|
||||
SELECT
|
||||
(id, creator, hostname, created_at, edited_at, active)
|
||||
FROM Routes
|
||||
WHERE hostname=?
|
||||
LIMIT 1;
|
||||
SELECT id, creator, hostname, created_at, edited_at, active FROM Routes WHERE hostname = $1 LIMIT 1;
|
||||
|
||||
-- name: find-all-routes-for-user
|
||||
|
||||
SELECT
|
||||
(id, creator, hostname, created_at, edited_at, active)
|
||||
FROM Routes
|
||||
WHERE creator=?;
|
||||
SELECT id, creator, hostname, created_at, edited_at, active FROM Routes WHERE creator = $1;
|
||||
|
||||
-- name: delete-one-route
|
||||
|
||||
DELETE
|
||||
FROM Routes
|
||||
WHERE
|
||||
id=? AND domain=?
|
||||
LIMIT 1;
|
||||
DELETE FROM Routes WHERE id = $1 AND domain = $2 LIMIT 1;
|
||||
|
||||
-- name: insert-token
|
||||
|
||||
INSERT INTO Tokens
|
||||
(body, creator, scopes, expires_at)
|
||||
VALUES
|
||||
(?, ?, ?, ?);
|
||||
INSERT INTO Tokens(body, creator, scopes, expires_at) VALUES ($1, $2, $3, $4);
|
||||
|
||||
-- name: get-one-token
|
||||
|
||||
SELECT
|
||||
(id, body, creator, scopes, created_at, expires_at, active)
|
||||
FROM Tokens
|
||||
WHERE id=?
|
||||
LIMIT 1;
|
||||
SELECT id, body, creator, scopes, created_at, expires_at, active FROM Tokens WHERE id = $1 LIMIT 1;
|
||||
|
||||
-- name: get-one-token-by-body
|
||||
|
||||
SELECT
|
||||
(id, body, creator, scopes, created_at, expires_at, active)
|
||||
FROM Tokens
|
||||
WHERE body=?
|
||||
LIMIT 1;
|
||||
SELECT id, body, creator, scopes, created_at, expires_at, active FROM Tokens WHERE body = $1 LIMIT 1;
|
||||
|
||||
-- name: get-all-tokens-for-user
|
||||
|
||||
SELECT
|
||||
(id, body, creator, scopes, created_at, expires_at, active)
|
||||
FROM Tokens
|
||||
WHERE creator=?;
|
||||
SELECT id, body, creator, scopes, created_at, expires_at, active FROM Tokens WHERE creator = $1;
|
||||
|
||||
-- name: remove-one-token
|
||||
DELETE FROM Tokens WHERE id = $1 LIMIT 1;
|
||||
|
||||
DELETE
|
||||
FROM Tokens
|
||||
WHERE id=?
|
||||
LIMIT 1;
|
||||
|
||||
-- name: remove-expired-tokens
|
||||
|
||||
DELETE
|
||||
FROM Tokens
|
||||
WHERE expires_at - interval '7 days';
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"git.xeserv.us/xena/route/internal/database/dmigrations"
|
||||
"github.com/Xe/uuid"
|
||||
"github.com/brandur/simplebox"
|
||||
"github.com/gchaincl/dotsql"
|
||||
_ "github.com/lib/pq"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Scanner interface {
|
||||
Scan(...interface{}) error
|
||||
}
|
||||
|
||||
type PostgresStorage struct {
|
||||
ds *dotsql.DotSql
|
||||
db *sql.DB
|
||||
sb *simplebox.SimpleBox
|
||||
|
||||
//cs *postgresCertificateStorage
|
||||
rs *postgresRouteStorage
|
||||
//ts *postgresTokenStorage
|
||||
}
|
||||
|
||||
type postgresCertificateStorage struct {
|
||||
*PostgresStorage
|
||||
}
|
||||
|
||||
type postgresRouteStorage struct {
|
||||
*PostgresStorage
|
||||
}
|
||||
|
||||
type postgresTokenStorage struct {
|
||||
*PostgresStorage
|
||||
}
|
||||
|
||||
// NewPostgresStorage creates a new Storage instance backed by postgres at the
|
||||
// given URL.
|
||||
func NewPostgresStorage(url string, key *[32]byte) (Storage, error) {
|
||||
db, err := sql.Open("postgres", url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := dmigrations.Asset("postgres.sql")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
ds, err := dotsql.Load(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k := range ds.QueryMap() {
|
||||
log.Printf("preparing %s", k)
|
||||
stmt, err := ds.Prepare(db, k)
|
||||
if err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
}
|
||||
|
||||
p := &PostgresStorage{
|
||||
db: db,
|
||||
ds: ds,
|
||||
sb: simplebox.NewFromSecretKey(key),
|
||||
}
|
||||
|
||||
//p.cs = &postgresCertificateStorage{p}
|
||||
p.rs = &postgresRouteStorage{p}
|
||||
//p.ts = &postgresTokenStorage{p}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Certs gets the certificate storage interface.
|
||||
func (p *PostgresStorage) Certs() Certs { return nil }
|
||||
|
||||
// Routes gets the route storage interface.
|
||||
func (p *PostgresStorage) Routes() Routes { return p.rs }
|
||||
|
||||
// Tokens gets the token storage interface.
|
||||
func (p *PostgresStorage) Tokens() Tokens { return nil }
|
||||
|
||||
// Close cleans up resources for this Storage.
|
||||
func (p *PostgresStorage) Close() error { return p.db.Close() }
|
||||
|
||||
// interface compliance
|
||||
var (
|
||||
_ Storage = &PostgresStorage{}
|
||||
//_ Certs = &postgresCertificateStorage{}
|
||||
_ Routes = &postgresRouteStorage{}
|
||||
//_ Tokens = &postgresTokenStorage{}
|
||||
)
|
||||
|
||||
func (p *postgresRouteStorage) getRouteInner(ctx context.Context, arg string, kind string) (Route, error) {
|
||||
r, err := p.ds.QueryRow(p.db, kind, arg)
|
||||
if err != nil {
|
||||
return Route{}, err
|
||||
}
|
||||
|
||||
rt := Route{}
|
||||
|
||||
err = (&rt).Scan(r)
|
||||
if err != nil {
|
||||
return Route{}, err
|
||||
}
|
||||
|
||||
return rt, nil
|
||||
}
|
||||
|
||||
func (p *postgresRouteStorage) Get(ctx context.Context, id string) (Route, error) {
|
||||
return p.getRouteInner(ctx, id, "find-one-route-by-id")
|
||||
}
|
||||
|
||||
func (p *postgresRouteStorage) GetHost(ctx context.Context, host string) (Route, error) {
|
||||
return p.getRouteInner(ctx, host, "find-one-route-by-host")
|
||||
}
|
||||
|
||||
func (p *postgresRouteStorage) GetAll(ctx context.Context, user string) ([]Route, error) {
|
||||
var result []Route
|
||||
|
||||
rows, err := p.ds.Query(p.db, "find-all-routes-for-user", user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
rt := &Route{}
|
||||
|
||||
if err := rt.Scan(rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, *rt)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *postgresRouteStorage) Put(ctx context.Context, r Route) (Route, error) {
|
||||
if r.ID == "" {
|
||||
r.ID = uuid.New()
|
||||
}
|
||||
|
||||
_, err := p.ds.Exec(p.db, "insert-route", r.ID, r.Hostname)
|
||||
if err != nil {
|
||||
return Route{}, err
|
||||
}
|
||||
|
||||
return p.Get(ctx, r.ID)
|
||||
}
|
||||
|
||||
func (p *postgresRouteStorage) Delete(ctx context.Context, r Route) (Route, error) {
|
||||
rt, err := p.Get(ctx, r.ID)
|
||||
if err != nil {
|
||||
return Route{}, err
|
||||
}
|
||||
|
||||
_, err = p.ds.Exec(p.db, "delete-one-route", rt.ID, rt.Hostname)
|
||||
if err != nil {
|
||||
return Route{}, err
|
||||
}
|
||||
|
||||
return rt, nil
|
||||
}
|
|
@ -2,6 +2,7 @@ package database
|
|||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
proto "git.xeserv.us/xena/route/proto"
|
||||
"github.com/Xe/ln"
|
||||
|
@ -26,6 +27,10 @@ type Route struct {
|
|||
ID string `storm:"id"`
|
||||
Creator string
|
||||
Hostname string `storm:"unique"`
|
||||
|
||||
CreatedAt time.Time
|
||||
EditedAt time.Time
|
||||
Active bool
|
||||
}
|
||||
|
||||
// F https://godoc.org/github.com/Xe/ln#F
|
||||
|
@ -37,6 +42,10 @@ func (r Route) F() ln.F {
|
|||
}
|
||||
}
|
||||
|
||||
func (r *Route) Scan(row Scanner) error {
|
||||
return row.Scan(&r.ID, &r.Creator, &r.Hostname, &r.CreatedAt, &r.EditedAt, &r.Active)
|
||||
}
|
||||
|
||||
// AsProto converts this into a protobuf Route.
|
||||
func (r Route) AsProto() *proto.Route {
|
||||
return &proto.Route{
|
||||
|
|
|
@ -75,3 +75,11 @@ func TestBoltDBRouteStorage(t *testing.T) {
|
|||
|
||||
testRoutes(ctx, t, st.Routes())
|
||||
}
|
||||
|
||||
func TestPostgresRouteStorage(t *testing.T) {
|
||||
st, ctx, cancel := newTestPostgresStorage(t, os.Getenv("DATABASE_URL"))
|
||||
defer st.Close()
|
||||
defer cancel()
|
||||
|
||||
testRoutes(ctx, t, st.Routes())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue