diff --git a/.gitignore b/.gitignore index 7a6353d..4c49bd7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -.envrc +.env diff --git a/.vscode/launch.json b/.vscode/launch.json index 689a0d4..f132fde 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,11 +6,12 @@ "configurations": [ { - "name": "Launch Package", + "name": "Launch API", "type": "go", "request": "launch", "mode": "auto", - "program": "${workspaceFolder}/cmd/api" + "program": "${workspaceFolder}/cmd/api", + "envFile": "${workspaceFolder}/.env" } ] } \ No newline at end of file diff --git a/Makefile b/Makefile index 80b733d..05dcb63 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ build/api: .PHONY: run/api run/api: @echo "Running the API" - go run ./cmd/api + go run ./cmd/api -db-dsn=${PARTY_DB_DSN} .PHONY: db/psql db/psql: diff --git a/cmd/api/main.go b/cmd/api/main.go index eaf76ef..483499c 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -80,6 +80,8 @@ var upgrader = websocket.Upgrader{ func main() { + fmt.Printf("Full Args: %v\n", os.Args) + var cfg config flag.IntVar(&cfg.port, "port", 4000, "API server port") diff --git a/cmd/api/users.go b/cmd/api/users.go index dbb473d..281daf7 100644 --- a/cmd/api/users.go +++ b/cmd/api/users.go @@ -12,12 +12,16 @@ import ( func (app *application) createUserHandler(w http.ResponseWriter, r *http.Request) { var input struct { - ProviderId int64 `json:"provider_id"` - Username string `json:"username"` - Email string `json:"email"` - Password string `json:"password"` - Name string `json:"name"` - AltName string `json:"alt_name"` + ProviderId int64 `json:"provider_id"` + Username string `json:"username"` + PhoneNumber string `json:"phone_number"` + Country string `json:"country"` + Email string `json:"email"` + Password string `json:"password"` + Name string `json:"name"` + AltName string `json:"alt_name"` + DateOfBirth time.Time `json:"date_of_birth"` + Address string `json:"address"` } err := app.readJSON(w, r, &input) @@ -28,8 +32,12 @@ func (app *application) createUserHandler(w http.ResponseWriter, r *http.Request user := &data.User{ Email: input.Email, + PhoneNumber: input.PhoneNumber, + Country: input.Country, Name: input.Name, AltName: sql.NullString{String: input.AltName, Valid: true}, + DateOfBirth: input.DateOfBirth, + Address: input.Address, Activated: false, } diff --git a/internal/data/user_identities.go b/internal/data/user_identities.go index 72c6564..3e2bf96 100644 --- a/internal/data/user_identities.go +++ b/internal/data/user_identities.go @@ -58,10 +58,10 @@ func ValidatePasswordPlaintext(v *validator.Validator, password string) { func ValidateUserIdentity(v *validator.Validator, userIdentity *UserIdentity) { v.Check(userIdentity.ProviderID == int64(ProviderLocal), "provider_id", "must be 1"); - if userIdentity.ProviderID == int64(ProviderLocal) { - v.Check(userIdentity.ProviderUserID != "", "username", "must be provided") - v.Check(len(userIdentity.ProviderUserID) <= 500, "username", "must not be more than 500 bytes long") - } + // if userIdentity.ProviderID == int64(ProviderLocal) { + // v.Check(userIdentity.ProviderUserID != "", "username", "must be provided") + // v.Check(len(userIdentity.ProviderUserID) <= 500, "username", "must not be more than 500 bytes long") + // } if userIdentity.Password.plaintext != nil { ValidatePasswordPlaintext(v, diff --git a/internal/data/users.go b/internal/data/users.go index 58fbc3f..77925f9 100644 --- a/internal/data/users.go +++ b/internal/data/users.go @@ -18,14 +18,18 @@ var ( var AnonymousUser = &User{} type User struct { - ID int64 `json:"id"` - Email string `json:"email"` - Name string `json:"name"` - AltName sql.NullString `json:"alt_name"` - Created time.Time `json:"created"` - LastLogin time.Time `json:"last_login"` - Activated bool `json:"activated"` - Version int32 `json:"-"` + ID int64 `json:"id"` + Email string `json:"email"` + PhoneNumber string `json:"phone_number"` + Country string `json:"country"` + Name string `json:"name"` + AltName sql.NullString `json:"alt_name"` + DateOfBirth time.Time `json:"date_of_birth"` + Address string `json:"address"` + Created time.Time `json:"created"` + LastLogin time.Time `json:"last_login"` + Activated bool `json:"activated"` + Version int32 `json:"-"` } func (u *User) IsAnonymous() bool { @@ -56,14 +60,18 @@ func (m UserModel) ExecuteRegistrationTx(user *User, userIdentity *UserIdentity) defer tx.Rollback() query := ` -INSERT INTO users (email, name, alt_name) -VALUES ($1, $2, $3) +INSERT INTO users (email, phone_number, country, name, alt_name, date_of_birth, address) +VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, created, last_login, version` args := []interface{}{ user.Email, + user.PhoneNumber, + user.Country, user.Name, user.AltName, + user.DateOfBirth, + user.Address, } err = tx.QueryRowContext(ctx, query, args...).Scan(&user.ID, &user.Created, &user.LastLogin, &user.Version) @@ -113,36 +121,6 @@ RETURNING id, version` return nil } -// func (m UserModel) Insert(user *User) error { -// query := ` -// INSERT INTO users (email, name, alt_name, activated) -// VALUES ($1, $2, $3, $4) -// RETURNING id, created, last_login, version` - -// args := []interface{}{ -// user.Email, -// user.Name, -// user.AltName, -// user.Activated, -// } - -// ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) -// defer cancel() - -// err := m.DB.QueryRowContext(ctx, query, args...).Scan(&user.ID, &user.Created, &user.LastLogin, &user.Version) -// if err != nil { -// switch { -// case err.Error() == -// `pq: duplicate key value violates unique constraint "users_email_key"`: -// return ErrDuplicateEmail -// default: -// return err -// } -// } - -// return nil -// } - func (m UserModel) Get(id int64) (*User, error) { if id < 1 { return nil, ErrRecordNotFound @@ -150,7 +128,7 @@ func (m UserModel) Get(id int64) (*User, error) { // Define the SQL query for retrieving the issue data. query :=` -SELECT id, email, name, alt_name, created, last_login, activated, version +SELECT id, email, phone_number, country, name, alt_name, date_of_birth, address, created, last_login, activated, version FROM users WHERE id = $1` @@ -160,8 +138,12 @@ WHERE id = $1` err := m.DB.QueryRow(query, id).Scan( &user.ID, &user.Email, + &user.PhoneNumber, + &user.Country, &user.Name, &user.AltName, + &user.DateOfBirth, + &user.Address, &user.Created, &user.LastLogin, &user.Activated, @@ -181,7 +163,7 @@ WHERE id = $1` func (m UserModel) GetByEmail(email string) (*User, error) { query :=` -SELECT id, email, name, alt_name, created, last_login, activated, version +SELECT id, email, phone, country, name, alt_name, date_of_birth, address, created, last_login, activated, version FROM users WHERE email = $1` @@ -192,8 +174,12 @@ WHERE email = $1` err := m.DB.QueryRowContext(ctx, query, email).Scan( &user.ID, &user.Email, + &user.PhoneNumber, + &user.Country, &user.Name, &user.AltName, + &user.DateOfBirth, + &user.Address, &user.Created, &user.LastLogin, &user.Activated, @@ -219,7 +205,7 @@ func (m UserModel) GetForToken(tokenScope, tokenPlaintext string) (*User, error) // Set up the SQL query. query :=` -SELECT users.id, users.created, users.name, users.email, users.activated, users.version +SELECT users.id, users.email, user.phone_number, user.country, users.name, users.date_of_birth, users.address, users.created, users.activated, users.version FROM users INNER JOIN tokens ON users.id = tokens.user_id WHERE tokens.hash = $1 @@ -239,9 +225,13 @@ AND tokens.expiry > $3` // record is found we return an ErrRecordNotFound error. err := m.DB.QueryRowContext(ctx, query, args...).Scan( &user.ID, - &user.Created, - &user.Name, &user.Email, + &user.PhoneNumber, + &user.Country, + &user.Name, + &user.DateOfBirth, + &user.Address, + &user.Created, &user.Activated, &user.Version, ) @@ -261,15 +251,19 @@ AND tokens.expiry > $3` func (m UserModel) Update(user *User) error { query := ` UPDATE users - SET email = $1, name = $2, alt_name = $3, activated = $4, version = version + 1 - WHERE id = $5 AND version = $6 + SET email = $1, phone_number = $2, country = $3, name = $4, alt_name = $5, date_of_birth = $6, address = $7, activated = $8, version = version + 1 + WHERE id = $9 AND version = $10 RETURNING version` // Create an args slice containing the values for the placeholder parameters. args := []interface{}{ user.Email, + user.PhoneNumber, + user.Country, user.Name, user.AltName, + user.DateOfBirth, + user.Address, user.Activated, user.ID, user.Version, diff --git a/migrations/000001_create_users_table.up.sql b/migrations/000001_create_users_table.up.sql index d29f312..5b453aa 100644 --- a/migrations/000001_create_users_table.up.sql +++ b/migrations/000001_create_users_table.up.sql @@ -1,8 +1,12 @@ CREATE TABLE IF NOT EXISTS users ( id BIGSERIAL PRIMARY KEY, email citext UNIQUE NOT NULL, + phone_number TEXT, + country CHAR(2), name TEXT NOT NULL, alt_name TEXT, + date_of_birth DATE, + address TEXT, created TIMESTAMPTZ NOT NULL DEFAULT now(), last_login TIMESTAMPTZ NOT NULL DEFAULT '1970-01-01 00:00:00+00', activated BOOL NOT NULL DEFAULT false, @@ -24,7 +28,7 @@ CREATE TABLE IF NOT EXISTS user_identities ( user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, -- For local: the username. For OIDC: the 'sub' (Subject ID) - provider_user_id TEXT NOT NULL, + provider_user_id TEXT, -- Nullable because OIDC users won't have a password in your DB password bytea,