Introducing LevelDB, currently handling only users

This commit is contained in:
nemunaire 2020-04-20 11:55:37 +02:00
parent efc6246685
commit 868a4e3abd
13 changed files with 273 additions and 8 deletions

View File

@ -106,7 +106,7 @@ func apiAuthHandler(f func(*config.Options, *happydns.User, httprouter.Params, i
err: err,
status: http.StatusUnauthorized,
}.WriteResponse(w)
} else if std, err := storage.MainStore.GetUser(int(session.IdUser)); err != nil {
} else if std, err := storage.UsersStore.GetUser(int(session.IdUser)); err != nil {
APIErrorResponse{
err: err,
status: http.StatusUnauthorized,

View File

@ -52,7 +52,7 @@ func dummyAuth(_ httprouter.Params, body io.Reader) Response {
}
}
if user, err := storage.MainStore.GetUserByEmail(lf.Email); err != nil {
if user, err := storage.UsersStore.GetUserByEmail(lf.Email); err != nil {
return APIErrorResponse{
err: err,
}
@ -88,7 +88,7 @@ func checkAuth(_ httprouter.Params, body io.Reader) Response {
}
}
if user, err := storage.MainStore.GetUserByEmail(lf.Email); err != nil {
if user, err := storage.UsersStore.GetUserByEmail(lf.Email); err != nil {
return APIErrorResponse{
err: err,
}

View File

@ -40,7 +40,7 @@ func registerUser(opts *config.Options, p httprouter.Params, body io.Reader) Res
return APIErrorResponse{
err: err,
}
} else if err := storage.MainStore.CreateUser(user); err != nil {
} else if err := storage.UsersStore.CreateUser(user); err != nil {
return APIErrorResponse{
err: err,
}

1
go.mod
View File

@ -6,4 +6,5 @@ require (
github.com/go-sql-driver/mysql v1.5.0
github.com/julienschmidt/httprouter v1.3.0
github.com/miekg/dns v1.1.29
github.com/syndtr/goleveldb v1.0.0
)

17
go.sum
View File

@ -1,18 +1,31 @@
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
@ -21,3 +34,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

19
main.go
View File

@ -3,14 +3,17 @@ package main
import (
"context"
"log"
"math/rand"
"net/http"
"net/url"
"strings"
"time"
"git.happydns.org/happydns/api"
"git.happydns.org/happydns/config"
"git.happydns.org/happydns/storage"
"git.happydns.org/happydns/storage/mysql"
leveldb "git.happydns.org/happydns/storage/leveldb"
mysql "git.happydns.org/happydns/storage/mysql"
)
type ResponseWriterPrefix struct {
@ -58,6 +61,8 @@ func StripPrefix(opts *config.Options, h http.Handler) http.Handler {
func main() {
var err error
rand.Seed(time.Now().UTC().UnixNano())
// Load and parse options
var opts *config.Options
if opts, err = config.ConsolidateConfig(); err != nil {
@ -66,17 +71,27 @@ func main() {
// Initialize contents
log.Println("Opening database...")
if store, err := database.NewMySQLStorage(opts.DSN); err != nil {
if store, err := mysql.NewMySQLStorage(opts.DSN); err != nil {
log.Fatal("Cannot open the database: ", err)
} else {
storage.MainStore = store
}
defer storage.MainStore.Close()
if store, err := leveldb.NewLevelDBStorage("happydns.db"); err != nil {
log.Fatal("Cannot open the database: ", err)
} else {
storage.UsersStore = store
}
defer storage.UsersStore.Close()
log.Println("Do database migrations...")
if err = storage.MainStore.DoMigration(); err != nil {
log.Fatal("Cannot migrate database: ", err)
}
if err = storage.UsersStore.DoMigration(); err != nil {
log.Fatal("Cannot migrate users database: ", err)
}
// Serve content
log.Println("Ready, listening on", opts.Bind)

View File

@ -33,3 +33,17 @@ type Storage interface {
DeleteZone(z *happydns.Zone) error
ClearZones() error
}
type UserStorage interface {
DoMigration() error
Close() error
GetUsers() (happydns.Users, error)
GetUser(id int) (*happydns.User, error)
GetUserByEmail(email string) (*happydns.User, error)
UserExists(email string) bool
CreateUser(user *happydns.User) error
UpdateUser(user *happydns.User) error
DeleteUser(user *happydns.User) error
ClearUsers() error
}

View File

@ -0,0 +1,76 @@
package database // import "happydns.org/storage/leveldb"
import (
"encoding/json"
"fmt"
"math/rand"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/util"
)
type LevelDBStorage struct {
db *leveldb.DB
}
// NewMySQLStorage establishes the connection to the database
func NewLevelDBStorage(path string) (*LevelDBStorage, error) {
if db, err := leveldb.OpenFile(path, nil); err != nil {
return nil, err
} else {
return &LevelDBStorage{db}, nil
}
}
func (s *LevelDBStorage) DoMigration() error {
return nil
}
func (s *LevelDBStorage) Close() error {
return s.db.Close()
}
func decodeData(data []byte, v interface{}) error {
return json.Unmarshal(data, v)
}
func (s *LevelDBStorage) get(key string, v interface{}) error {
data, err := s.db.Get([]byte(key), nil)
if err != nil {
return err
}
return decodeData(data, v)
}
func (s *LevelDBStorage) put(key string, v interface{}) error {
data, err := json.Marshal(v)
if err != nil {
return err
}
return s.db.Put([]byte(key), data, nil)
}
func (s *LevelDBStorage) findKey(prefix string) (key string, id int64, err error) {
found := true
for found {
id = rand.Int63()
key = fmt.Sprintf("%s%d", prefix, id)
found, err = s.db.Has([]byte(key), nil)
if err != nil {
return
}
}
return
}
func (s *LevelDBStorage) delete(key string) error {
return s.db.Delete([]byte(key), nil)
}
func (s *LevelDBStorage) search(prefix string) iterator.Iterator {
return s.db.NewIterator(util.BytesPrefix([]byte(prefix)), nil)
}

109
storage/leveldb/user.go Normal file
View File

@ -0,0 +1,109 @@
package database
import (
"fmt"
"git.happydns.org/happydns/model"
"github.com/syndtr/goleveldb/leveldb/util"
)
func (s *LevelDBStorage) GetUsers() (users happydns.Users, err error) {
iter := s.search("user-")
defer iter.Release()
for iter.Next() {
var u happydns.User
err = decodeData(iter.Value(), &u)
if err != nil {
return
}
users = append(users, &u)
}
return
}
func (s *LevelDBStorage) GetUser(id int) (u *happydns.User, err error) {
u = &happydns.User{}
err = s.get(fmt.Sprintf("user-%d", id), &u)
return
}
func (s *LevelDBStorage) GetUserByEmail(email string) (u *happydns.User, err error) {
var users happydns.Users
users, err = s.GetUsers()
if err != nil {
return
}
for _, user := range users {
if user.Email == email {
u = user
return
}
}
return nil, fmt.Errorf("Unable to find user with email address '%s'.", email)
}
func (s *LevelDBStorage) UserExists(email string) bool {
users, err := s.GetUsers()
if err != nil {
return false
}
for _, user := range users {
if user.Email == email {
return true
}
}
return false
}
func (s *LevelDBStorage) CreateUser(u *happydns.User) error {
key, id, err := s.findKey("user-")
if err != nil {
return err
}
u.Id = id
return s.put(key, u)
}
func (s *LevelDBStorage) UpdateUser(u *happydns.User) error {
return s.put(fmt.Sprintf("user-%d", u.Id), u)
}
func (s *LevelDBStorage) DeleteUser(u *happydns.User) error {
return s.delete(fmt.Sprintf("user-%d", u.Id))
}
func (s *LevelDBStorage) ClearUsers() error {
tx, err := s.db.OpenTransaction()
if err != nil {
return err
}
iter := tx.NewIterator(util.BytesPrefix([]byte("user-")), nil)
defer iter.Release()
for iter.Next() {
err = tx.Delete(iter.Key(), nil)
if err != nil {
tx.Discard()
return err
}
}
err = tx.Commit()
if err != nil {
tx.Discard()
return err
}
return nil
}

View File

@ -72,6 +72,7 @@ func (s *MySQLStorage) DoMigration() error {
}
if err := tx.Commit(); err != nil {
tx.Rollback()
return err
}
}

View File

@ -2,7 +2,7 @@
package database // import "happydns.org/database"
const schemaVersion = 1
const schemaVersion = 2
var schemaRevisions = map[uint16]string{
1: `CREATE TABLE schema_version (
@ -35,5 +35,20 @@ CREATE TABLE zones (
storage_facility ENUM("live", "history") NOT NULL DEFAULT "live",
FOREIGN KEY(id_user) REFERENCES users(id_user)
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
`,
2: `ALTER TABLE user_sessions
DROP FOREIGN KEY user_sessions_ibfk_1;
ALTER TABLE zones
DROP FOREIGN KEY zones_ibfk_1;
ALTER TABLE users
CHANGE id_user id_user BIGINT NOT NULL AUTO_INCREMENT;
ALTER TABLE user_sessions
CHANGE id_user id_user BIGINT NOT NULL;
ALTER TABLE zones
CHANGE id_user id_user BIGINT NOT NULL;
`,
}

View File

@ -0,0 +1,14 @@
ALTER TABLE user_sessions
DROP FOREIGN KEY user_sessions_ibfk_1;
ALTER TABLE zones
DROP FOREIGN KEY zones_ibfk_1;
ALTER TABLE users
CHANGE id_user id_user BIGINT NOT NULL AUTO_INCREMENT;
ALTER TABLE user_sessions
CHANGE id_user id_user BIGINT NOT NULL;
ALTER TABLE zones
CHANGE id_user id_user BIGINT NOT NULL;

View File

@ -1,3 +1,6 @@
package storage
var MainStore Storage
var (
MainStore Storage
UsersStore UserStorage
)