package storage import ( "database/sql" "encoding/json" "fmt" _ "github.com/lib/pq" "github.com/spf13/viper" "regexp" "strconv" "strings" ) type PostgresqlStorage struct { DbConnection *sql.DB } 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") ) portInt, _ := strconv.Atoi(port) psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, portInt, user, password, dbname) db, err := sql.Open("postgres", psqlconn) if err != nil { fmt.Println("error", err) return PostgresqlStorage{}, fmt.Errorf("connection to postgresql failed") } err = db.Ping() if err != nil { return PostgresqlStorage{}, fmt.Errorf("connection to postgresql database failed") } return PostgresqlStorage{ DbConnection: db, }, nil } func (psql PostgresqlStorage) GetAccount(id string) (*Account, error) { var ( data, metadata, emailValidation, phoneValidation []byte ) account := &Account{} if isUUIDv4(id) { stmtAccounts, err := psql.DbConnection.Prepare("SELECT id, namespace, data, metadata FROM accounts WHERE id = $1") if err != nil { return nil, fmt.Errorf("psql connection failed") } defer stmtAccounts.Close() err = stmtAccounts.QueryRow(id).Scan(&account.ID, &account.Namespace, &data, &metadata) if err != nil { return nil, fmt.Errorf("psql select account query failed") } err = json.Unmarshal(data, &account.Data) if err != nil { return nil, err } err = json.Unmarshal(metadata, &account.Metadata) if err != nil { return nil, err } stmtAccount_auth, err := psql.DbConnection.Prepare("SELECT username, password, email, email_validation, phone_number, phone_number_validation FROM account_auth WHERE account_id= $1") if err != nil { return nil, fmt.Errorf("psql connection failed") } defer stmtAccount_auth.Close() err = stmtAccount_auth.QueryRow(id).Scan(&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 auth query failed") } 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 ) 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 account, nil } 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) ` query = fmt.Sprintf(query, namespacesStr) rows, err := psql.DbConnection.Query(query) if err != nil { return nil, err } defer rows.Close() for rows.Next() { var account Account var dataBytes []byte var metadataBytes []byte var emailValidationBytes []byte var phoneNumberValidationBytes []byte 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, ) if err != nil { return nil, err } err = json.Unmarshal(dataBytes, &account.Data) if err != nil { return nil, err } err = json.Unmarshal(metadataBytes, &account.Metadata) 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 } accounts = append(accounts, account) } return accounts, nil } func (PostgresqlStorage) GetAccountsByIds(accountids []string) ([]Account, error) { return nil, fmt.Errorf("") } 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 } metadataAccountJson, err := json.Marshal(account.Metadata) if err != nil { return err } _, 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 } 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 { 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") if err != nil { return err } dataAccountJson, err := json.Marshal(account.Data) if err != nil { return err } metadataAccountJson, err := json.Marshal(account.Metadata) if err != nil { return err } _, err = updateAccountStmt.Exec(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 { return err } return nil } func isUUIDv4(str string) bool { pattern := regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89aAbB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$`) return pattern.MatchString(str) }