diff options
Diffstat (limited to 'backend')
| -rw-r--r-- | backend/db/queries/users.sql | 14 | ||||
| -rw-r--r-- | backend/db/users.sql.go | 69 | ||||
| -rw-r--r-- | backend/go.mod | 2 | ||||
| -rw-r--r-- | backend/main.go | 57 |
4 files changed, 141 insertions, 1 deletions
diff --git a/backend/db/queries/users.sql b/backend/db/queries/users.sql new file mode 100644 index 0000000..99515d5 --- /dev/null +++ b/backend/db/queries/users.sql @@ -0,0 +1,14 @@ +-- name: CreateUser :one +INSERT INTO users (username, password_hash) +VALUES (?, ?) +RETURNING *; + +-- name: GetUserByUsername :one +SELECT id, username, password_hash, created_at +FROM users +WHERE username = ?; + +-- name: GetUserByID :one +SELECT id, username, password_hash, created_at +FROM users +WHERE id = ?; diff --git a/backend/db/users.sql.go b/backend/db/users.sql.go new file mode 100644 index 0000000..7255eba --- /dev/null +++ b/backend/db/users.sql.go @@ -0,0 +1,69 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: users.sql + +package db + +import ( + "context" +) + +const createUser = `-- name: CreateUser :one +INSERT INTO users (username, password_hash) +VALUES (?, ?) +RETURNING id, username, password_hash, created_at +` + +type CreateUserParams struct { + Username string + PasswordHash string +} + +func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) { + row := q.db.QueryRowContext(ctx, createUser, arg.Username, arg.PasswordHash) + var i User + err := row.Scan( + &i.ID, + &i.Username, + &i.PasswordHash, + &i.CreatedAt, + ) + return i, err +} + +const getUserByID = `-- name: GetUserByID :one +SELECT id, username, password_hash, created_at +FROM users +WHERE id = ? +` + +func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) { + row := q.db.QueryRowContext(ctx, getUserByID, id) + var i User + err := row.Scan( + &i.ID, + &i.Username, + &i.PasswordHash, + &i.CreatedAt, + ) + return i, err +} + +const getUserByUsername = `-- name: GetUserByUsername :one +SELECT id, username, password_hash, created_at +FROM users +WHERE username = ? +` + +func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User, error) { + row := q.db.QueryRowContext(ctx, getUserByUsername, username) + var i User + err := row.Scan( + &i.ID, + &i.Username, + &i.PasswordHash, + &i.CreatedAt, + ) + return i, err +} diff --git a/backend/go.mod b/backend/go.mod index 78ca5d0..68a6343 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -11,6 +11,7 @@ require ( github.com/mattn/go-sqlite3 v1.14.28 github.com/mmcdole/gofeed v1.3.0 github.com/vektah/gqlparser/v2 v2.5.30 + golang.org/x/crypto v0.39.0 ) require ( @@ -69,7 +70,6 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.39.0 // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect golang.org/x/mod v0.25.0 // indirect golang.org/x/net v0.41.0 // indirect diff --git a/backend/main.go b/backend/main.go index 3bbe2f7..117cfb9 100644 --- a/backend/main.go +++ b/backend/main.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "context" "database/sql" "embed" @@ -10,6 +11,7 @@ import ( "net/http" "os" "os/signal" + "strings" "syscall" "time" @@ -23,6 +25,7 @@ import ( _ "github.com/mattn/go-sqlite3" "github.com/mmcdole/gofeed" "github.com/vektah/gqlparser/v2/ast" + "golang.org/x/crypto/bcrypt" "undef.ninja/x/feedaka/db" "undef.ninja/x/feedaka/graphql" @@ -129,6 +132,53 @@ func fetchAllFeeds(ctx context.Context) error { return result.ErrorOrNil() } +func runCreateUser(database *sql.DB) { + queries := db.New(database) + reader := bufio.NewReader(os.Stdin) + + // Read username + fmt.Print("Enter username: ") + username, err := reader.ReadString('\n') + if err != nil { + log.Fatalf("Failed to read username: %v", err) + } + username = strings.TrimSpace(username) + if username == "" { + log.Fatal("Username cannot be empty") + } + + // Read password + fmt.Print("Enter password: ") + password, err := reader.ReadString('\n') + if err != nil { + log.Fatalf("Failed to read password: %v", err) + } + password = strings.TrimSpace(password) + + // Validate password length + if len(password) < 15 { + log.Fatalf("Password must be at least 15 characters long (got %d characters)", len(password)) + } + + // Hash password + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + log.Fatalf("Failed to hash password: %v", err) + } + + // Create user + ctx := context.Background() + user, err := queries.CreateUser(ctx, db.CreateUserParams{ + Username: username, + PasswordHash: string(hashedPassword), + }) + if err != nil { + log.Fatalf("Failed to create user: %v", err) + } + + log.Printf("User created successfully: ID=%d, Username=%s", user.ID, user.Username) +} + func scheduled(ctx context.Context, d time.Duration, fn func()) { ticker := time.NewTicker(d) go func() { @@ -146,6 +196,7 @@ func scheduled(ctx context.Context, d time.Duration, fn func()) { func main() { // Parse command line flags var migrate = flag.Bool("migrate", false, "Run database migrations") + var createUser = flag.Bool("create-user", false, "Create a new user") flag.Parse() port := os.Getenv("FEEDAKA_PORT") @@ -171,6 +222,12 @@ func main() { return } + // Create user mode + if *createUser { + runCreateUser(database) + return + } + err = db.ValidateSchemaVersion(database) if err != nil { log.Fatal(err) |
