added a bunch of customer columns

This commit is contained in:
Vicente Ferrari Smith 2026-04-14 21:28:25 +02:00
parent f380dfe58d
commit 173c01b01b
8 changed files with 71 additions and 62 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
.envrc .env

5
.vscode/launch.json vendored
View File

@ -6,11 +6,12 @@
"configurations": [ "configurations": [
{ {
"name": "Launch Package", "name": "Launch API",
"type": "go", "type": "go",
"request": "launch", "request": "launch",
"mode": "auto", "mode": "auto",
"program": "${workspaceFolder}/cmd/api" "program": "${workspaceFolder}/cmd/api",
"envFile": "${workspaceFolder}/.env"
} }
] ]
} }

View File

@ -24,7 +24,7 @@ build/api:
.PHONY: run/api .PHONY: run/api
run/api: run/api:
@echo "Running the API" @echo "Running the API"
go run ./cmd/api go run ./cmd/api -db-dsn=${PARTY_DB_DSN}
.PHONY: db/psql .PHONY: db/psql
db/psql: db/psql:

View File

@ -80,6 +80,8 @@ var upgrader = websocket.Upgrader{
func main() { func main() {
fmt.Printf("Full Args: %v\n", os.Args)
var cfg config var cfg config
flag.IntVar(&cfg.port, "port", 4000, "API server port") flag.IntVar(&cfg.port, "port", 4000, "API server port")

View File

@ -12,12 +12,16 @@ import (
func (app *application) createUserHandler(w http.ResponseWriter, r *http.Request) { func (app *application) createUserHandler(w http.ResponseWriter, r *http.Request) {
var input struct { var input struct {
ProviderId int64 `json:"provider_id"` ProviderId int64 `json:"provider_id"`
Username string `json:"username"` Username string `json:"username"`
Email string `json:"email"` PhoneNumber string `json:"phone_number"`
Password string `json:"password"` Country string `json:"country"`
Name string `json:"name"` Email string `json:"email"`
AltName string `json:"alt_name"` 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) err := app.readJSON(w, r, &input)
@ -28,8 +32,12 @@ func (app *application) createUserHandler(w http.ResponseWriter, r *http.Request
user := &data.User{ user := &data.User{
Email: input.Email, Email: input.Email,
PhoneNumber: input.PhoneNumber,
Country: input.Country,
Name: input.Name, Name: input.Name,
AltName: sql.NullString{String: input.AltName, Valid: true}, AltName: sql.NullString{String: input.AltName, Valid: true},
DateOfBirth: input.DateOfBirth,
Address: input.Address,
Activated: false, Activated: false,
} }

View File

@ -58,10 +58,10 @@ func ValidatePasswordPlaintext(v *validator.Validator, password string) {
func ValidateUserIdentity(v *validator.Validator, userIdentity *UserIdentity) { func ValidateUserIdentity(v *validator.Validator, userIdentity *UserIdentity) {
v.Check(userIdentity.ProviderID == int64(ProviderLocal), "provider_id", "must be 1"); v.Check(userIdentity.ProviderID == int64(ProviderLocal), "provider_id", "must be 1");
if userIdentity.ProviderID == int64(ProviderLocal) { // if userIdentity.ProviderID == int64(ProviderLocal) {
v.Check(userIdentity.ProviderUserID != "", "username", "must be provided") // v.Check(userIdentity.ProviderUserID != "", "username", "must be provided")
v.Check(len(userIdentity.ProviderUserID) <= 500, "username", "must not be more than 500 bytes long") // v.Check(len(userIdentity.ProviderUserID) <= 500, "username", "must not be more than 500 bytes long")
} // }
if userIdentity.Password.plaintext != nil { if userIdentity.Password.plaintext != nil {
ValidatePasswordPlaintext(v, ValidatePasswordPlaintext(v,

View File

@ -18,14 +18,18 @@ var (
var AnonymousUser = &User{} var AnonymousUser = &User{}
type User struct { type User struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Email string `json:"email"` Email string `json:"email"`
Name string `json:"name"` PhoneNumber string `json:"phone_number"`
AltName sql.NullString `json:"alt_name"` Country string `json:"country"`
Created time.Time `json:"created"` Name string `json:"name"`
LastLogin time.Time `json:"last_login"` AltName sql.NullString `json:"alt_name"`
Activated bool `json:"activated"` DateOfBirth time.Time `json:"date_of_birth"`
Version int32 `json:"-"` 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 { func (u *User) IsAnonymous() bool {
@ -56,14 +60,18 @@ func (m UserModel) ExecuteRegistrationTx(user *User, userIdentity *UserIdentity)
defer tx.Rollback() defer tx.Rollback()
query := ` query := `
INSERT INTO users (email, name, alt_name) INSERT INTO users (email, phone_number, country, name, alt_name, date_of_birth, address)
VALUES ($1, $2, $3) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, created, last_login, version` RETURNING id, created, last_login, version`
args := []interface{}{ args := []interface{}{
user.Email, user.Email,
user.PhoneNumber,
user.Country,
user.Name, user.Name,
user.AltName, user.AltName,
user.DateOfBirth,
user.Address,
} }
err = tx.QueryRowContext(ctx, query, args...).Scan(&user.ID, &user.Created, &user.LastLogin, &user.Version) err = tx.QueryRowContext(ctx, query, args...).Scan(&user.ID, &user.Created, &user.LastLogin, &user.Version)
@ -113,36 +121,6 @@ RETURNING id, version`
return nil 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) { func (m UserModel) Get(id int64) (*User, error) {
if id < 1 { if id < 1 {
return nil, ErrRecordNotFound return nil, ErrRecordNotFound
@ -150,7 +128,7 @@ func (m UserModel) Get(id int64) (*User, error) {
// Define the SQL query for retrieving the issue data. // Define the SQL query for retrieving the issue data.
query :=` 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 FROM users
WHERE id = $1` WHERE id = $1`
@ -160,8 +138,12 @@ WHERE id = $1`
err := m.DB.QueryRow(query, id).Scan( err := m.DB.QueryRow(query, id).Scan(
&user.ID, &user.ID,
&user.Email, &user.Email,
&user.PhoneNumber,
&user.Country,
&user.Name, &user.Name,
&user.AltName, &user.AltName,
&user.DateOfBirth,
&user.Address,
&user.Created, &user.Created,
&user.LastLogin, &user.LastLogin,
&user.Activated, &user.Activated,
@ -181,7 +163,7 @@ WHERE id = $1`
func (m UserModel) GetByEmail(email string) (*User, error) { func (m UserModel) GetByEmail(email string) (*User, error) {
query :=` 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 FROM users
WHERE email = $1` WHERE email = $1`
@ -192,8 +174,12 @@ WHERE email = $1`
err := m.DB.QueryRowContext(ctx, query, email).Scan( err := m.DB.QueryRowContext(ctx, query, email).Scan(
&user.ID, &user.ID,
&user.Email, &user.Email,
&user.PhoneNumber,
&user.Country,
&user.Name, &user.Name,
&user.AltName, &user.AltName,
&user.DateOfBirth,
&user.Address,
&user.Created, &user.Created,
&user.LastLogin, &user.LastLogin,
&user.Activated, &user.Activated,
@ -219,7 +205,7 @@ func (m UserModel) GetForToken(tokenScope, tokenPlaintext string) (*User, error)
// Set up the SQL query. // Set up the SQL query.
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 FROM users
INNER JOIN tokens ON users.id = tokens.user_id INNER JOIN tokens ON users.id = tokens.user_id
WHERE tokens.hash = $1 WHERE tokens.hash = $1
@ -239,9 +225,13 @@ AND tokens.expiry > $3`
// record is found we return an ErrRecordNotFound error. // record is found we return an ErrRecordNotFound error.
err := m.DB.QueryRowContext(ctx, query, args...).Scan( err := m.DB.QueryRowContext(ctx, query, args...).Scan(
&user.ID, &user.ID,
&user.Created,
&user.Name,
&user.Email, &user.Email,
&user.PhoneNumber,
&user.Country,
&user.Name,
&user.DateOfBirth,
&user.Address,
&user.Created,
&user.Activated, &user.Activated,
&user.Version, &user.Version,
) )
@ -261,15 +251,19 @@ AND tokens.expiry > $3`
func (m UserModel) Update(user *User) error { func (m UserModel) Update(user *User) error {
query := ` query := `
UPDATE users UPDATE users
SET email = $1, name = $2, alt_name = $3, activated = $4, version = version + 1 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 = $5 AND version = $6 WHERE id = $9 AND version = $10
RETURNING version` RETURNING version`
// Create an args slice containing the values for the placeholder parameters. // Create an args slice containing the values for the placeholder parameters.
args := []interface{}{ args := []interface{}{
user.Email, user.Email,
user.PhoneNumber,
user.Country,
user.Name, user.Name,
user.AltName, user.AltName,
user.DateOfBirth,
user.Address,
user.Activated, user.Activated,
user.ID, user.ID,
user.Version, user.Version,

View File

@ -1,8 +1,12 @@
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
email citext UNIQUE NOT NULL, email citext UNIQUE NOT NULL,
phone_number TEXT,
country CHAR(2),
name TEXT NOT NULL, name TEXT NOT NULL,
alt_name TEXT, alt_name TEXT,
date_of_birth DATE,
address TEXT,
created TIMESTAMPTZ NOT NULL DEFAULT now(), created TIMESTAMPTZ NOT NULL DEFAULT now(),
last_login TIMESTAMPTZ NOT NULL DEFAULT '1970-01-01 00:00:00+00', last_login TIMESTAMPTZ NOT NULL DEFAULT '1970-01-01 00:00:00+00',
activated BOOL NOT NULL DEFAULT false, 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, user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
-- For local: the username. For OIDC: the 'sub' (Subject ID) -- 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 -- Nullable because OIDC users won't have a password in your DB
password bytea, password bytea,