Skip to content

Commit

Permalink
Merge pull request #1097 from IgnisDa/issue-1096
Browse files Browse the repository at this point in the history
Add `user export` subcommand
  • Loading branch information
FreddyDevelop authored Oct 17, 2023
2 parents 45c2442 + 10ba936 commit 1b33191
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 55 deletions.
82 changes: 82 additions & 0 deletions backend/cmd/user/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package user

import (
"encoding/json"
"fmt"
"log"
"os"

"github.com/spf13/cobra"
"github.com/teamhanko/hanko/backend/config"
"github.com/teamhanko/hanko/backend/persistence"
)

func NewExportCommand() *cobra.Command {
var (
configFile string
outputFile string
)

cmd := &cobra.Command{
Use: "export",
Short: "Export users from database into a Json file",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
cfg, err := config.Load(&configFile)
if err != nil {
log.Fatal(err)
}
persister, err := persistence.New(cfg.Database)
if err != nil {
log.Fatal(err)
}
err = export(persister, outputFile)
if err != nil {
log.Fatal(err)
}
log.Println(fmt.Sprintf("Successfully exported users to %s", outputFile))
},
}

cmd.Flags().StringVar(&configFile, "config", config.DefaultConfigFilePath, "config file")
cmd.Flags().StringVarP(&outputFile, "outputFile", "o", "", "The path of the output file.")
err := cmd.MarkFlagRequired("outputFile")
if err != nil {
log.Println(err)
}
return cmd
}

func export(persister persistence.Persister, outFile string) error {
var entries []ImportOrExportEntry
users, err := persister.GetUserPersister().All()
if err != nil {
return fmt.Errorf("failed to get list of users: %w", err)
}
for _, user := range users {
var emails []ImportOrExportEmail
for _, email := range user.Emails {
emails = append(emails, ImportOrExportEmail{
Address: email.Address,
IsPrimary: email.IsPrimary(),
IsVerified: email.Verified,
})
}
entry := ImportOrExportEntry{
UserID: user.ID.String(),
Emails: emails,
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
}
entries = append(entries, entry)
}
bytes, err := json.Marshal(entries)
if err != nil {
return err
}
err = os.WriteFile(outFile, bytes, 0600)
if err != nil {
return err
}
return nil
}
19 changes: 10 additions & 9 deletions backend/cmd/user/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package user
import (
"errors"
"fmt"
"github.com/gofrs/uuid"
"time"

"github.com/gofrs/uuid"
)

// ImportEmail The import format for a user's email
type ImportEmail struct {
// ImportOrExportEmail The import/export format for a user's email
type ImportOrExportEmail struct {
// Address Valid email address
Address string `json:"address" yaml:"address"`
// IsPrimary indicates if this is the primary email of the users. In the Emails array there has to be exactly one primary email.
Expand All @@ -18,10 +19,10 @@ type ImportEmail struct {
}

// Emails Array of email addresses
type Emails []ImportEmail
type Emails []ImportOrExportEmail

// ImportEntry represents a user to be imported to the Hanko database
type ImportEntry struct {
// ImportOrExportEntry represents a user to be imported/export to the Hanko database
type ImportOrExportEntry struct {
// UserID optional uuid.v4. If not provided a new one will be generated for the user
UserID string `json:"user_id" yaml:"user_id"`
// Emails List of emails
Expand All @@ -32,10 +33,10 @@ type ImportEntry struct {
UpdatedAt *time.Time `json:"updated_at" yaml:"updated_at"`
}

// ImportList a list of ImportEntries
type ImportList []ImportEntry
// ImportOrExportList a list of ImportEntries
type ImportOrExportList []ImportOrExportEntry

func (entry *ImportEntry) validate() error {
func (entry *ImportOrExportEntry) validate() error {
if len(entry.Emails) == 0 {
return errors.New(fmt.Sprintf("Entry with id: %v has got no Emails.", entry.UserID))
}
Expand Down
17 changes: 9 additions & 8 deletions backend/cmd/user/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package user

import (
"fmt"
"github.com/stretchr/testify/assert"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

const validUUID = "62418053-a2cd-47a8-9b61-4426380d263a"
Expand All @@ -27,7 +28,7 @@ func TestImportEntry_validate(t *testing.T) {
fields: fields{
UserID: "",
Emails: Emails{
ImportEmail{
ImportOrExportEmail{
Address: "[email protected]",
IsPrimary: true,
IsVerified: false,
Expand All @@ -43,7 +44,7 @@ func TestImportEntry_validate(t *testing.T) {
fields: fields{
UserID: validUUID,
Emails: Emails{
ImportEmail{
ImportOrExportEmail{
Address: "[email protected]",
IsPrimary: true,
IsVerified: false,
Expand All @@ -59,7 +60,7 @@ func TestImportEntry_validate(t *testing.T) {
fields: fields{
UserID: invalidUUID,
Emails: Emails{
ImportEmail{
ImportOrExportEmail{
Address: "[email protected]",
IsPrimary: true,
IsVerified: false,
Expand All @@ -85,7 +86,7 @@ func TestImportEntry_validate(t *testing.T) {
fields: fields{
UserID: "",
Emails: Emails{
ImportEmail{
ImportOrExportEmail{
Address: "[email protected]",
IsPrimary: false,
IsVerified: false,
Expand All @@ -101,12 +102,12 @@ func TestImportEntry_validate(t *testing.T) {
fields: fields{
UserID: "",
Emails: Emails{
ImportEmail{
ImportOrExportEmail{
Address: "[email protected]",
IsPrimary: true,
IsVerified: false,
},
ImportEmail{
ImportOrExportEmail{
Address: "[email protected]",
IsPrimary: true,
IsVerified: false,
Expand All @@ -120,7 +121,7 @@ func TestImportEntry_validate(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
entry := &ImportEntry{
entry := &ImportOrExportEntry{
UserID: tt.fields.UserID,
Emails: tt.fields.Emails,
CreatedAt: tt.fields.CreatedAt,
Expand Down
13 changes: 7 additions & 6 deletions backend/cmd/user/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package user

import (
"encoding/json"
"github.com/brianvoe/gofakeit/v6"
"github.com/gofrs/uuid"
"github.com/spf13/cobra"
"log"
"os"
"time"

"github.com/brianvoe/gofakeit/v6"
"github.com/gofrs/uuid"
"github.com/spf13/cobra"
)

var outputFile string
Expand Down Expand Up @@ -36,18 +37,18 @@ func NewGenerateCommand() *cobra.Command {
}

func generate() error {
var entries []ImportEntry
var entries []ImportOrExportEntry
for i := 0; i < count; i++ {
now := time.Now().UTC()
id, _ := uuid.NewV4()
emails := []ImportEmail{
emails := []ImportOrExportEmail{
{
Address: gofakeit.Email(),
IsPrimary: true,
IsVerified: true,
},
}
entry := ImportEntry{
entry := ImportOrExportEntry{
UserID: id.String(),
Emails: emails,
CreatedAt: &now,
Expand Down
21 changes: 11 additions & 10 deletions backend/cmd/user/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/gobuffalo/pop/v6"
"github.com/gofrs/uuid"
"github.com/spf13/cobra"
"github.com/teamhanko/hanko/backend/config"
"github.com/teamhanko/hanko/backend/persistence"
"github.com/teamhanko/hanko/backend/persistence/models"
"io"
"log"
"net/http"
"os"
"strings"
"time"

"github.com/gobuffalo/pop/v6"
"github.com/gofrs/uuid"
"github.com/spf13/cobra"
"github.com/teamhanko/hanko/backend/config"
"github.com/teamhanko/hanko/backend/persistence"
"github.com/teamhanko/hanko/backend/persistence/models"
)

func NewImportCommand() *cobra.Command {
Expand Down Expand Up @@ -103,7 +104,7 @@ func NewImportCommand() *cobra.Command {

// loadAndValidate reads json from an io.Reader so we read every entry separate and validate it. We go through the whole
// array to print out every validation error in the input data.
func loadAndValidate(input io.Reader) ([]ImportEntry, error) {
func loadAndValidate(input io.Reader) ([]ImportOrExportEntry, error) {
dec := json.NewDecoder(input)

// read the open bracket
Expand All @@ -112,14 +113,14 @@ func loadAndValidate(input io.Reader) ([]ImportEntry, error) {
return nil, err
}

users := []ImportEntry{}
users := []ImportOrExportEntry{}

numErrors := 0
index := 0
// while the array contains values
for dec.More() {
index = index + 1
var userEntry ImportEntry
var userEntry ImportOrExportEntry
// decode one ImportEntry
err := dec.Decode(&userEntry)
if err != nil {
Expand Down Expand Up @@ -152,7 +153,7 @@ func loadAndValidate(input io.Reader) ([]ImportEntry, error) {
}

// commits the list of ImportEntries to the database. Wrapped in a transaction so if something fails no new users are added.
func addToDatabase(entries []ImportEntry, persister persistence.Persister) error {
func addToDatabase(entries []ImportOrExportEntry, persister persistence.Persister) error {
tx := persister.GetConnection()
err := tx.Transaction(func(tx *pop.Connection) error {
for i, v := range entries {
Expand Down
Loading

0 comments on commit 1b33191

Please sign in to comment.