Extend PostgreSQL implementation and unit tests on MongoDB storage

This commit is contained in:
2023-05-02 00:34:33 +02:00
parent 1bf02aa132
commit c6ba00b74f
18 changed files with 870 additions and 752 deletions

View File

@@ -1,39 +1,40 @@
/*
___ ___ ___ _ __ __ _ ___
/ __/ _ \ / _ \| '_ \ / _` |/ _ \
| (_| (_) | (_) | |_) | (_| | (_) |
\___\___/ \___/| .__/ \__, |\___/
| | __/ |
|_| |___/
*/
package storage
import (
"context"
"database/sql"
"encoding/json"
"fmt"
_ "github.com/lib/pq"
"github.com/spf13/viper"
"os"
"strconv"
"strings"
"ariga.io/atlas/sql/postgres"
"ariga.io/atlas/sql/schema"
"github.com/lib/pq"
_ "github.com/lib/pq"
"github.com/spf13/viper"
)
type PostgresqlStorage struct {
DbConnection *sql.DB
Tables map[string]string
}
func NewPostgresqlStorage(cfg *viper.Viper) (PostgresqlStorage, error) {
var (
host = cfg.GetString("storage.db.psql.host")
port = cfg.GetString("storage.db.psql.port")
user = cfg.GetString("storage.db.psql.user")
password = cfg.GetString("storage.db.psql.password")
dbname = cfg.GetString("storage.db.psql.dbname")
host = cfg.GetString("storage.db.psql.host")
port = cfg.GetString("storage.db.psql.port")
user = cfg.GetString("storage.db.psql.user")
password = cfg.GetString("storage.db.psql.password")
dbname = cfg.GetString("storage.db.psql.dbname")
sslmode = cfg.GetString("storage.db.psql.sslmode")
pg_schema = cfg.GetString("storage.db.psql.schema")
pgtables_accounts = cfg.GetString("storage.db.psql.tables.accounts")
pgtables_accounts_auth_local = cfg.GetString("storage.db.psql.tables.accounts_auth_local")
)
portInt, _ := strconv.Atoi(port)
psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, portInt,
user, password, dbname)
psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", host, portInt, user, password, dbname, sslmode)
db, err := sql.Open("postgres", psqlconn)
if err != nil {
fmt.Println("error", err)
@@ -41,10 +42,15 @@ func NewPostgresqlStorage(cfg *viper.Viper) (PostgresqlStorage, error) {
}
err = db.Ping()
if err != nil {
fmt.Println(err)
return PostgresqlStorage{}, fmt.Errorf("connection to postgresql database failed")
}
return PostgresqlStorage{
DbConnection: db,
Tables: map[string]string{
"accounts": fmt.Sprintf("%s.%s", pg_schema, pgtables_accounts),
"accounts_auth_local": fmt.Sprintf("%s.%s", pg_schema, pgtables_accounts_auth_local),
},
}, nil
}
@@ -53,29 +59,91 @@ func (psql PostgresqlStorage) GetAccount(id string) (*Account, error) {
data, metadata, emailValidation, phoneValidation []byte
)
account := &Account{}
stmtAccounts, err := psql.DbConnection.Prepare("" +
"SELECT id, namespace, data, " +
"metadata, username, password, email, email_validation, " +
"phone_number, phone_number_validation FROM accounts a JOIN " +
"account_auth auth ON id = account_id WHERE id = $1")
if err != nil {
return nil, fmt.Errorf("psql connection failed")
}
defer stmtAccounts.Close()
err = stmtAccounts.QueryRow(id).Scan(&account.ID,
var username, password, email, phonenumber *string
req := fmt.Sprintf(`SELECT id, namespace, data, metadata, username, password, email, email_validation, phone_number, phone_number_validation
FROM %s a
LEFT JOIN %s auth ON id = account_id WHERE id = $1`, psql.Tables["accounts"], psql.Tables["accounts_auth_local"])
err := psql.DbConnection.QueryRow(req, id).Scan(&account.ID,
&account.Namespace,
&data,
&metadata,
&username,
&password,
&email,
&emailValidation,
&phonenumber,
&phoneValidation)
if err != nil {
return nil, fmt.Errorf("psql select account query failed : %s", err)
}
err = json.Unmarshal(data, &account.Data)
if err != nil {
return nil, err
}
err = json.Unmarshal(metadata, &account.Metadata)
if err != nil {
return nil, err
}
if password != nil || username != nil || email != nil || phonenumber != nil {
account.Authentication.Local = &LocalAuth{
Username: username,
Password: *password,
Email: email,
PhoneNumber: phonenumber,
}
err = json.Unmarshal(emailValidation, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneValidation, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
}
}
return account, nil
}
func (psql PostgresqlStorage) LocalAuthentication(namespace string, username *string, email *string, phone_number *string) (*Account, error) {
account := &Account{}
var (
data, metadata, emailValidation, phoneValidation []byte
)
requested_field := ""
requested_value := ""
if username != nil {
requested_field = "username"
requested_value = *username
} else if email != nil {
requested_field = "email"
requested_value = *email
} else if phone_number != nil {
requested_field = "phone_number"
requested_value = *phone_number
} else {
return nil, fmt.Errorf("localauthentication func error PSQL")
}
req := fmt.Sprintf(`SELECT id, namespace, data, metadata, username, password, email, email_validation, phone_number, phone_number_validation
FROM %s INNER JOIN %s ON id = account_id
WHERE account_namespace = $1 AND %s = $2;`, psql.Tables["accounts"], psql.Tables["accounts_auth_local"], requested_field)
account.Authentication.Local = &LocalAuth{}
err := psql.DbConnection.QueryRow(req, namespace, requested_value).Scan(
&account.ID,
&account.Namespace, &data, &metadata,
&account.Authentication.Local.Username,
&account.Authentication.Local.Password,
&account.Authentication.Local.Email,
&emailValidation,
&account.Authentication.Local.PhoneNumber,
&phoneValidation)
if err != nil {
return nil, fmt.Errorf("psql select account query failed")
}
err = json.Unmarshal(data, &account.Data)
if err != nil {
return nil, err
}
@@ -83,6 +151,10 @@ func (psql PostgresqlStorage) GetAccount(id string) (*Account, error) {
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &account.Data)
if err != nil {
return nil, err
}
err = json.Unmarshal(emailValidation, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
@@ -94,142 +166,15 @@ func (psql PostgresqlStorage) GetAccount(id string) (*Account, error) {
return account, nil
}
func (psql PostgresqlStorage) LocalAuthentication(namespace string, username string, email string,
phone_number string) (*Account, error) {
account := &Account{}
var (
data, metadata, emailValidation, phoneValidation []byte
)
if username != "" {
usernameStmt, err := psql.DbConnection.Prepare("SELECT id, namespace, data, metadata, username, " +
"password, email, email_validation, phone_number, phone_number_validation " +
"FROM accounts INNER JOIN account_auth ON accounts.id = account_auth.account_id WHERE " +
"namespace = $1 AND username = $2;")
if err != nil {
return nil, err
}
err = usernameStmt.QueryRow(namespace, username).Scan(
&account.ID,
&account.Namespace, &data, &metadata,
&account.Authentication.Local.Username,
&account.Authentication.Local.Password,
&account.Authentication.Local.Email,
&emailValidation, &account.Authentication.Local.PhoneNumber,
&phoneValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(metadata, &account.Metadata)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &account.Data)
if err != nil {
return nil, err
}
err = json.Unmarshal(emailValidation, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneValidation, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
}
return account, nil
} else if email != "" {
account.Authentication.Local.Email = email
emailStmt, err := psql.DbConnection.Prepare("SELECT id, namespace, data, metadata, username, " +
"password, email_validation, phone_number, phone_number_validation " +
"FROM accounts INNER JOIN account_auth ON " +
"accounts.id = account_auth.account_id WHERE namespace = $1 AND email = $2;")
if err != nil {
return nil, err
}
err = emailStmt.QueryRow(namespace, email).Scan(
&account.ID,
&account.Namespace,
&data, &metadata,
&account.Authentication.Local.Username,
&account.Authentication.Local.Password,
&emailValidation,
&account.Authentication.Local.PhoneNumber,
&phoneValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(metadata, &account.Metadata)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &account.Data)
if err != nil {
return nil, err
}
err = json.Unmarshal(emailValidation, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneValidation, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
}
return account, nil
} else if phone_number != "" {
account.Authentication.Local.PhoneNumber = phone_number
phoneStmt, err := psql.DbConnection.Prepare("SELECT id, namespace, " +
"data, metadata, username, password, email, " +
"email_validation, phone_number_validation FROM accounts " +
"INNER JOIN account_auth ON accounts.id = account_auth.account_id WHERE " +
"namespace = $1 AND phone_number = $2;")
if err != nil {
return nil, err
}
err = phoneStmt.QueryRow(namespace, phone_number).Scan(&account.ID,
&account.Namespace,
&data,
&metadata,
&account.Authentication.Local.Username,
&account.Authentication.Local.Password,
&account.Authentication.Local.Email,
&emailValidation,
&phoneValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(metadata, &account.Metadata)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &account.Data)
if err != nil {
return nil, err
}
err = json.Unmarshal(emailValidation, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneValidation, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
}
return account, nil
}
return nil, fmt.Errorf("localauthentication func error PSQL")
}
func (psql PostgresqlStorage) GetAccounts(namespaces []string) ([]Account, error) {
var accounts []Account
namespacesStr := "'" + strings.Join(namespaces, "', '") + "'"
query := `
SELECT accounts.id, accounts.namespace, accounts.data, accounts.metadata,
account_auth.username, account_auth.password,
account_auth.email, account_auth.email_validation,
account_auth.phone_number, account_auth.phone_number_validation
FROM accounts
INNER JOIN account_auth ON accounts.id = account_auth.account_id
WHERE accounts.namespace IN (%s)
SELECT id, namespace, data, metadata, username, password, email, email_validation, phone_number, phone_number_validation
FROM %s LEFT JOIN %s ON id = account_id
WHERE namespace IN (%s)
`
query = fmt.Sprintf(query, namespacesStr)
query = fmt.Sprintf(query, psql.Tables["accounts"], psql.Tables["accounts_auth_local"], namespacesStr)
rows, err := psql.DbConnection.Query(query)
if err != nil {
return nil, err
@@ -239,19 +184,20 @@ func (psql PostgresqlStorage) GetAccounts(namespaces []string) ([]Account, error
var account Account
var dataBytes []byte
var metadataBytes []byte
var emailValidationBytes []byte
var phoneNumberValidationBytes []byte
var emailValidation []byte
var phoneValidation []byte
var username, password, email, phonenumber *string
err := rows.Scan(
&account.ID,
&account.Namespace,
&dataBytes,
&metadataBytes,
&account.Authentication.Local.Username,
&account.Authentication.Local.Password,
&account.Authentication.Local.Email,
&emailValidationBytes,
&account.Authentication.Local.PhoneNumber,
&phoneNumberValidationBytes,
&username,
&password,
&email,
&emailValidation,
&phonenumber,
&phoneValidation,
)
if err != nil {
return nil, err
@@ -266,14 +212,22 @@ func (psql PostgresqlStorage) GetAccounts(namespaces []string) ([]Account, error
if err != nil {
return nil, err
}
err = json.Unmarshal(emailValidationBytes, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneNumberValidationBytes, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
if password != nil || username != nil || email != nil || phonenumber != nil {
account.Authentication.Local = &LocalAuth{
Username: username,
Password: *password,
Email: email,
PhoneNumber: phonenumber,
}
err = json.Unmarshal(emailValidation, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneValidation, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
}
}
accounts = append(accounts, account)
}
@@ -282,18 +236,15 @@ func (psql PostgresqlStorage) GetAccounts(namespaces []string) ([]Account, error
func (psql PostgresqlStorage) GetAccountsByIds(accountids []string) ([]Account, error) {
var accounts []Account
accountIdsStr := "'" + strings.Join(accountids, "', '") + "'"
query := `
SELECT accounts.id, accounts.namespace, accounts.data, accounts.metadata,
account_auth.username, account_auth.password,
account_auth.email, account_auth.email_validation,
account_auth.phone_number, account_auth.phone_number_validation
FROM accounts
INNER JOIN account_auth ON accounts.id = account_auth.account_id
WHERE accounts.id IN (%s)
SELECT id, namespace, data, metadata, username, password, email, email_validation, phone_number, phone_number_validation
FROM %s
LEFT JOIN %s ON id = account_id
WHERE id = ANY ($1)
`
query = fmt.Sprintf(query, accountIdsStr)
rows, err := psql.DbConnection.Query(query)
query = fmt.Sprintf(query, psql.Tables["accounts"], psql.Tables["accounts_auth_local"]) //, accountIdsStr)
rows, err := psql.DbConnection.Query(query, pq.Array(accountids))
if err != nil {
return nil, err
}
@@ -302,19 +253,21 @@ func (psql PostgresqlStorage) GetAccountsByIds(accountids []string) ([]Account,
var account Account
var dataBytes []byte
var metadataBytes []byte
var emailValidationBytes []byte
var phoneNumberValidationBytes []byte
var emailValidation []byte
var phoneValidation []byte
var username, password, email, phonenumber *string
err := rows.Scan(
&account.ID,
&account.Namespace,
&dataBytes,
&metadataBytes,
&account.Authentication.Local.Username,
&account.Authentication.Local.Password,
&account.Authentication.Local.Email,
&emailValidationBytes,
&account.Authentication.Local.PhoneNumber,
&phoneNumberValidationBytes)
&username,
&password,
&email,
&emailValidation,
&phonenumber,
&phoneValidation,
)
if err != nil {
return nil, err
}
@@ -329,14 +282,21 @@ func (psql PostgresqlStorage) GetAccountsByIds(accountids []string) ([]Account,
return nil, err
}
err = json.Unmarshal(emailValidationBytes, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneNumberValidationBytes, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
if password != nil || username != nil || email != nil || phonenumber != nil {
account.Authentication.Local = &LocalAuth{
Username: username,
Password: *password,
Email: email,
PhoneNumber: phonenumber,
}
err = json.Unmarshal(emailValidation, &account.Authentication.Local.EmailValidation)
if err != nil {
return nil, err
}
err = json.Unmarshal(phoneValidation, &account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return nil, err
}
}
accounts = append(accounts, account)
}
@@ -344,11 +304,6 @@ func (psql PostgresqlStorage) GetAccountsByIds(accountids []string) ([]Account,
}
func (psql PostgresqlStorage) CreateAccount(account Account) error {
insertAccountStmt, err := psql.DbConnection.Prepare("INSERT INTO accounts (id, namespace, data, metadata)" +
" VALUES ($1, $2, $3, $4)")
if err != nil {
return err
}
dataAccountJson, err := json.Marshal(account.Data)
if err != nil {
return err
@@ -357,43 +312,67 @@ func (psql PostgresqlStorage) CreateAccount(account Account) error {
if err != nil {
return err
}
tx, err := psql.DbConnection.BeginTx(context.Background(), nil)
if err != nil {
return err
}
defer tx.Rollback()
insertAccountStmt, err := tx.Prepare(fmt.Sprintf("INSERT INTO %s (id, namespace, data, metadata) VALUES ($1, $2, $3, $4)", psql.Tables["accounts"]))
if err != nil {
return err
}
defer insertAccountStmt.Close()
_, err = insertAccountStmt.Exec(account.ID, account.Namespace, dataAccountJson, metadataAccountJson)
if err != nil {
return err
}
insertAccountAuthStmt, err := psql.DbConnection.Prepare("INSERT INTO account_auth (account_id, username," +
" password, email, email_validation,phone_number,phone_number_validation) " +
"values($1, $2, $3, $4, $5, $6, $7)")
if err != nil {
return err
if account.Authentication.Local != nil {
emailValidationJson, err := json.Marshal(account.Authentication.Local.EmailValidation)
if err != nil {
return err
}
phoneValidationJson, err := json.Marshal(account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return err
}
insertAccountAuthStmt, err := tx.Prepare(fmt.Sprintf(`INSERT INTO %s (account_id, account_namespace, username, password, email, email_validation, phone_number, phone_number_validation)
VALUES($1, $2, $3, $4, $5, $6, $7, $8);`, psql.Tables["accounts_auth_local"]))
if err != nil {
return err
}
defer insertAccountAuthStmt.Close()
_, err = insertAccountAuthStmt.Exec(account.ID,
account.Namespace,
account.Authentication.Local.Username,
account.Authentication.Local.Password,
account.Authentication.Local.Email,
emailValidationJson,
account.Authentication.Local.PhoneNumber,
phoneValidationJson)
if err != nil {
return err
}
}
emailValidationJson, err := json.Marshal(account.Authentication.Local.EmailValidation)
if err != nil {
return err
}
phoneValidationJson, err := json.Marshal(account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return err
}
_, err = insertAccountAuthStmt.Exec(account.ID,
account.Authentication.Local.Username,
account.Authentication.Local.Password,
account.Authentication.Local.Email,
emailValidationJson,
account.Authentication.Local.PhoneNumber,
phoneValidationJson)
if err != nil {
if err := tx.Commit(); err != nil {
return err
}
return nil
}
func (psql PostgresqlStorage) UpdateAccount(account Account) error {
updateAccountStmt, err := psql.DbConnection.Prepare("update accounts set namespace=$1, data=$2, " +
" metadata=$3 where id= $4")
tx, err := psql.DbConnection.BeginTx(context.Background(), nil)
if err != nil {
return err
}
defer tx.Rollback()
dataAccountJson, err := json.Marshal(account.Data)
if err != nil {
return err
@@ -402,33 +381,77 @@ func (psql PostgresqlStorage) UpdateAccount(account Account) error {
if err != nil {
return err
}
_, err = updateAccountStmt.Exec(account.Namespace, dataAccountJson, metadataAccountJson, account.ID)
req := fmt.Sprintf("UPDATE %s SET namespace=$1, data=$2, metadata=$3 where id= $4", psql.Tables["accounts"])
_, err = tx.Exec(req, account.Namespace, dataAccountJson, metadataAccountJson, account.ID)
if err != nil {
return err
}
updateAccountAuthStmt, err := psql.DbConnection.Prepare("update account_auth set username = $1," +
" password = $2," +
" email = $3, email_validation = $4 ," +
"phone_number = $5,phone_number_validation = $6 where account_id = $7")
if err != nil {
return err
}
emailValidationJson, err := json.Marshal(account.Authentication.Local.EmailValidation)
if err != nil {
return err
}
phoneValidationJson, err := json.Marshal(account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return err
}
_, err = updateAccountAuthStmt.Exec(account.Authentication.Local.Username,
account.Authentication.Local.Password,
account.Authentication.Local.Email,
emailValidationJson,
account.Authentication.Local.PhoneNumber,
phoneValidationJson, account.ID)
if err != nil {
if account.Authentication.Local != nil {
emailValidationJson, err := json.Marshal(account.Authentication.Local.EmailValidation)
if err != nil {
return err
}
phoneValidationJson, err := json.Marshal(account.Authentication.Local.PhoneNumberValidation)
if err != nil {
return err
}
req = fmt.Sprintf(`UPDATE %s
SET username = $1, password = $2, email = $3, email_validation = $4,
phone_number = $5,phone_number_validation = $6 where account_id = $7`, psql.Tables["accounts_auth_local"])
_, err = tx.Exec(req, account.Authentication.Local.Username,
account.Authentication.Local.Password,
account.Authentication.Local.Email,
emailValidationJson,
account.Authentication.Local.PhoneNumber,
phoneValidationJson, account.ID)
if err != nil {
return err
}
}
if err := tx.Commit(); err != nil {
return err
}
return nil
}
func (psql PostgresqlStorage) Migrate() error {
ctx := context.Background()
driver, err := postgres.Open(psql.DbConnection)
if err != nil {
return err
}
existing, err := driver.InspectRealm(ctx, nil)
if err != nil {
return err
}
var desired schema.Realm
hcl, err := os.ReadFile("postgresql/schema.hcl")
if err != nil {
return err
}
err = postgres.EvalHCLBytes(hcl, &desired, nil)
if err != nil {
return err
}
diff, err := driver.RealmDiff(existing, &desired)
if err != nil {
return err
}
err = driver.ApplyChanges(ctx, diff)
if err != nil {
return err
}
return nil
}