package main import ( "context" "database/sql" _ "embed" "flag" "fmt" "go-nkode/internal/entities" "go-nkode/internal/models" "go-nkode/internal/repository" sqlite_queue "go-nkode/internal/sqlc" "go-nkode/sqlite" "log" "os" "path/filepath" "strings" _ "github.com/mattn/go-sqlite3" ) func main() { if len(os.Args) < 2 { log.Fatal("Please provide a command: build-db") } switch os.Args[1] { case "build-db": BuildDB() case "create-customer": CreateCustomer() case "add-user": AddUser() default: log.Fatalf("Unknown command: %s", os.Args[1]) } } func CreateCustomer() { cliCmd := flag.NewFlagSet("create-customer", flag.ExitOnError) customerIDStr := cliCmd.String("customer-id", "", "Customer UUID") dbPath := cliCmd.String("db-path", "", "Path to sqlite database") if err := cliCmd.Parse(os.Args[2:]); err != nil { log.Fatalf("Failed to parse flags: %v", err) } customerID, err := models.CustomerIDFromString(*customerIDStr) if err != nil { log.Fatalf("Failed to parse flags: %v", err) } ctx := context.Background() sqliteDb, err := sqlite_queue.OpenSqliteDb(*dbPath) queue, err := sqlite_queue.NewQueue(ctx, sqliteDb) queue.Start() defer queue.Stop() sqliteRepo := repository.NewSqliteRepository(ctx, queue) if err != nil { log.Fatal("error starting sqlite repo: ", err) } nkodePolicy := models.NewDefaultNKodePolicy() customer, err := entities.NewCustomer(nkodePolicy) customer.ID = customerID if err != nil { log.Fatal(err) } if err = sqliteRepo.CreateCustomer(*customer); err != nil { log.Fatal(err) } } func AddUser() { cliCmd := flag.NewFlagSet("add-user", flag.ExitOnError) imgPath := cliCmd.String("img-path", "", "Path to directory with image files to add to database. The total must number must equal attrs-per-key X numb-of-keys") imgType := cliCmd.String("img-type", "webp", "Image types webp, svg, png, jpeg, default webp") customerIDStr := cliCmd.String("customer-id", "", "Customer ID") dbPath := cliCmd.String("db-path", "", "Path to the database") userEmailStr := cliCmd.String("user-email", "", "User email") attrsPerKey := cliCmd.Int("attrs-per-key", -1, "Attributes per key") numbOfKeys := cliCmd.Int("numb-of-keys", -1, "Number of keys") nkodeIcons := cliCmd.String("nkode-icons", "", "common separated file names of the users nKode icons with no space. filename order sets the nkode passcode order") if err := cliCmd.Parse(os.Args[2:]); err != nil { log.Fatalf("Failed to parse flags: %v", err) } fmt.Println("os args: ", os.Args) ctx := context.Background() sqliteDb, err := sqlite_queue.OpenSqliteDb(*dbPath) queue, err := sqlite_queue.NewQueue(ctx, sqliteDb) queue.Start() defer queue.Stop() sqliteRepo := repository.NewSqliteRepository(ctx, queue) if err != nil { log.Fatal("error starting sqlite repo: ", err) } customer, err := validCustomerID(*customerIDStr, &sqliteRepo) if err != nil { log.Println("db path: ", *dbPath) log.Fatal("invalid customer id: ", err) } validateUserEmail(*userEmailStr, customer.ID, &sqliteRepo) kp := entities.KeypadDimension{ AttrsPerKey: *attrsPerKey, NumbOfKeys: *numbOfKeys, } if *attrsPerKey < entities.KeypadMin.AttrsPerKey || entities.KeypadMax.AttrsPerKey < *attrsPerKey { log.Fatalf("invalid attributes per key valid range is %d-%d", entities.KeypadMin.AttrsPerKey, entities.KeypadMax.AttrsPerKey) } if *numbOfKeys < entities.KeypadMin.NumbOfKeys || entities.KeypadMax.NumbOfKeys < *numbOfKeys { log.Fatalf("invalid number of keys. valid range is %d-%d", entities.KeypadMin.NumbOfKeys, entities.KeypadMax.NumbOfKeys) } if kp.IsDispersable() { log.Fatal("Keypad can't be dispersable") } imgs := getImgs(*imgPath, *imgType) if len(imgs) != kp.TotalAttrs() { log.Fatal("svgs in directory not equal to keypad size") } imgIDs := make([]int, len(imgs)) for idx, img := range imgs { id, err := sqliteRepo.AddSVGIcon(img) if err != nil { log.Fatal(err) } imgIDs[idx] = int(id) } iconNames := strings.Split(*nkodeIcons, ",") passcodeIdxs := getPasscodeSvgIdx(iconNames, *imgPath) if err = customer.IsValidNKode(kp, passcodeIdxs); err != nil { log.Fatal("invalid nkode: ", err) } userInterface, err := entities.NewUserInterface(&kp, models.SvgIdInterface(imgIDs)) if err != nil { log.Fatal("error creating user interface: ", err) } user, err := entities.NewUser(customer, *userEmailStr, passcodeIdxs, *userInterface, kp) if err != nil { log.Fatal("error creating user: ", err) } if err = sqliteRepo.WriteNewUser(*user); err != nil { log.Fatal("error storing user: ", err) } } func getPasscodeSvgIdx(nkodeSvgFileNames []string, svgDir string) []int { files, err := os.ReadDir(svgDir) if err != nil { log.Fatal(err) } fileNames := make([]string, 0) for _, file := range files { if file.IsDir() || filepath.Ext(file.Name()) != ".svg" { continue } fileNames = append(fileNames, file.Name()) } passcode := make([]int, 0) for _, fileName := range nkodeSvgFileNames { idx := indexOf(fileNames, fileName) if idx == -1 { log.Fatal("file does not exist in svg dir: ", fileName) } passcode = append(passcode, idx) } return passcode } func indexOf(slice []string, value string) int { for i, v := range slice { if v == value { return i } } return -1 // not found } func getImgs(imgDir, imgType string) []string { files, err := os.ReadDir(imgDir) if err != nil { log.Fatalf("error opening dir: %s with err: %v", imgDir, err) } imgs := make([]string, 0) for _, file := range files { if file.IsDir() || filepath.Ext(file.Name()) != "."+imgType { continue } filePath := filepath.Join(imgDir, file.Name()) content, err := os.ReadFile(filePath) if err != nil { log.Println("Error reading file:", filePath, err) continue } imgs = append(imgs, string(content)) } return imgs } func validCustomerID(id string, repo *repository.SqliteRepository) (entities.Customer, error) { cID, err := models.CustomerIDFromString(id) if err != nil { return entities.Customer{}, err } customer, err := repo.GetCustomer(cID) if err != nil { return entities.Customer{}, err } return *customer, nil } func validateUserEmail(email string, customerID models.CustomerID, repo *repository.SqliteRepository) { userEmail, err := models.ParseEmail(email) if err != nil { log.Fatal("user email error: ", err) } _, err = repo.GetUser(userEmail, customerID) if err == nil { log.Fatal("user already exists") } fmt.Println("user email is valid:", userEmail) } func BuildDB() { sqliteSchema, err := sqlite.FS.ReadFile("schema.sql") if err != nil { log.Fatal(err) } cliCmd := flag.NewFlagSet("build-db", flag.ExitOnError) dbPath := cliCmd.String("db-path", "", "Path to the database") imgPath := cliCmd.String("img-path", "", "Path to the directory with images") imgType := cliCmd.String("img-type", "svg", "Image types webp, svg, png, jpeg, default webp") if err = cliCmd.Parse(os.Args[2:]); err != nil { log.Fatalf("Failed to parse flags: %v", err) } if err = MakeTables(*dbPath, string(sqliteSchema)); err != nil { log.Fatal(err) } ctx := context.Background() sqliteDb, err := sqlite_queue.OpenSqliteDb(*dbPath) queue, err := sqlite_queue.NewQueue(ctx, sqliteDb) queue.Start() defer queue.Stop() sqliteRepo := repository.NewSqliteRepository(ctx, queue) if err != nil { log.Fatal("error starting sqlite repo: ", err) } numbImgs := IconImgToSqlite(&sqliteRepo, *imgPath, *imgType) log.Printf("Successfully added %d Images in %s to the database at %s\n", numbImgs, *imgPath, *dbPath) } func IconImgToSqlite(repo *repository.SqliteRepository, imgDir, imgType string) int { imgs := getImgs(imgDir, imgType) for _, img := range imgs { if _, err := repo.AddSVGIcon(img); err != nil { log.Fatal(err) } } return len(imgs) } func MakeTables(dbPath string, schema string) error { if _, err := os.Stat(dbPath); os.IsNotExist(err) { if err = os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil { return err } if _, err = os.Create(dbPath); err != nil { return err } } db, err := sql.Open("sqlite3", dbPath) if err != nil { return err } if _, err = db.Exec(schema); err != nil { return err } return db.Close() }