Files
go-nkode/cmd/cli/main.go
2025-08-01 10:49:46 -05:00

274 lines
8.0 KiB
Go

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()
}