<commit_before>// OWNER: stan

package model

import (
	"context"
	"encoding/base64"
	"time"

	"golang.org/x/crypto/scrypt"

	"github.com/jmoiron/sqlx"
	"github.com/lib/pq"
	sqlite3 "github.com/mattn/go-sqlite3"
	"github.com/spolu/settle/lib/db"
	"github.com/spolu/settle/lib/errors"
	"github.com/spolu/settle/lib/token"
	"github.com/spolu/settle/register"
)

// User represents a user object. Users are not tied to a mint user until they
// are verified.
type User struct {
	Token   string
	Created time.Time

	Status   register.UsrStatus
	Username string
	Email    string

	Secret   string
	Password string

	MintToken *string `db:"mint_token"`
}

// NewUserResource generates a new user resource.
func NewUserResource(
	ctx context.Context,
	user *User,
) register.UserResource {
	return register.UserResource{
		ID:       user.Token,
		Created:  user.Created.UnixNano() / register.TimeResolutionNs,
		Status:   user.Status,
		Username: user.Username,
		Email:    user.Email,
	}
}

// CreateUser creates and stores a new User object.
func CreateUser(
	ctx context.Context,
	username string,
	email string,
) (*User, error) {
	user := User{
		Token:   token.New("user"),
		Created: time.Now().UTC(),

		Status:   register.UsrStUnverified,
		Username: username,
		Email:    email,
	}

	h, err := scrypt.Key([]byte(token.RandStr()), []byte(user.Token), 16384, 8, 1, 64)
	if err != nil {
		return nil, errors.Trace(err)
	}
	user.Secret = base64.URLEncoding.EncodeToString(h)

	h, err = scrypt.Key([]byte(token.RandStr()), []byte(user.Token), 16384, 8, 1, 64)
	if err != nil {
		return nil, errors.Trace(err)
	}
	user.Password = base64.URLEncoding.EncodeToString(h)

	ext := db.Ext(ctx, "register")
	if _, err := sqlx.NamedExec(ext, `
INSERT INTO users
  (token, created, status, username, email, secret, password)
VALUES
  (:token, :created, :status, :username, :email, :secret, :password)
`, user); err != nil {
		switch err := err.(type) {
		case *pq.Error:
			if err.Code.Name() == "unique_violation" {
				return nil, errors.Trace(ErrUniqueConstraintViolation{err})
			}
		case sqlite3.Error:
			if err.ExtendedCode == sqlite3.ErrConstraintUnique {
				return nil, errors.Trace(ErrUniqueConstraintViolation{err})
			}
		}
		return nil, errors.Trace(err)
	}

	return &user, nil
}

// Save updates the object database representation with the in-memory values.
func (u *User) Save(
	ctx context.Context,
) error {
	ext := db.Ext(ctx, "register")
	_, err := sqlx.NamedExec(ext, `
UPDATE users<commit_msg>Make secret and password 32 bits<commit_after>// OWNER: stan

package model

import (
	"context"
	"encoding/base64"
	"time"

	"golang.org/x/crypto/scrypt"

	"github.com/jmoiron/sqlx"
	"github.com/lib/pq"
	sqlite3 "github.com/mattn/go-sqlite3"
	"github.com/spolu/settle/lib/db"
	"github.com/spolu/settle/lib/errors"
	"github.com/spolu/settle/lib/token"
	"github.com/spolu/settle/register"
)

// User represents a user object. Users are not tied to a mint user until they
// are verified.
type User struct {
	Token   string
	Created time.Time

	Status   register.UsrStatus
	Username string
	Email    string

	Secret   string
	Password string

	MintToken *string `db:"mint_token"`
}

// NewUserResource generates a new user resource.
func NewUserResource(
	ctx context.Context,
	user *User,
) register.UserResource {
	return register.UserResource{
		ID:       user.Token,
		Created:  user.Created.UnixNano() / register.TimeResolutionNs,
		Status:   user.Status,
		Username: user.Username,
		Email:    user.Email,
	}
}

// CreateUser creates and stores a new User object.
func CreateUser(
	ctx context.Context,
	username string,
	email string,
) (*User, error) {
	user := User{
		Token:   token.New("user"),
		Created: time.Now().UTC(),

		Status:   register.UsrStUnverified,
		Username: username,
		Email:    email,
	}

	h, err := scrypt.Key([]byte(token.RandStr()), []byte(user.Token), 16384, 8, 1, 32)
	if err != nil {
		return nil, errors.Trace(err)
	}
	user.Secret = base64.RawURLEncoding.EncodeToString(h)

	h, err = scrypt.Key([]byte(token.RandStr()), []byte(user.Token), 16384, 8, 1, 32)
	if err != nil {
		return nil, errors.Trace(err)
	}
	user.Password = base64.RawURLEncoding.EncodeToString(h)

	ext := db.Ext(ctx, "register")
	if _, err := sqlx.NamedExec(ext, `
INSERT INTO users
  (token, created, status, username, email, secret, password)
VALUES
  (:token, :created, :status, :username, :email, :secret, :password)
`, user); err != nil {
		switch err := err.(type) {
		case *pq.Error:
			if err.Code.Name() == "unique_violation" {
				return nil, errors.Trace(ErrUniqueConstraintViolation{err})
			}
		case sqlite3.Error:
			if err.ExtendedCode == sqlite3.ErrConstraintUnique {
				return nil, errors.Trace(ErrUniqueConstraintViolation{err})
			}
		}
		return nil, errors.Trace(err)
	}

	return &user, nil
}

// Save updates the object database representation with the in-memory values.
func (u *User) Save(
	ctx context.Context,
) error {
	ext := db.Ext(ctx, "register")
	_, err := sqlx.NamedExec(ext, `
UPDATE users
{'fraction_non_alphanumeric': 0.1383422778771616, 'fraction_numerical': 0.011528523156430133, 'mean_word_length': 3.866537717601547, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1840312', 'n_tokens_mistral': 1912, 'n_tokens_neox': 1771, 'n_words': 442}