Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ LOAD_ENV := doppler run --
LLM_URL := http://127.0.0.1:11434

.PHONY: all build run dev test format tidy download clean docker-build docker-run \
migrate-new migrate-up migrate-down migrate-status migrate-reset db-start db-stop db-reset swagger swagger-fmt docker-build docker-run llm-start genkit-run seed
migrate-new migrate-up migrate-down migrate-status migrate-reset db-start db-stop db-reset swagger swagger-fmt docker-build docker-run llm-start genkit-run seed cli

all: build

Expand Down Expand Up @@ -42,6 +42,9 @@ genkit-run: llm-start db-start
@$(LOAD_ENV) \
genkit start -- go run $(CMD_PATH)

cli:
@$(LOAD_ENV) go run ./cmd/cli $(filter-out $@,$(MAKECMDGOALS))

sync-users:
@$(LOAD_ENV) go run ./cmd/clerk/sync.go

Expand Down
103 changes: 103 additions & 0 deletions backend/cmd/cli/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package main

import (
"context"
"flag"
"fmt"
"log"
"os"

"github.com/generate/selfserve/config"
"github.com/generate/selfserve/internal/repository"
"github.com/generate/selfserve/internal/service/clerk"
storage "github.com/generate/selfserve/internal/service/storage/postgres"
"github.com/sethvargo/go-envconfig"
)

// command is a runnable CLI subcommand.
type command struct {
description string
run func(ctx context.Context, cfg config.Config, args []string) error
}

// commands is the registry of all available CLI subcommands.
// To add a new command: add an entry here and implement its run func below.
//
// "my-new-command": {
// description: "What this command does",
// run: runMyNewCommand,
// },
var commands = map[string]command{
"sync-users": {
description: "Sync users from Clerk into the database",
run: runSyncUsers,
},
}

func main() {
flag.Usage = printUsage
flag.Parse()

args := flag.Args()
if len(args) == 0 {
printUsage()
os.Exit(1)
}

name := args[0]
cmd, ok := commands[name]
if !ok {
fmt.Fprintf(os.Stderr, "unknown command: %q\n\n", name)
printUsage()
os.Exit(1)
}

ctx := context.Background()
var cfg config.Config
if err := envconfig.Process(ctx, &cfg); err != nil {
log.Fatal("failed to process config:", err)
}

if err := cmd.run(ctx, cfg, args[1:]); err != nil {
log.Fatalf("%s: %v", name, err)
}
}

func printUsage() {
fmt.Fprintf(os.Stderr, "Usage: cli <command> [args]\n\nAvailable commands:\n")
for name, cmd := range commands {
fmt.Fprintf(os.Stderr, " %-20s %s\n", name, cmd.description)
}
fmt.Fprintln(os.Stderr)
}

// =============================================================================
// Command implementations
// =============================================================================

func runSyncUsers(ctx context.Context, cfg config.Config, _ []string) error {
repo, err := storage.NewRepository(cfg.DB)
if err != nil {
return fmt.Errorf("failed to connect to db: %w", err)
}
defer repo.Close()

usersRepo := repository.NewUsersRepository(repo.DB)

users, err := clerk.FetchUsersFromClerk(cfg.BaseURL+"/users", cfg.SecretKey)
if err != nil {
return err
}

transformed, err := clerk.ValidateAndReformatUserData(users)
if err != nil {
return err
}

if err := usersRepo.BulkInsertUsers(ctx, transformed); err != nil {
return fmt.Errorf("failed to insert users: %w", err)
}

fmt.Println("sync-users completed successfully")
return nil
}
Loading